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
|
@@ -1,36 +1,48 @@
|
|
|
1
|
+
// Трекер плана: указатель текущего шага, статусы, прогресс-строка.
|
|
2
|
+
// Двигает указатель мимо уже settled (skipped/done) шагов, не мутируя их —
|
|
3
|
+
// перезапись "skipped" в "in_progress" дедлочит план (модель не возьмётся за
|
|
4
|
+
// шаг, который осознанно пропустила).
|
|
1
5
|
import { PlanCreator, getPlanProgress } from "./planner";
|
|
6
|
+
/** Трекер плана: указатель, статусы шагов, прогресс. */
|
|
2
7
|
export class PlanTracker {
|
|
3
8
|
plan;
|
|
4
9
|
currentStepIndex = 0;
|
|
5
10
|
constructor(plan) {
|
|
6
11
|
this.plan = plan;
|
|
7
12
|
}
|
|
13
|
+
/** План целиком. */
|
|
8
14
|
getPlan() {
|
|
9
15
|
return this.plan;
|
|
10
16
|
}
|
|
17
|
+
/** Индекс текущего шага. */
|
|
11
18
|
getCurrentStepIndex() {
|
|
12
19
|
return this.currentStepIndex;
|
|
13
20
|
}
|
|
21
|
+
/** Текущий шаг (по указателю). */
|
|
14
22
|
getCurrentStep() {
|
|
15
23
|
return this.plan.steps[this.currentStepIndex];
|
|
16
24
|
}
|
|
25
|
+
/** Шаг по id. */
|
|
17
26
|
getStep(id) {
|
|
18
27
|
return this.plan.steps.find((s) => s.id === id);
|
|
19
28
|
}
|
|
29
|
+
/** Ставит статус шагу. */
|
|
20
30
|
updateStepStatus(id, status) {
|
|
21
31
|
const step = this.getStep(id);
|
|
22
32
|
if (step)
|
|
23
33
|
step.status = status;
|
|
24
34
|
}
|
|
35
|
+
/** Добавляет заметку к шагу. */
|
|
25
36
|
addNote(id, note) {
|
|
26
37
|
const step = this.getStep(id);
|
|
27
38
|
if (step)
|
|
28
39
|
step.note = note;
|
|
29
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Двигает указатель к следующему незакрытому шагу (мимо done/skipped без
|
|
43
|
+
* мутаций). False, если шагов не осталось.
|
|
44
|
+
*/
|
|
30
45
|
advance() {
|
|
31
|
-
// Move past any already-settled (skipped/done) steps without mutating
|
|
32
|
-
// them — overwriting a "skipped" step with "in_progress" deadlocks the
|
|
33
|
-
// plan (the model will never work on a step it deliberately skipped).
|
|
34
46
|
let idx = this.currentStepIndex + 1;
|
|
35
47
|
while (idx < this.plan.steps.length &&
|
|
36
48
|
(this.plan.steps[idx].status === "skipped" || this.plan.steps[idx].status === "done")) {
|
|
@@ -45,11 +57,10 @@ export class PlanTracker {
|
|
|
45
57
|
return true;
|
|
46
58
|
}
|
|
47
59
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* accumulates false-positive "no progress" warnings.
|
|
60
|
+
* Подтягивает указатель мимо done/skipped шагов без смены статусов.
|
|
61
|
+
* Синхронизирует currentStepIndex, когда модель маркит шаги через тул
|
|
62
|
+
* `plan update` (он advance() не зовёт). Без этого getCurrentStep() вечно
|
|
63
|
+
* стоит на первом шаге и stuck-детектор копит ложные "no progress".
|
|
53
64
|
*/
|
|
54
65
|
syncCurrentStep() {
|
|
55
66
|
while (this.currentStepIndex < this.plan.steps.length - 1 &&
|
|
@@ -58,9 +69,11 @@ export class PlanTracker {
|
|
|
58
69
|
this.currentStepIndex++;
|
|
59
70
|
}
|
|
60
71
|
}
|
|
72
|
+
/** True, если все шаги done/skipped. */
|
|
61
73
|
isComplete() {
|
|
62
74
|
return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
|
|
63
75
|
}
|
|
76
|
+
/** Строка прогресса: `[id] title settled/total (skipped) bar pct`. */
|
|
64
77
|
getProgressString() {
|
|
65
78
|
const p = getPlanProgress(this.plan);
|
|
66
79
|
const total = p.total;
|
|
@@ -72,6 +85,7 @@ export class PlanTracker {
|
|
|
72
85
|
const skippedPart = p.skipped > 0 ? ` (${p.skipped} skipped)` : "";
|
|
73
86
|
return `[${this.plan.id}] ${this.plan.title} ${settled}/${total}${skippedPart} ${bar} ${pct}%`;
|
|
74
87
|
}
|
|
88
|
+
/** Промпт-блок плана с текущей позиции указателя. */
|
|
75
89
|
toPromptBlock() {
|
|
76
90
|
return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
|
|
77
91
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Верификатор шагов: файлы, синтаксис, скрипты, typecheck, success_criteria.
|
|
1
2
|
import { existsSync, readFileSync } from "fs";
|
|
2
3
|
import { resolve, extname, dirname, join } from "path";
|
|
3
4
|
import { spawn } from "child_process";
|
|
@@ -6,19 +7,27 @@ import { validateExpertConfig } from "../../config/experts";
|
|
|
6
7
|
import { findProjectRoot, resolveTscCommand } from "../lsp/project-root";
|
|
7
8
|
import { extractFileLikeTokens, stripUrls } from "../hallucination/js-identifiers";
|
|
8
9
|
import { findExistingFile } from "./auditor";
|
|
10
|
+
import { findTypecheckRoot } from "./audit-runners";
|
|
11
|
+
import { killTree } from "../processes/kill-tree";
|
|
12
|
+
import { settleChildClose } from "../../utils/child-run";
|
|
13
|
+
/** Верификатор шагов и MoE-планов: файлы, синтаксис, скрипты, tsc, критерии. */
|
|
9
14
|
export class StepVerifier {
|
|
10
15
|
baseDir;
|
|
11
16
|
constructor(baseDir) {
|
|
12
17
|
this.baseDir = baseDir;
|
|
13
18
|
}
|
|
19
|
+
/** Проверяет существование файла (с nested-резолвером findExistingFile). */
|
|
14
20
|
async checkFileExists(path) {
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
// Nested-aware (тот же резолвер, что в verifyStep): проект в
|
|
22
|
+
// `baseDir/proj/` нельзя репортить пропавшим, если файл ниже.
|
|
23
|
+
const resolved = findExistingFile(this.baseDir, path);
|
|
24
|
+
const exists = !!resolved;
|
|
17
25
|
return {
|
|
18
26
|
passed: exists,
|
|
19
27
|
message: exists ? t("verify.file_exists", { path }) : t("verify.file_not_found", { path }),
|
|
20
28
|
};
|
|
21
29
|
}
|
|
30
|
+
/** Запускает `bun run <script>` (60s), возвращает пункт проверки. */
|
|
22
31
|
async runScript(scriptName) {
|
|
23
32
|
try {
|
|
24
33
|
await this.runAsync(`bun run ${scriptName}`, this.baseDir, 60_000);
|
|
@@ -31,75 +40,88 @@ export class StepVerifier {
|
|
|
31
40
|
};
|
|
32
41
|
}
|
|
33
42
|
}
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
/** Проектный typecheck (tsc --noEmit); без tsconfig/tsc — пропуск, не фейл. */
|
|
44
|
+
async runTypeCheck(existingFiles = []) {
|
|
45
|
+
// Корень проекта резолвим и по артефактам: проект во вложенном
|
|
46
|
+
// `baseDir/proj/` надо типчекать, а не скипать ("No tsconfig.json found") —
|
|
47
|
+
// старый поиск только от baseDir его не видел.
|
|
48
|
+
const root = findTypecheckRoot(this.baseDir, existingFiles) ?? this.baseDir;
|
|
49
|
+
const tsconfigPath = join(root, "tsconfig.json");
|
|
36
50
|
if (!existsSync(tsconfigPath)) {
|
|
37
|
-
return { passed: true, message: "
|
|
51
|
+
return { passed: true, message: t("verify.typecheck_skipped_no_tsconfig") };
|
|
38
52
|
}
|
|
39
|
-
const tsc = resolveTscCommand(
|
|
53
|
+
const tsc = resolveTscCommand(root);
|
|
40
54
|
if (!tsc)
|
|
41
|
-
return { passed: true, message: "
|
|
55
|
+
return { passed: true, message: t("verify.typecheck_skipped_no_tsc") };
|
|
42
56
|
try {
|
|
43
|
-
await this.runAsync(`${tsc} --noEmit`,
|
|
44
|
-
return { passed: true, message: "
|
|
57
|
+
await this.runAsync(`${tsc} --noEmit`, root, 60_000);
|
|
58
|
+
return { passed: true, message: t("verify.typecheck_passed") };
|
|
45
59
|
}
|
|
46
60
|
catch (e) {
|
|
47
61
|
const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
|
|
48
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
passed: false,
|
|
64
|
+
message: t("verify.typecheck_failed", { error: stderr.slice(0, 500) }),
|
|
65
|
+
};
|
|
49
66
|
}
|
|
50
67
|
}
|
|
51
68
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
69
|
+
* Типчекает конкретный файл, резолвя корень проекта от РАСПОЛОЖЕНИЯ ФАЙЛА
|
|
70
|
+
* (не от baseDir агента). Для вложенных раскладок (`baseDir/proj/` со своим
|
|
71
|
+
* tsconfig.json) старая проверка от baseDir молча скипала каждую ошибку типов
|
|
72
|
+
* (было в ses_mst2r0r5: ноль type feedback за всю сессию). Маркеры зеркалят
|
|
73
|
+
* конфиг TypeScript LSP-сервера.
|
|
57
74
|
*/
|
|
58
75
|
async runTypeCheckForFile(filePath) {
|
|
59
76
|
const projectRoot = findProjectRoot(filePath, this.baseDir, ["tsconfig.json", "package.json"]);
|
|
60
77
|
const tsconfigPath = join(projectRoot, "tsconfig.json");
|
|
61
78
|
if (!existsSync(tsconfigPath)) {
|
|
62
|
-
return { passed: true, message: "
|
|
79
|
+
return { passed: true, message: t("verify.typecheck_skipped_no_tsconfig") };
|
|
63
80
|
}
|
|
64
81
|
const tsc = resolveTscCommand(projectRoot);
|
|
65
82
|
if (!tsc)
|
|
66
|
-
return { passed: true, message: "
|
|
83
|
+
return { passed: true, message: t("verify.typecheck_skipped_no_tsc") };
|
|
67
84
|
try {
|
|
68
85
|
await this.runAsync(`${tsc} --noEmit --skipLibCheck`, projectRoot, 60_000);
|
|
69
|
-
return { passed: true, message: "
|
|
86
|
+
return { passed: true, message: t("verify.typecheck_passed") };
|
|
70
87
|
}
|
|
71
88
|
catch (e) {
|
|
72
89
|
const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
|
|
73
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
passed: false,
|
|
92
|
+
message: t("verify.typecheck_failed", { error: stderr.slice(0, 500) }),
|
|
93
|
+
};
|
|
74
94
|
}
|
|
75
95
|
}
|
|
96
|
+
/** Запускает `test`-скрипт проекта; без package.json/скрипта — пропуск. */
|
|
76
97
|
async runTests() {
|
|
77
98
|
const pkgPath = resolve(this.baseDir, "package.json");
|
|
78
99
|
if (!existsSync(pkgPath)) {
|
|
79
|
-
return { passed: true, message: "
|
|
100
|
+
return { passed: true, message: t("verify.tests_skipped_no_package") };
|
|
80
101
|
}
|
|
81
102
|
try {
|
|
82
103
|
const pkg = JSON.parse(require("fs").readFileSync(pkgPath, "utf-8"));
|
|
83
104
|
if (!pkg.scripts?.test) {
|
|
84
|
-
return { passed: true, message: "
|
|
105
|
+
return { passed: true, message: t("verify.tests_skipped_no_script") };
|
|
85
106
|
}
|
|
86
107
|
}
|
|
87
108
|
catch {
|
|
88
|
-
/* fall through —
|
|
109
|
+
/* fall through — пробуем запустить */
|
|
89
110
|
}
|
|
90
111
|
return this.runScript("test");
|
|
91
112
|
}
|
|
113
|
+
/** Проверяет существование списка файлов-артефактов (параллельно). */
|
|
92
114
|
async verifyArtifactFiles(files) {
|
|
93
115
|
return Promise.all(files.map((f) => this.checkFileExists(f)));
|
|
94
116
|
}
|
|
95
117
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
118
|
+
* Проверяет machine-readable success_criteria подзадачи ДО решения о merge
|
|
119
|
+
* (Plan 5.1). Поддержаны формы:
|
|
98
120
|
* - `file-exists:<path>`
|
|
99
121
|
* - `substring-in-file:<path>:<text>`
|
|
100
122
|
* - `command-exit-0:<command>`
|
|
101
|
-
*
|
|
102
|
-
*
|
|
123
|
+
* Остальное — LLM-only критерий: варнинг (не ошибка), чтобы Router всё равно
|
|
124
|
+
* видел его в контексте.
|
|
103
125
|
*/
|
|
104
126
|
async verifySubtaskCriteria(subtask) {
|
|
105
127
|
const errors = [];
|
|
@@ -161,6 +183,7 @@ export class StepVerifier {
|
|
|
161
183
|
}
|
|
162
184
|
return { errors, warnings, details };
|
|
163
185
|
}
|
|
186
|
+
/** Верифицирует MoE-манифест: эксперты, typecheck, файлы и критерии подзадач. */
|
|
164
187
|
async verifyMoEManifest(plan, config, allToolTags) {
|
|
165
188
|
const errors = [];
|
|
166
189
|
const warnings = [];
|
|
@@ -170,7 +193,11 @@ export class StepVerifier {
|
|
|
170
193
|
errors.push(err);
|
|
171
194
|
details.push({ passed: false, message: err });
|
|
172
195
|
}
|
|
173
|
-
const
|
|
196
|
+
const existingArtifacts = plan.subtasks
|
|
197
|
+
.flatMap((s) => s.allowed_files || [])
|
|
198
|
+
.map((f) => findExistingFile(this.baseDir, f))
|
|
199
|
+
.filter((f) => !!f);
|
|
200
|
+
const tscResult = await this.runTypeCheck(existingArtifacts);
|
|
174
201
|
details.push(tscResult);
|
|
175
202
|
if (!tscResult.passed) {
|
|
176
203
|
errors.push(tscResult.message);
|
|
@@ -183,7 +210,7 @@ export class StepVerifier {
|
|
|
183
210
|
errors.push(check.message);
|
|
184
211
|
}
|
|
185
212
|
}
|
|
186
|
-
// Machine-readable success_criteria (Plan 5.1) —
|
|
213
|
+
// Machine-readable success_criteria (Plan 5.1) — проверяются до merge.
|
|
187
214
|
const criteria = await this.verifySubtaskCriteria(sub);
|
|
188
215
|
errors.push(...criteria.errors);
|
|
189
216
|
warnings.push(...criteria.warnings);
|
|
@@ -196,11 +223,12 @@ export class StepVerifier {
|
|
|
196
223
|
details,
|
|
197
224
|
};
|
|
198
225
|
}
|
|
226
|
+
/** Верифицирует шаг: файлы из описания существуют (+синтаксис; delete — наоборот). */
|
|
199
227
|
async verifyStep(stepDescription, kind) {
|
|
200
228
|
const fileMatches = extractFileLikeTokens(stripUrls(stepDescription));
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
229
|
+
// Нет именованных файлов — шаг непроверяем по существованию. НЕ пропускаем
|
|
230
|
+
// тривиально (пустой every() = true); сигналим noFiles, чтобы тул вёл
|
|
231
|
+
// модель к настоящей проверке.
|
|
204
232
|
if (fileMatches.length === 0) {
|
|
205
233
|
return { passed: false, syntaxValid: true, failed: [], noFiles: true };
|
|
206
234
|
}
|
|
@@ -210,17 +238,17 @@ export class StepVerifier {
|
|
|
210
238
|
const resolved = findExistingFile(this.baseDir, filePath);
|
|
211
239
|
const exists = !!resolved;
|
|
212
240
|
if (kind === "delete") {
|
|
213
|
-
//
|
|
241
|
+
// Для delete-шагов: отсутствие файла — pass
|
|
214
242
|
results.push({
|
|
215
243
|
passed: !exists,
|
|
216
244
|
message: exists
|
|
217
245
|
? t("verify.file_still_exists", { path: filePath })
|
|
218
246
|
: t("verify.file_not_found", { path: filePath }),
|
|
219
247
|
});
|
|
220
|
-
//
|
|
248
|
+
// Удалённый файл синтаксически не проверяем
|
|
221
249
|
continue;
|
|
222
250
|
}
|
|
223
|
-
// Create
|
|
251
|
+
// Create-шаги: файл обязан существовать + валидный синтаксис
|
|
224
252
|
if (!exists) {
|
|
225
253
|
results.push({ passed: false, message: t("verify.file_not_found", { path: filePath }) });
|
|
226
254
|
continue;
|
|
@@ -248,7 +276,7 @@ export class StepVerifier {
|
|
|
248
276
|
if (!tsc)
|
|
249
277
|
return true;
|
|
250
278
|
try {
|
|
251
|
-
//
|
|
279
|
+
// Путь в кавычках — команды идут через shell и ломаются на пробелах.
|
|
252
280
|
await this.runAsync(`${tsc} --noEmit --skipLibCheck "${filePath}"`, this.baseDir, 20000);
|
|
253
281
|
return true;
|
|
254
282
|
}
|
|
@@ -277,8 +305,8 @@ export class StepVerifier {
|
|
|
277
305
|
return true;
|
|
278
306
|
}
|
|
279
307
|
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
308
|
+
* Запускает команду асинхронно (не блокируя event loop) и резолвится на
|
|
309
|
+
* выходе. На ненулевой exit или таймаут — reject с захваченными stderr/stdout.
|
|
282
310
|
*/
|
|
283
311
|
runAsync(command, cwd, timeoutMs) {
|
|
284
312
|
return new Promise((resolve, reject) => {
|
|
@@ -297,7 +325,9 @@ export class StepVerifier {
|
|
|
297
325
|
stderr += d.toString();
|
|
298
326
|
});
|
|
299
327
|
const timer = setTimeout(() => {
|
|
300
|
-
child.kill()
|
|
328
|
+
// Убиваем всё дерево: при shell:true child.kill() останавливает только
|
|
329
|
+
// shell, а реальный внук tsc/node продолжает висеть.
|
|
330
|
+
killTree(child);
|
|
301
331
|
reject(new Error(`Command timed out after ${timeoutMs}ms`));
|
|
302
332
|
}, timeoutMs);
|
|
303
333
|
child.on("error", (err) => {
|
|
@@ -306,16 +336,7 @@ export class StepVerifier {
|
|
|
306
336
|
});
|
|
307
337
|
child.on("close", (code) => {
|
|
308
338
|
clearTimeout(timer);
|
|
309
|
-
|
|
310
|
-
resolve({ stdout, stderr });
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
const err = new Error(`Command failed with exit code ${code}`);
|
|
314
|
-
err.stdout = stdout;
|
|
315
|
-
err.stderr = stderr;
|
|
316
|
-
err.status = code;
|
|
317
|
-
reject(err);
|
|
318
|
-
}
|
|
339
|
+
settleChildClose(code, stdout, stderr, `Command failed with exit code ${code}`, resolve, reject);
|
|
319
340
|
});
|
|
320
341
|
});
|
|
321
342
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unix
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Unix-команды, которых нет в Windows cmd.exe (или семантика несовместима).
|
|
3
|
+
* 9B-модель тянется к grep/sed/ls/find через тул bash; каждый фейл возвращает
|
|
4
|
+
* хинт, но модель ретраит.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* Сессионный модульный компаньон пер-командного hard-stop самого тула bash
|
|
7
|
+
* (`UNIX_TO_WIN_HINTS` / `FAILING_FIRST_WORDS` / `HARD_BLOCK_THRESHOLD` в
|
|
8
|
+
* src/tools/bash.ts). Тот механизм инжектит "STOP using X" В вывод тула,
|
|
9
|
+
* который 9B-модель склонна игнорировать; этот, гейченный на win32 как bash.ts,
|
|
10
|
+
* кладёт <system-summary> перед следующим вызовом модели. Списки команд держим
|
|
11
|
+
* синхронно.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Это allow/deny-список команд (как чёрный список команд security-модуля),
|
|
14
|
+
* НЕ классификация задач (правило #10).
|
|
15
15
|
*/
|
|
16
16
|
const FORBIDDEN_COMMANDS = new Set([
|
|
17
17
|
"grep",
|
|
@@ -40,7 +40,7 @@ function firstWord(command) {
|
|
|
40
40
|
const segs = command.trim().split(/[\s;|&]+/);
|
|
41
41
|
return (segs[0] || "").replace(/[^\w-]/g, "").toLowerCase();
|
|
42
42
|
}
|
|
43
|
-
/**
|
|
43
|
+
/** Имя запрещённой команды, если `command` с неё начинается (понимает `cd x && …`). */
|
|
44
44
|
export function forbiddenWindowsCommand(command) {
|
|
45
45
|
const m = command.match(/^\s*cd\s+\S+\s*&&\s*(\S+)/);
|
|
46
46
|
if (m)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Проверки заявлений об артефактах (ses_mul5pepu_9mdq90): qwen3.5-9b записала fizzbuzz.py,
|
|
3
|
+
* не смогла запустить (python в чёрном списке), затем вставила "ожидаемый" вывод
|
|
4
|
+
* программы с неверными значениями как якобы реальный; также записала schema.drawio
|
|
5
|
+
* с битым `<diagram/>` + висячим `</diagram>` и заявила "валидный XML".
|
|
6
|
+
* Проверка success-claim срабатывает только на сигналы ошибок в выводах тулов,
|
|
7
|
+
* поэтому оба случая прошли молча.
|
|
8
|
+
*
|
|
9
|
+
* Две детерминированные проверки (regex + состояние диска от вызывающего, без LLM):
|
|
10
|
+
* - `checkArtifactClaims`: ответ подаёт файл как созданный/валидный, а на диске
|
|
11
|
+
* его нет или он не парсится.
|
|
12
|
+
* - `checkUnverifiedOutput`: ответ подаёт кодовый блок как реальный вывод
|
|
13
|
+
* программы, но ни один тул в окне доказательств ничего успешно не исполнял.
|
|
14
|
+
*
|
|
15
|
+
* Чистые функции — состояние диска (`ArtifactState[]`) резолвит вызывающий
|
|
16
|
+
* (цикл агента, владелец baseDir), модуль остаётся тестируемым.
|
|
17
|
+
* Точность смещена в сторону recall, как в success-claim.ts: ложное срабатывание
|
|
18
|
+
* стоит одну ограниченную re-check итерацию, пропуск — статус-кво.
|
|
19
|
+
*/
|
|
20
|
+
const WORD = "A-Za-zА-Яа-яЁё0-9_";
|
|
21
|
+
const wb = (body) => new RegExp(`(?<![${WORD}])(?:${body})(?![${WORD}])`, "i");
|
|
22
|
+
/** Язык успеха про файл (любая локаль, на которой говорит агент). */
|
|
23
|
+
const ARTIFACT_SUCCESS_RE = wb("создан[а-яё]*|записан[а-яё]*|сохран[её]н[а-яё]*|готов[а-яё]*|валид[а-яё]*|проверен[а-яё]*|существует|valid(?:ated)?|verified|checked|written|saved|created|exists?|ready");
|
|
24
|
+
/**
|
|
25
|
+
* Каким словом успеха (если есть) ответ описывает упоминание файла.
|
|
26
|
+
* Возвращает найденное слово для точного retry-сообщения.
|
|
27
|
+
*/
|
|
28
|
+
function claimedAs(response, path) {
|
|
29
|
+
const idx = response.toLowerCase().indexOf(path.toLowerCase());
|
|
30
|
+
if (idx === -1)
|
|
31
|
+
return null;
|
|
32
|
+
// Смотрим окно вокруг упоминания — заявление обычно в том же
|
|
33
|
+
// предложении ("Файл schema.drawio создан и валиден").
|
|
34
|
+
const window = response.slice(Math.max(0, idx - 160), idx + path.length + 160);
|
|
35
|
+
const m = window.match(ARTIFACT_SUCCESS_RE);
|
|
36
|
+
return m ? m[0] : null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Возвращает противоречие, если ответ подаёт резолвленный артефакт как
|
|
40
|
+
* созданный/валидный, а на диске его нет или он не парсится.
|
|
41
|
+
*/
|
|
42
|
+
export function checkArtifactClaims(response, artifacts) {
|
|
43
|
+
if (!response)
|
|
44
|
+
return null;
|
|
45
|
+
for (const a of artifacts) {
|
|
46
|
+
const claim = a.claim ?? claimedAs(response, a.path);
|
|
47
|
+
if (!claim)
|
|
48
|
+
continue;
|
|
49
|
+
if (!a.exists) {
|
|
50
|
+
return {
|
|
51
|
+
tool: "disk",
|
|
52
|
+
path: a.path,
|
|
53
|
+
snippet: `${a.path}: file not found on disk, claimed "${claim}"`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (a.parseError) {
|
|
57
|
+
return {
|
|
58
|
+
tool: "disk",
|
|
59
|
+
path: a.path,
|
|
60
|
+
snippet: `${a.path}: ${a.parseError}, claimed "${claim}"`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/** Фразы, подающие кодовый блок как РЕАЛЬНЫЙ вывод исполнения (не пример). */
|
|
67
|
+
const OUTPUT_AS_RESULT_RE = wb("вывод программы|вывод команды|результат запуска|результат выполнения|program output|command output|execution result|запустил[а-яё]*.*вывод|покажу вывод|actual output");
|
|
68
|
+
/** Хеджирование, понижающее блок до ожидания/примера — честно, пропускаем. */
|
|
69
|
+
const OUTPUT_HEDGE_RE = wb("ожидаем[а-яё]*|expected|пример[а-яё]*|example|для справки|sample|например|предполагаем[а-яё]*|должен быть|should be");
|
|
70
|
+
const FENCE_RE = /```/;
|
|
71
|
+
/** Тулы, чей успех означает "что-то реально исполнилось". */
|
|
72
|
+
const EXEC_TOOLS = new Set(["bash", "process_wait", "pipeline_run"]);
|
|
73
|
+
/**
|
|
74
|
+
* Возвращает противоречие, если ответ показывает вывод как результат запуска,
|
|
75
|
+
* но ни один exec-тул в окне доказательств успешно не отработал.
|
|
76
|
+
*/
|
|
77
|
+
export function checkUnverifiedOutput(response, tools) {
|
|
78
|
+
if (!response || !OUTPUT_AS_RESULT_RE.test(response))
|
|
79
|
+
return null;
|
|
80
|
+
if (OUTPUT_HEDGE_RE.test(response))
|
|
81
|
+
return null;
|
|
82
|
+
if (!FENCE_RE.test(response))
|
|
83
|
+
return null;
|
|
84
|
+
const recent = tools.filter((t) => t.text?.trim()).slice(-10);
|
|
85
|
+
const executed = recent.some((t) => t.ok === true && t.name !== undefined && EXEC_TOOLS.has(t.name));
|
|
86
|
+
if (executed)
|
|
87
|
+
return null;
|
|
88
|
+
const m = response.match(OUTPUT_AS_RESULT_RE);
|
|
89
|
+
return {
|
|
90
|
+
tool: "no-execution",
|
|
91
|
+
snippet: `claimed "${m?.[0] ?? "output"}" with no successful execution tool in evidence`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { t } from "../../i18n/index";
|
|
2
2
|
const MIN_CHARS = 1;
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
* degeneration and must not burn a hallucination retry. */
|
|
3
|
+
/** Оба ответа должны быть не короче — короткий честный ответ ("Готово.") дважды
|
|
4
|
+
* подряд не деградация и не должен жечь hallucination-retry. */
|
|
6
5
|
const MIN_REPEAT_CHARS = 120;
|
|
7
6
|
const MIN_REPEAT_WORDS = 20;
|
|
8
|
-
/**
|
|
7
|
+
/** Пересечение слов выше порога считается повтором (было 0.5). */
|
|
9
8
|
const REPEAT_OVERLAP_THRESHOLD = 0.8;
|
|
9
|
+
/** Проверка уверенности: пустой ответ, дословный повтор, низкая diversity. */
|
|
10
10
|
export class ConfidenceCheck {
|
|
11
11
|
previousResponse = "";
|
|
12
|
+
/** Запоминает предыдущий ответ для детекции повторов. */
|
|
12
13
|
setPreviousResponse(response) {
|
|
13
14
|
this.previousResponse = response;
|
|
14
15
|
}
|
|
16
|
+
/** Валидирует ответ: retry на пустоту/повтор, warn на низкую diversity, иначе pass. */
|
|
15
17
|
validate(response) {
|
|
16
18
|
if (!response || response.length < MIN_CHARS) {
|
|
17
19
|
return {
|
|
@@ -20,13 +22,13 @@ export class ConfidenceCheck {
|
|
|
20
22
|
reason: t("hall.short_response"),
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
25
|
+
// Короткие реплики не флагуются: приветствие, скупое подтверждение или
|
|
26
|
+
// однострочник — валидный ответ. Реальное усечение ловит finish_reason
|
|
27
|
+
// провайдера ("length"), пустоту — retry выше.
|
|
26
28
|
const wordCount = response.split(/\s+/).filter(Boolean).length;
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
29
|
+
// Языконезависимо: детекция повторов по пересечению слов. Флагуем только
|
|
30
|
+
// если ОБА ответа содержательные — короткий ответ после длинного (или два
|
|
31
|
+
// коротких) не цикл.
|
|
30
32
|
if (this.previousResponse) {
|
|
31
33
|
const prevWords = this.previousResponse.split(/\s+/).filter(Boolean).length;
|
|
32
34
|
const bothSubstantive = response.length >= MIN_REPEAT_CHARS &&
|
|
@@ -44,7 +46,7 @@ export class ConfidenceCheck {
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
|
-
//
|
|
49
|
+
// Языконезависимо: очень низкая diversity слов (одни и те же слова по кругу)
|
|
48
50
|
const words = response
|
|
49
51
|
.toLowerCase()
|
|
50
52
|
.split(/\s+/)
|
|
@@ -1,28 +1,36 @@
|
|
|
1
|
+
/** Проверка консистентности: решения, созданные/удалённые файлы сессии. */
|
|
1
2
|
export class ConsistencyCheck {
|
|
2
3
|
decisions = [];
|
|
3
4
|
createdFiles = new Set();
|
|
4
5
|
deletedFiles = new Set();
|
|
6
|
+
/** Запоминает решение агента. */
|
|
5
7
|
trackDecision(decision, location) {
|
|
6
8
|
this.decisions.push({ decision, location });
|
|
7
9
|
}
|
|
10
|
+
/** Отмечает созданный файл (снимает с него флаг удалённого). */
|
|
8
11
|
trackCreatedFile(path) {
|
|
9
12
|
this.createdFiles.add(path);
|
|
10
|
-
//
|
|
11
|
-
//
|
|
13
|
+
// Удаление с последующим пересозданием снимает флаг — иначе wasFileDeleted()
|
|
14
|
+
// считает файл пропавшим и после delete+create.
|
|
12
15
|
this.deletedFiles.delete(path);
|
|
13
16
|
}
|
|
17
|
+
/** Отмечает удалённый файл. */
|
|
14
18
|
trackDeletedFile(path) {
|
|
15
19
|
this.deletedFiles.add(path);
|
|
16
20
|
}
|
|
21
|
+
/** Все созданные файлы. */
|
|
17
22
|
getCreatedFiles() {
|
|
18
23
|
return Array.from(this.createdFiles);
|
|
19
24
|
}
|
|
25
|
+
/** Все удалённые файлы. */
|
|
20
26
|
getDeletedFiles() {
|
|
21
27
|
return Array.from(this.deletedFiles);
|
|
22
28
|
}
|
|
29
|
+
/** Все отслеживаемые решения. */
|
|
23
30
|
getDecisions() {
|
|
24
31
|
return this.decisions;
|
|
25
32
|
}
|
|
33
|
+
/** True, если есть хоть одно решение. */
|
|
26
34
|
hasDecisions() {
|
|
27
35
|
return this.decisions.length > 0;
|
|
28
36
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
// Оркестратор проверок галлюцинаций: confidence → factual → LLM-судья.
|
|
1
2
|
import { ConsistencyCheck } from "./consistency";
|
|
2
3
|
import { ConfidenceCheck } from "./confidence";
|
|
3
4
|
import { FactualCheck } from "./factual";
|
|
4
5
|
import { LLMJudge } from "./llm-judge";
|
|
6
|
+
/** Детектор галлюцинаций: объединяет проверки уверенности, фактов и LLM-судью. */
|
|
5
7
|
export class HallucinationDetector {
|
|
6
8
|
consistency;
|
|
7
9
|
confidence;
|
|
@@ -11,22 +13,25 @@ export class HallucinationDetector {
|
|
|
11
13
|
this.consistency = new ConsistencyCheck();
|
|
12
14
|
this.confidence = new ConfidenceCheck();
|
|
13
15
|
this.factual = baseDir ? new FactualCheck(baseDir) : null;
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
16
|
+
// Судья opt-out: работает только если передан провайдер И вызывающий явно
|
|
17
|
+
// его не отключил (главный агент отключает по умолчанию через
|
|
18
|
+
// config.hallucination.judge.enabled).
|
|
17
19
|
const judgeEnabled = options?.judgeEnabled ?? true;
|
|
18
20
|
this.judge = llmProvider && judgeEnabled ? new LLMJudge(llmProvider) : null;
|
|
19
21
|
}
|
|
22
|
+
/** Доступ к проверке консистентности (решения, созданные/удалённые файлы). */
|
|
20
23
|
getConsistencyCheck() {
|
|
21
24
|
return this.consistency;
|
|
22
25
|
}
|
|
26
|
+
/** Доступ к проверке уверенности (повторы, diversity). */
|
|
23
27
|
getConfidenceCheck() {
|
|
24
28
|
return this.confidence;
|
|
25
29
|
}
|
|
26
|
-
/**
|
|
30
|
+
/** Добавляет файлы, известные из сводок компакции, в фактическую проверку. */
|
|
27
31
|
addKnownFiles(files) {
|
|
28
32
|
this.factual?.addKnownFiles(files);
|
|
29
33
|
}
|
|
34
|
+
/** Валидирует ответ: retry/block уверенности → факты → LLM-судья + warn-агрегация. */
|
|
30
35
|
async validate(response) {
|
|
31
36
|
const confidenceResult = this.confidence.validate(response);
|
|
32
37
|
if (confidenceResult.status === "retry" || confidenceResult.status === "block") {
|
|
@@ -37,7 +42,7 @@ export class HallucinationDetector {
|
|
|
37
42
|
if (factualResult && factualResult.status !== "pass") {
|
|
38
43
|
return factualResult;
|
|
39
44
|
}
|
|
40
|
-
// LLM
|
|
45
|
+
// Судья по консистентности (LLM): быстрый путь (нет решений) — без LLM-вызова.
|
|
41
46
|
const judgeResult = this.judge ? await this.judge.validate(response, this.consistency) : null;
|
|
42
47
|
const warnings = [];
|
|
43
48
|
if (judgeResult && judgeResult.status === "warn")
|