micro-models-agent 2.11.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/assets/js/tabs.js +22 -3
- 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/index.js +37 -3
- 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 +273 -47
- 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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Детектор застревания: повторы, фейлы, read-only петли, bash-флейлинг, хинты.
|
|
1
2
|
import { t } from "../../i18n/index";
|
|
2
3
|
import { isTransientError } from "./transient-error";
|
|
3
4
|
const BLOCKING_PATTERNS = [
|
|
@@ -11,6 +12,7 @@ const BLOCKING_PATTERNS = [
|
|
|
11
12
|
/401/i,
|
|
12
13
|
/403/i,
|
|
13
14
|
];
|
|
15
|
+
/** Классифицирует вывод ошибки: транзиентная / блокирующая / системная. */
|
|
14
16
|
export function classifyError(output) {
|
|
15
17
|
if (isTransientError(output))
|
|
16
18
|
return "transient";
|
|
@@ -21,11 +23,11 @@ export function classifyError(output) {
|
|
|
21
23
|
return "systematic";
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* Тулы только чтения: состояние наблюдают, не меняют. Длинная серия только их
|
|
27
|
+
* без write/exec между — модель читает/разведывает, ничего не производя:
|
|
28
|
+
* read-only петля, которую детектор обязан подсветить (было: 50+ read_file
|
|
29
|
+
* итераций с нулём записей после ложноположительного реджекта готового
|
|
30
|
+
* аудита задачи).
|
|
29
31
|
*/
|
|
30
32
|
const READ_ONLY_TOOLS = new Set([
|
|
31
33
|
"read_file",
|
|
@@ -44,19 +46,19 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
44
46
|
"process_list",
|
|
45
47
|
"process_log",
|
|
46
48
|
"plan",
|
|
49
|
+
"plan_step",
|
|
47
50
|
"todo",
|
|
48
51
|
"verify",
|
|
49
52
|
"load_skill",
|
|
50
53
|
]);
|
|
51
|
-
/**
|
|
54
|
+
/** Подряд read-only вызовов, образующих петлю. */
|
|
52
55
|
const READ_ONLY_LOOP_THRESHOLD = 10;
|
|
53
56
|
/**
|
|
54
|
-
* Bash
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* trying to count/merge files on Windows).
|
|
57
|
+
* Bash-флейлинг: ≥BASH_FLAIL_THRESHOLD фейлов в последних BASH_FLAIL_WINDOW
|
|
58
|
+
* bash-попытках, независимо от перемежающихся успехов. Per-word hard-stop
|
|
59
|
+
* (bash.ts) обходится ротацией команд (wc → cat → Get-Content → …); окно
|
|
60
|
+
* ловит саму ротацию (было в ses_mt4x3zbo: 8 фейловых shell-уловок за ~12
|
|
61
|
+
* итераций при подсчёте/склейке файлов на Windows).
|
|
60
62
|
*/
|
|
61
63
|
const BASH_FLAIL_WINDOW = 6;
|
|
62
64
|
const BASH_FLAIL_THRESHOLD = 4;
|
|
@@ -112,10 +114,10 @@ const FRAMEWORK_WORDS = [
|
|
|
112
114
|
"tailwind",
|
|
113
115
|
];
|
|
114
116
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
117
|
+
* Стабильная идентичность ошибки: тул + нормализованные первые error-строки
|
|
118
|
+
* (ANSI вычищен, пути/локации заменены, пробелы склеены, кап 120 символов).
|
|
119
|
+
* Два прогона с одной первопричиной дают одну сигнатуру — она и триггерит
|
|
120
|
+
* автопоиск в вебе.
|
|
119
121
|
*/
|
|
120
122
|
export function normalizeErrorSignature(toolName, output) {
|
|
121
123
|
if (!output)
|
|
@@ -134,21 +136,20 @@ export function normalizeErrorSignature(toolName, output) {
|
|
|
134
136
|
return `${toolName}::${cleaned.slice(0, SIGNATURE_MAX_CHARS)}`;
|
|
135
137
|
}
|
|
136
138
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
139
|
+
* Гейт автопоиска в вебе: искать стоит только реальный содержательный текст
|
|
140
|
+
* ошибки. "Command failed with exit code 1" без деталей — шум, поиск по нему
|
|
141
|
+
* лишь жжёт запрос.
|
|
140
142
|
*/
|
|
141
143
|
export function isSearchableError(output) {
|
|
142
|
-
if (!output
|
|
144
|
+
if (!output?.trim())
|
|
143
145
|
return false;
|
|
144
146
|
const text = output.trim();
|
|
145
147
|
return SEARCHABLE_PATTERNS.some((p) => p.test(text));
|
|
146
148
|
}
|
|
147
149
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* when the error text is empty. Capped at maxQueryChars.
|
|
150
|
+
* Строит поисковый запрос по падающей ошибке: до 3 чищенных error-строк (пути
|
|
151
|
+
* вырезаны) с префиксом рантайма/фреймворка из step/bash-контекста и суффиксом
|
|
152
|
+
* "how to fix". На пустом тексте — фолбэк `tool step`. Кап maxQueryChars.
|
|
152
153
|
*/
|
|
153
154
|
export function buildSearchQuery(stepDescription, lastBashCommand, lastErrorOutput, toolName, maxQueryChars) {
|
|
154
155
|
const errorText = (lastErrorOutput || "")
|
|
@@ -180,6 +181,7 @@ export function buildSearchQuery(stepDescription, lastBashCommand, lastErrorOutp
|
|
|
180
181
|
}
|
|
181
182
|
return query.slice(0, maxQueryChars);
|
|
182
183
|
}
|
|
184
|
+
/** Детектор застревания: счётчики итераций, ошибок, повторов, петель, эскалаций. */
|
|
183
185
|
export class StuckDetector {
|
|
184
186
|
threshold;
|
|
185
187
|
errorThreshold;
|
|
@@ -218,14 +220,15 @@ export class StuckDetector {
|
|
|
218
220
|
this.resetStepState(stepId);
|
|
219
221
|
}
|
|
220
222
|
}
|
|
223
|
+
/** Отмечает вызов тула (для детекции повторов; write/exec сбрасывают read-only стрик). */
|
|
221
224
|
recordToolCall(name, args) {
|
|
222
225
|
const argsKey = JSON.stringify(args);
|
|
223
226
|
this.recentToolCalls.push({ name, argsKey });
|
|
224
227
|
if (this.recentToolCalls.length > this.maxRecentCalls) {
|
|
225
228
|
this.recentToolCalls.shift();
|
|
226
229
|
}
|
|
227
|
-
//
|
|
228
|
-
// plan/todo
|
|
230
|
+
// Трекаем подряд read-only стрик. Пишущий/исполняющий тул сбрасывает;
|
|
231
|
+
// plan/todo-бухгалтерия считается read-only (не реальный прогресс).
|
|
229
232
|
if (READ_ONLY_TOOLS.has(name)) {
|
|
230
233
|
this.readOnlyStreak++;
|
|
231
234
|
}
|
|
@@ -234,13 +237,14 @@ export class StuckDetector {
|
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
239
|
/**
|
|
237
|
-
* True
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
+
* True после длинной серии read-only вызовов без записей между. Модель
|
|
241
|
+
* исследует/петляет, ничего не производя — инжектим recovery-хинт, чтобы
|
|
242
|
+
* перестала перечитывать и реально правила файлы.
|
|
240
243
|
*/
|
|
241
244
|
hasReadOnlyLoop() {
|
|
242
245
|
return this.readOnlyStreak >= READ_ONLY_LOOP_THRESHOLD;
|
|
243
246
|
}
|
|
247
|
+
/** Отмечает ошибку тула (счётчики + сигнатура для автопоиска). */
|
|
244
248
|
recordToolError(toolName, output) {
|
|
245
249
|
this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
|
|
246
250
|
this.consecutiveFailures++;
|
|
@@ -252,13 +256,14 @@ export class StuckDetector {
|
|
|
252
256
|
this.errorSignatureCounts.set(sig, (this.errorSignatureCounts.get(sig) || 0) + 1);
|
|
253
257
|
}
|
|
254
258
|
}
|
|
259
|
+
/** Последний текст ошибки. */
|
|
255
260
|
getLastErrorOutput() {
|
|
256
261
|
return this.lastErrorOutput;
|
|
257
262
|
}
|
|
258
263
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
264
|
+
* Записывает КАЖДЫЙ вызов bash (успех или нет): детекция рассуждает о
|
|
265
|
+
* повторных прогонах с пустым выводом, проблемах entry-point и ошибках
|
|
266
|
+
* платформы (PowerShell-командлеты в cmd.exe, bash-heredoc'и, …).
|
|
262
267
|
*/
|
|
263
268
|
recordBashOutput(command, output) {
|
|
264
269
|
const trimmed = output.trim();
|
|
@@ -271,13 +276,13 @@ export class StuckDetector {
|
|
|
271
276
|
this.lastBashCommand = command;
|
|
272
277
|
this.lastBashOutput = output;
|
|
273
278
|
}
|
|
274
|
-
/**
|
|
279
|
+
/** Исход одной bash-попытки (успех/фейл) — для детекции флейлинга. */
|
|
275
280
|
recordBashAttempt(success) {
|
|
276
281
|
this.bashRecent.push(success);
|
|
277
282
|
if (this.bashRecent.length > BASH_FLAIL_WINDOW)
|
|
278
283
|
this.bashRecent.shift();
|
|
279
284
|
}
|
|
280
|
-
/** True
|
|
285
|
+
/** True, если свежие bash-попытки в массе фейлятся — модель флейлит. */
|
|
281
286
|
hasBashFlailing() {
|
|
282
287
|
if (this.bashRecent.length < BASH_FLAIL_THRESHOLD)
|
|
283
288
|
return false;
|
|
@@ -287,20 +292,22 @@ export class StuckDetector {
|
|
|
287
292
|
failures++;
|
|
288
293
|
return failures >= BASH_FLAIL_THRESHOLD;
|
|
289
294
|
}
|
|
295
|
+
/** Последняя bash-команда. */
|
|
290
296
|
getLastBashCommand() {
|
|
291
297
|
return this.lastBashCommand;
|
|
292
298
|
}
|
|
299
|
+
/** Вывод последней bash-команды. */
|
|
293
300
|
getLastBashOutput() {
|
|
294
301
|
return this.lastBashOutput;
|
|
295
302
|
}
|
|
303
|
+
/** Последний упавший тул. */
|
|
296
304
|
getLastFailedTool() {
|
|
297
305
|
return this.lastFailedTool;
|
|
298
306
|
}
|
|
299
307
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* and not the per-tool error count.
|
|
308
|
+
* Первая сигнатура ошибки, чей счётчик повторов достиг порога веб-поиска и
|
|
309
|
+
* которая ЕЩЁ не искалась (или размаркирована для ретрая). Именно она
|
|
310
|
+
* триггерит автопоиск — не итерации и не per-tool счётчик ошибок.
|
|
304
311
|
*/
|
|
305
312
|
getRepeatedErrorSignature() {
|
|
306
313
|
for (const [sig, count] of this.errorSignatureCounts) {
|
|
@@ -310,63 +317,75 @@ export class StuckDetector {
|
|
|
310
317
|
}
|
|
311
318
|
return null;
|
|
312
319
|
}
|
|
320
|
+
/** Счётчик повторов сигнатуры ошибки. */
|
|
313
321
|
getErrorSignatureCount(sig) {
|
|
314
322
|
return this.errorSignatureCounts.get(sig) || 0;
|
|
315
323
|
}
|
|
324
|
+
/** Помечает сигнатуру как уже искавшуюся. */
|
|
316
325
|
markErrorSearched(sig) {
|
|
317
326
|
this.searchedErrorSignatures.add(sig);
|
|
318
327
|
}
|
|
328
|
+
/** Снимает метку поиска (разрешает один ретрай). */
|
|
319
329
|
unmarkErrorSearched(sig) {
|
|
320
330
|
this.searchedErrorSignatures.delete(sig);
|
|
321
331
|
}
|
|
332
|
+
/** True, если сигнатура уже искалась. */
|
|
322
333
|
hasErrorSearched(sig) {
|
|
323
334
|
return this.searchedErrorSignatures.has(sig);
|
|
324
335
|
}
|
|
336
|
+
/** Итераций на текущем шаге. */
|
|
325
337
|
getIterationsOnCurrentStep() {
|
|
326
338
|
return this.iterationsOnCurrentStep;
|
|
327
339
|
}
|
|
340
|
+
/** Отмечает успешный тул: сброс фейлов, затухание per-tool счётчиков и итераций. */
|
|
328
341
|
recordToolSuccess() {
|
|
329
342
|
this.consecutiveFailures = 0;
|
|
330
343
|
this.lastFailedTool = "";
|
|
331
|
-
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
// (
|
|
335
|
-
//
|
|
344
|
+
// NOTE: escalationCount осознанно НЕ сбрасываем. Один тривиальный успех
|
|
345
|
+
// (напр. один read_file) раньше его чистил, и shouldEscalate() на флейлящей
|
|
346
|
+
// сессии почти не достигался — в ущерб анти-гейминг свойству, которое
|
|
347
|
+
// resetStepState() явно хранит.
|
|
348
|
+
// Затухание per-tool счётчиков на продуктивных вызовах: рано упавший тул не
|
|
349
|
+
// держит варнинг "Tool X failed N times" вечно (ses_mt4fn58c — bash упал 3x,
|
|
350
|
+
// варнинг перелоггировался после 30 успешных записей). Свежие фейлы счёт
|
|
351
|
+
// набирают заново.
|
|
336
352
|
for (const [tool, count] of this.toolErrors) {
|
|
337
353
|
if (count <= 1)
|
|
338
354
|
this.toolErrors.delete(tool);
|
|
339
355
|
else
|
|
340
356
|
this.toolErrors.set(tool, count - 1);
|
|
341
357
|
}
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
// an analysis step).
|
|
358
|
+
// Затухание счётчика итераций на продуктивных вызовах: чтение новых файлов,
|
|
359
|
+
// записи, правки — всё прогресс. Защита от ложных stuck-варнингов при
|
|
360
|
+
// легитимной разведке файлов (напр. чтение 12 файлов в analytic-шаге).
|
|
346
361
|
if (this.iterationsOnCurrentStep > 0) {
|
|
347
362
|
this.iterationsOnCurrentStep = Math.max(0, this.iterationsOnCurrentStep - 1);
|
|
348
363
|
}
|
|
349
364
|
}
|
|
365
|
+
/** Ставит текущий шаг (сброс per-step состояния при смене). */
|
|
350
366
|
setCurrentStep(stepId, description) {
|
|
351
367
|
if (stepId !== this.currentStepId) {
|
|
352
368
|
this.resetStepState(stepId);
|
|
353
369
|
}
|
|
354
370
|
this.currentStepDescription = description;
|
|
355
371
|
}
|
|
372
|
+
/** True, если итераций на шаге больше порога. */
|
|
356
373
|
isStuck() {
|
|
357
374
|
return this.iterationsOnCurrentStep > this.threshold;
|
|
358
375
|
}
|
|
376
|
+
/** True, если тул (или любой) ошибался ≥ порога. */
|
|
359
377
|
hasRepetitiveErrors(toolName) {
|
|
360
378
|
if (toolName) {
|
|
361
379
|
return (this.toolErrors.get(toolName) || 0) >= this.errorThreshold;
|
|
362
380
|
}
|
|
363
381
|
return Array.from(this.toolErrors.values()).some((c) => c >= this.errorThreshold);
|
|
364
382
|
}
|
|
383
|
+
/** True при повторяющихся вызовах: подряд идентичных или перемежающихся (A,B,A,B,A). */
|
|
365
384
|
hasRepetitiveToolCalls() {
|
|
366
385
|
if (this.recentToolCalls.length < this.repetitionThreshold)
|
|
367
386
|
return false;
|
|
368
387
|
const last = this.recentToolCalls[this.recentToolCalls.length - 1];
|
|
369
|
-
//
|
|
388
|
+
// Проверка подряд идентичных (исходная логика)
|
|
370
389
|
let consecutiveCount = 1;
|
|
371
390
|
for (let i = this.recentToolCalls.length - 2; i >= 0; i--) {
|
|
372
391
|
if (this.recentToolCalls[i].name === last.name &&
|
|
@@ -379,8 +398,8 @@ export class StuckDetector {
|
|
|
379
398
|
}
|
|
380
399
|
if (consecutiveCount >= this.repetitionThreshold)
|
|
381
400
|
return true;
|
|
382
|
-
//
|
|
383
|
-
//
|
|
401
|
+
// Плюс перемежающиеся паттерны (A, B, A, B, A): считаем каждую связку
|
|
402
|
+
// тул+args в окне и флагуем при ≥ порога
|
|
384
403
|
const counts = new Map();
|
|
385
404
|
for (const call of this.recentToolCalls) {
|
|
386
405
|
const key = `${call.name}::${call.argsKey}`;
|
|
@@ -392,24 +411,31 @@ export class StuckDetector {
|
|
|
392
411
|
}
|
|
393
412
|
return false;
|
|
394
413
|
}
|
|
414
|
+
/** True при ≥ порога подряд фейлов. */
|
|
395
415
|
hasConsecutiveFailures() {
|
|
396
416
|
return this.consecutiveFailures >= this.errorThreshold;
|
|
397
417
|
}
|
|
418
|
+
/** Число подряд фейлов. */
|
|
398
419
|
getConsecutiveFailuresCount() {
|
|
399
420
|
return this.consecutiveFailures;
|
|
400
421
|
}
|
|
422
|
+
/** Отмечает эскалацию (счётчик для shouldEscalate). */
|
|
401
423
|
recordEscalation() {
|
|
402
424
|
this.escalationCount++;
|
|
403
425
|
}
|
|
426
|
+
/** True, если эскалаций набралось на порог. */
|
|
404
427
|
shouldEscalate() {
|
|
405
428
|
return this.escalationCount >= this.escalationThreshold;
|
|
406
429
|
}
|
|
430
|
+
/** Сбрасывает счётчик эскалаций. */
|
|
407
431
|
resetEscalation() {
|
|
408
432
|
this.escalationCount = 0;
|
|
409
433
|
}
|
|
434
|
+
/** Число эскалаций. */
|
|
410
435
|
getEscalationCount() {
|
|
411
436
|
return this.escalationCount;
|
|
412
437
|
}
|
|
438
|
+
/** Хинты по описанию шага + состояниям, ещё не покрытым recovery-сообщением. */
|
|
413
439
|
getHints() {
|
|
414
440
|
const hints = [];
|
|
415
441
|
const desc = this.currentStepDescription.toLowerCase();
|
|
@@ -425,11 +451,11 @@ export class StuckDetector {
|
|
|
425
451
|
if (desc.includes("deploy") || desc.includes("publish")) {
|
|
426
452
|
hints.push(t("exec.hint_deploy"));
|
|
427
453
|
}
|
|
428
|
-
// State-based
|
|
429
|
-
//
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
//
|
|
454
|
+
// State-based буллеты. Recovery-сообщение (getRecoveryMessage) уже репортит
|
|
455
|
+
// ПЕРВИЧНУЮ причину (приоритет: stuck > tool-errors > consecutive >
|
|
456
|
+
// repetitive > read-only). Дублировать то же состояние хинтом — задвоить
|
|
457
|
+
// гайданс в одном cooldown-блоке. Буллет добавляем, только если recovery
|
|
458
|
+
// это состояние НЕ покрывает.
|
|
433
459
|
const primary = this.getPrimaryReason();
|
|
434
460
|
if (this.hasRepetitiveToolCalls() && primary !== "repetitive") {
|
|
435
461
|
hints.push(t("exec.hint_repetitive"));
|
|
@@ -442,81 +468,83 @@ export class StuckDetector {
|
|
|
442
468
|
}
|
|
443
469
|
return hints;
|
|
444
470
|
}
|
|
471
|
+
/** Actionable-хинты по последней ошибке: рантаймы, синтаксис, сеть, шелл-несоответствия. */
|
|
445
472
|
getActionableHints() {
|
|
446
473
|
const hints = [];
|
|
447
474
|
const error = this.lastErrorOutput;
|
|
448
|
-
// Bash
|
|
449
|
-
//
|
|
475
|
+
// Bash-хинты от фейла тула не зависят — срабатывают, даже когда команда
|
|
476
|
+
// "успешна" с пустым выводом (потерянный entry point).
|
|
450
477
|
const isScriptRun = /(^|[\s&])(bun|node|tsx|ts-node|deno|python|python3)\S*\s+[^|&]+\s+[^\s]+$/.test(this.lastBashCommand);
|
|
451
478
|
if (isScriptRun && !this.lastBashOutput.trim() && this.emptyBashRunCount >= 2) {
|
|
452
479
|
hints.push(`The command "${this.lastBashCommand}" ran ${this.emptyBashRunCount} times with EMPTY output. The program likely has no entry point — read the file with read_file and check that it actually calls its main function with process.argv / CLI arguments and prints results (console.log). Then run it again.`);
|
|
453
480
|
}
|
|
454
481
|
if (!error)
|
|
455
482
|
return hints;
|
|
456
|
-
//
|
|
483
|
+
// Несовместимость рантайма — тул/модуль крашится на этой версии Node
|
|
457
484
|
if (/Cannot read properties of undefined|is not a function|is not a constructor/i.test(error)) {
|
|
458
485
|
if (/node_modules/.test(error)) {
|
|
459
486
|
hints.push("A dependency is incompatible with your Node.js version. Check if there is an alternative package or use a different runtime.");
|
|
460
487
|
}
|
|
461
488
|
}
|
|
462
|
-
//
|
|
489
|
+
// Синтаксические ошибки TypeScript — модель сгенерила невалидный код
|
|
463
490
|
if (/TS1005|TS1003|TS1109|TS1128|TS1434/i.test(error)) {
|
|
464
491
|
hints.push("TypeScript syntax error. Read the error message carefully — it tells you the exact line and column. Fix the syntax before retrying.");
|
|
465
492
|
}
|
|
466
|
-
//
|
|
493
|
+
// Ошибки типов TypeScript — модель не понимает типы
|
|
467
494
|
if (/TS2322|TS2345|TS2769|TS7006|TS7016/i.test(error)) {
|
|
468
495
|
hints.push("TypeScript type mismatch. Check the type signature of the function/API you are using. If a package lacks type declarations, install @types/<package> or use skipLibCheck.");
|
|
469
496
|
}
|
|
470
|
-
//
|
|
497
|
+
// Модуль не найден — нет зависимости или неверный импорт
|
|
471
498
|
if (/Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i.test(error)) {
|
|
472
499
|
hints.push("Module not found. Run npm install or check if the import path is correct.");
|
|
473
500
|
}
|
|
474
|
-
//
|
|
501
|
+
// Ошибки прав доступа
|
|
475
502
|
if (/EACCES|EPERM|permission denied/i.test(error)) {
|
|
476
503
|
hints.push("Permission denied. Check file permissions or run with appropriate privileges.");
|
|
477
504
|
}
|
|
478
|
-
//
|
|
505
|
+
// Сетевые ошибки
|
|
479
506
|
if (/ECONNREFUSED|ETIMEDOUT|ENOTFOUND|fetch failed/i.test(error)) {
|
|
480
507
|
hints.push("Network error. Check if the server is running and accessible.");
|
|
481
508
|
}
|
|
482
|
-
//
|
|
509
|
+
// Команда не найдена
|
|
483
510
|
if (/is not recognized|command not found|not found in path/i.test(error)) {
|
|
484
511
|
hints.push("Command not found. Check if the tool is installed and in PATH.");
|
|
485
512
|
}
|
|
486
|
-
//
|
|
513
|
+
// Петля перезаписи: один файл безуспешно переписывается
|
|
487
514
|
if (this.hasExcessiveRewrites()) {
|
|
488
515
|
const file = this.getExcessiveRewriteFile();
|
|
489
516
|
hints.push(`File ${file} has been rewritten ${this.getFileRewriteCount(file)} times without success. Stop rewriting and try a fundamentally different approach.`);
|
|
490
517
|
}
|
|
491
|
-
//
|
|
492
|
-
// entry point (
|
|
493
|
-
// PowerShell
|
|
518
|
+
// Повторные идентичные bash-прогоны с пустым выводом — вероятно, потерянный
|
|
519
|
+
// entry point (программа не вызывает main).
|
|
520
|
+
// PowerShell-командлеты внутри cmd.exe (шелл-несоответствие Windows).
|
|
494
521
|
if (/Write-Host|Get-Content|Select-String|Out-File|Set-Content/i.test(error)) {
|
|
495
522
|
hints.push("That looks like a PowerShell cmdlet — the shell here is cmd.exe. Use echo/type for output or the read_file/write_file tools instead.");
|
|
496
523
|
}
|
|
497
|
-
//
|
|
524
|
+
// Модель выдумала несуществующий Bun API.
|
|
498
525
|
if (/Bun\.\w+ is not a function|Bun\.\w+ is not defined|Bun\.\w+ is not a constructor/i.test(error)) {
|
|
499
526
|
hints.push("That Bun API does not exist. Verify the API name in the Bun docs — common ones are Bun.file, Bun.write, Bun.spawn, Bun.serve. For file checks use fs.existsSync from node:fs.");
|
|
500
527
|
}
|
|
501
|
-
//
|
|
502
|
-
//
|
|
528
|
+
// Модель продолжает писать файл, в выводе которого type/syntax ошибка —
|
|
529
|
+
// читай реальную ошибку и чини её, не переписывай вслепую.
|
|
503
530
|
if (/error TS\d+|typecheck failed|syntax check failed/i.test(error)) {
|
|
504
531
|
hints.push("The file still has a type/syntax error (see the error line in the output). Read the file with read_file around the reported line, fix the actual error, then re-run — rewriting the whole file blindly usually makes it worse.");
|
|
505
532
|
}
|
|
506
|
-
// Bash
|
|
533
|
+
// Bash-heredoc в cmd.exe.
|
|
507
534
|
if (/unexpected.*<<|Непредвиденное появление/i.test(error)) {
|
|
508
535
|
hints.push("Heredoc (<< EOF) is a bash feature — this shell is cmd.exe and does not support it. Write the file with write_file instead.");
|
|
509
536
|
}
|
|
510
537
|
return hints;
|
|
511
538
|
}
|
|
539
|
+
/** Альтернативный тул при рантайм-краше (ts-node→tsx, jest→vitest, …). Null, если нечего предложить. */
|
|
512
540
|
getToolAlternative() {
|
|
513
541
|
const tool = this.lastFailedTool;
|
|
514
542
|
const error = this.lastErrorOutput;
|
|
515
543
|
if (!tool)
|
|
516
544
|
return null;
|
|
517
|
-
//
|
|
545
|
+
// Упавший тул с рантайм-крашем — предлагаем альтернативы
|
|
518
546
|
if (/Cannot read properties of undefined|is not a function|TypeError|ReferenceError/i.test(error)) {
|
|
519
|
-
//
|
|
547
|
+
// Карта упавших тулов на частые альтернативы
|
|
520
548
|
const alternatives = {
|
|
521
549
|
"ts-node": "tsx",
|
|
522
550
|
jest: "vitest",
|
|
@@ -536,16 +564,20 @@ export class StuckDetector {
|
|
|
536
564
|
}
|
|
537
565
|
return null;
|
|
538
566
|
}
|
|
567
|
+
/** Отмечает перезапись файла (счётчик для детекции петли). */
|
|
539
568
|
recordFileRewrite(filePath) {
|
|
540
569
|
const count = (this.fileRewriteCount.get(filePath) || 0) + 1;
|
|
541
570
|
this.fileRewriteCount.set(filePath, count);
|
|
542
571
|
}
|
|
572
|
+
/** Число перезаписей файла. */
|
|
543
573
|
getFileRewriteCount(filePath) {
|
|
544
574
|
return this.fileRewriteCount.get(filePath) || 0;
|
|
545
575
|
}
|
|
576
|
+
/** True, если какой-то файл переписывается ≥ порога. */
|
|
546
577
|
hasExcessiveRewrites() {
|
|
547
578
|
return Array.from(this.fileRewriteCount.values()).some((c) => c >= this.fileRewriteThreshold);
|
|
548
579
|
}
|
|
580
|
+
/** Файл с избыточными перезаписями, или null. */
|
|
549
581
|
getExcessiveRewriteFile() {
|
|
550
582
|
for (const [file, count] of this.fileRewriteCount) {
|
|
551
583
|
if (count >= this.fileRewriteThreshold)
|
|
@@ -553,18 +585,25 @@ export class StuckDetector {
|
|
|
553
585
|
}
|
|
554
586
|
return null;
|
|
555
587
|
}
|
|
588
|
+
/** Сообщение о повторяющемся туле (+хинт про process_wait для process_log). */
|
|
556
589
|
getRepetitiveToolMessage() {
|
|
557
590
|
if (!this.hasRepetitiveToolCalls())
|
|
558
591
|
return "";
|
|
559
592
|
const last = this.recentToolCalls[this.recentToolCalls.length - 1];
|
|
560
|
-
|
|
593
|
+
const base = t("exec.repetitive_tool", {
|
|
561
594
|
tool: last.name,
|
|
562
595
|
count: this.repetitionThreshold,
|
|
563
596
|
});
|
|
597
|
+
// Опрос фонового процесса по одному process_log за раз жжёт LLM-вызов на
|
|
598
|
+
// опрос — указываем на одиночное блокирующее ожидание.
|
|
599
|
+
if (last.name === "process_log") {
|
|
600
|
+
return `${base} ${t("exec.repetitive_process_log_hint")}`;
|
|
601
|
+
}
|
|
602
|
+
return base;
|
|
564
603
|
}
|
|
565
604
|
/**
|
|
566
|
-
*
|
|
567
|
-
*
|
|
605
|
+
* Стабильная идентичность варнинга для анти-спам дедупа: тот же ключ каждую
|
|
606
|
+
* итерацию — спам, НОВЫЙ ключ (или длинная пауза) заслуживает re-warn.
|
|
568
607
|
*/
|
|
569
608
|
getWarningKey() {
|
|
570
609
|
if (this.isStuck())
|
|
@@ -582,6 +621,7 @@ export class StuckDetector {
|
|
|
582
621
|
return "read-only";
|
|
583
622
|
return "";
|
|
584
623
|
}
|
|
624
|
+
/** Причина застревания для лога (пусто если здоров). */
|
|
585
625
|
getStuckReason() {
|
|
586
626
|
if (this.isStuck()) {
|
|
587
627
|
return t("exec.stuck", {
|
|
@@ -594,6 +634,22 @@ export class StuckDetector {
|
|
|
594
634
|
if (errorTool) {
|
|
595
635
|
return t("exec.tool_errors", { tool: errorTool[0], count: errorTool[1] });
|
|
596
636
|
}
|
|
637
|
+
if (this.hasBashFlailing()) {
|
|
638
|
+
const fails = this.bashRecent.filter((ok) => !ok).length;
|
|
639
|
+
return t("exec.bash_flailing", {
|
|
640
|
+
fails: String(fails),
|
|
641
|
+
window: String(this.bashRecent.length),
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
if (this.hasConsecutiveFailures()) {
|
|
645
|
+
return t("exec.consecutive_failures", { count: String(this.consecutiveFailures) });
|
|
646
|
+
}
|
|
647
|
+
// Повторные идентичные вызовы — петля, даже если ничего не фейлится (напр.
|
|
648
|
+
// цикл опроса process_log): без этой ветки оставались невидимы —
|
|
649
|
+
// getWarningKey репортил "repetitive", но причина не логгировалась.
|
|
650
|
+
if (this.hasRepetitiveToolCalls()) {
|
|
651
|
+
return this.getRepetitiveToolMessage();
|
|
652
|
+
}
|
|
597
653
|
if (this.hasReadOnlyLoop()) {
|
|
598
654
|
return t("exec.read_only_loop", {
|
|
599
655
|
count: String(this.readOnlyStreak),
|
|
@@ -601,6 +657,7 @@ export class StuckDetector {
|
|
|
601
657
|
}
|
|
602
658
|
return "";
|
|
603
659
|
}
|
|
660
|
+
/** Recovery-сообщение по первичной причине (пусто если здоров). */
|
|
604
661
|
getRecoveryMessage() {
|
|
605
662
|
const reason = this.getPrimaryReason();
|
|
606
663
|
if (reason === "stuck") {
|
|
@@ -610,7 +667,7 @@ export class StuckDetector {
|
|
|
610
667
|
description: this.currentStepDescription,
|
|
611
668
|
});
|
|
612
669
|
}
|
|
613
|
-
// Per-tool
|
|
670
|
+
// Per-tool recovery (конкретнее — напр. "bash упал 3 раза")
|
|
614
671
|
if (reason === "tool-errors") {
|
|
615
672
|
const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
|
|
616
673
|
return t("exec.tool_errors_recovery", {
|
|
@@ -618,7 +675,7 @@ export class StuckDetector {
|
|
|
618
675
|
count: errorTool[1],
|
|
619
676
|
});
|
|
620
677
|
}
|
|
621
|
-
//
|
|
678
|
+
// Подряд фейлы разных тулов (общее — напр. "5 consecutive failures")
|
|
622
679
|
if (reason === "consecutive") {
|
|
623
680
|
return t("exec.consecutive_failures_recovery", {
|
|
624
681
|
count: this.consecutiveFailures,
|
|
@@ -642,11 +699,56 @@ export class StuckDetector {
|
|
|
642
699
|
return "";
|
|
643
700
|
}
|
|
644
701
|
/**
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
|
|
649
|
-
|
|
702
|
+
* Классифицирует текущее состояние фейла в стабильный {@link FailureType}
|
|
703
|
+
* для диагностики/логов. Null если здоров. `iteration` даёт вызывающий
|
|
704
|
+
* (детектор глобальный счётчик цикла не ведёт).
|
|
705
|
+
*/
|
|
706
|
+
classifyFailure(iteration = 0) {
|
|
707
|
+
const primary = this.getPrimaryReason();
|
|
708
|
+
if (!primary)
|
|
709
|
+
return null;
|
|
710
|
+
switch (primary) {
|
|
711
|
+
case "stuck":
|
|
712
|
+
return { type: "timeout", iteration };
|
|
713
|
+
case "tool-errors": {
|
|
714
|
+
const entry = Array.from(this.toolErrors.entries()).find(([, c]) => c >= this.errorThreshold);
|
|
715
|
+
return { type: "error_loop", tool: entry?.[0], count: entry?.[1], iteration };
|
|
716
|
+
}
|
|
717
|
+
case "consecutive":
|
|
718
|
+
return { type: "error_loop", count: this.consecutiveFailures, iteration };
|
|
719
|
+
case "repetitive": {
|
|
720
|
+
const last = this.recentToolCalls[this.recentToolCalls.length - 1];
|
|
721
|
+
return { type: "repeat", tool: last?.name, count: this.repetitionThreshold, iteration };
|
|
722
|
+
}
|
|
723
|
+
case "bash-flailing": {
|
|
724
|
+
const fails = this.bashRecent.filter((ok) => !ok).length;
|
|
725
|
+
return { type: "bash_flailing", count: fails, iteration };
|
|
726
|
+
}
|
|
727
|
+
case "read-only":
|
|
728
|
+
return { type: "read_only", count: this.readOnlyStreak, iteration };
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
/** Компактный маркер `[STUCK: type=..., tool=..., count=..., iteration=N]`. */
|
|
732
|
+
formatFailureDiagnostic(c) {
|
|
733
|
+
const parts = [`type=${c.type}`];
|
|
734
|
+
if (c.tool)
|
|
735
|
+
parts.push(`tool=${c.tool}`);
|
|
736
|
+
if (typeof c.count === "number")
|
|
737
|
+
parts.push(`count=${c.count}`);
|
|
738
|
+
if (c.iteration > 0)
|
|
739
|
+
parts.push(`iteration=${c.iteration}`);
|
|
740
|
+
return `[STUCK: ${parts.join(", ")}]`;
|
|
741
|
+
}
|
|
742
|
+
/** Диагностический маркер текущего состояния, или "" если здоров. */
|
|
743
|
+
getStuckDiagnostic(iteration = 0) {
|
|
744
|
+
const c = this.classifyFailure(iteration);
|
|
745
|
+
return c ? this.formatFailureDiagnostic(c) : "";
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Единый источник правды приоритета recovery-причины. Общий для
|
|
749
|
+
* getRecoveryMessage() и getHints(), чтобы не расходились: bullet состояния,
|
|
750
|
+
* которое getRecoveryMessage уже репортит, хинтом НЕ дублируется
|
|
751
|
+
* (приоритет: stuck > tool-errors > consecutive > repetitive > read-only).
|
|
650
752
|
*/
|
|
651
753
|
getPrimaryReason() {
|
|
652
754
|
if (this.isStuck())
|
|
@@ -664,6 +766,7 @@ export class StuckDetector {
|
|
|
664
766
|
return "read-only";
|
|
665
767
|
return null;
|
|
666
768
|
}
|
|
769
|
+
/** Полный сброс всего состояния детектора. */
|
|
667
770
|
reset() {
|
|
668
771
|
this.currentStepId = null;
|
|
669
772
|
this.iterationsOnCurrentStep = 0;
|
|
@@ -683,10 +786,10 @@ export class StuckDetector {
|
|
|
683
786
|
this.searchedErrorSignatures.clear();
|
|
684
787
|
}
|
|
685
788
|
/**
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
789
|
+
* Сбрасывает только step-прогресс, храня историю вызовов и read-only стрик.
|
|
790
|
+
* Нужно, когда план готов, но цикл продолжается (напр. audit-gate реджектнул
|
|
791
|
+
* готовую задачу): step-stuck варнинги глушатся, а петля чтения-без-записи
|
|
792
|
+
* всё равно детектится.
|
|
690
793
|
*/
|
|
691
794
|
resetStepProgress() {
|
|
692
795
|
this.currentStepId = null;
|
|
@@ -705,9 +808,9 @@ export class StuckDetector {
|
|
|
705
808
|
this.searchedErrorSignatures.clear();
|
|
706
809
|
}
|
|
707
810
|
/**
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
* stuck/recovery
|
|
811
|
+
* Сбрасывает всё per-step состояние при переходе на новый шаг. Не даёт
|
|
812
|
+
* ошибкам и повторам одного шага протекать в следующий (ложные
|
|
813
|
+
* stuck/recovery на первой итерации нового шага).
|
|
711
814
|
*/
|
|
712
815
|
resetStepState(stepId) {
|
|
713
816
|
this.currentStepId = stepId;
|
|
@@ -725,7 +828,7 @@ export class StuckDetector {
|
|
|
725
828
|
this.readOnlyStreak = 0;
|
|
726
829
|
this.errorSignatureCounts.clear();
|
|
727
830
|
this.searchedErrorSignatures.clear();
|
|
728
|
-
//
|
|
729
|
-
//
|
|
831
|
+
// escalationCount храним — иначе агент "геймит" детекцию, маркируя шаг
|
|
832
|
+
// done и переходя к следующему
|
|
730
833
|
}
|
|
731
834
|
}
|