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
package/dist/core/agent.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { ProviderManager } from "../modules/providers/manager";
|
|
1
|
+
import { ProviderManager, activeProviderEndpoint, reasoningStrategyForProvider } from "../modules/providers/manager";
|
|
2
2
|
import { buildActiveProvider } from "../modules/providers/factory";
|
|
3
3
|
import { t } from "../i18n/index";
|
|
4
|
-
import {
|
|
4
|
+
import { errMsg } from "../utils";
|
|
5
|
+
import { resolveVerbosity } from "../ui/theme";
|
|
5
6
|
import { join } from "path";
|
|
7
|
+
import { projectMmaDir } from "../config/paths";
|
|
6
8
|
import { PromptBuilder } from "./prompt-builder";
|
|
7
9
|
import { processRegistry } from "../modules/processes";
|
|
8
10
|
import { SessionLogger } from "./session-logger";
|
|
@@ -15,20 +17,29 @@ import { createLoopState, clearIterationSignals } from "./agent/loop-state";
|
|
|
15
17
|
import { CompactionService } from "./agent/compaction";
|
|
16
18
|
import { TokenTracker } from "./agent/token-tracker";
|
|
17
19
|
import { buildPromptSnapshot, diffPrompt } from "./agent/prefix-monitor";
|
|
20
|
+
import { recordArtifactWrite, testChurnFile } from "./agent/artifact-progress";
|
|
18
21
|
import { ReasoningEffortResolver } from "./agent/reasoning-resolver";
|
|
22
|
+
import { repairToolCallPairing } from "./agent/tool-pairing";
|
|
19
23
|
import { ContextRenderer } from "./agent/context-renderer";
|
|
20
24
|
import { ToolBatchExecutor } from "./agent/tool-batch";
|
|
21
25
|
import { HallucinationGate } from "./agent/hallucination-gate";
|
|
26
|
+
import { resolveArtifactStates } from "./agent/artifact-resolve";
|
|
22
27
|
import { AuditGate } from "./agent/audit-gate";
|
|
28
|
+
import { CriticGate, gatherTurnToolEvents } from "./agent/critic-gate";
|
|
29
|
+
import { seedToolTags } from "../tools/auto-tags";
|
|
30
|
+
import { CriticClient } from "../modules/critic/client";
|
|
31
|
+
import { buildCriticProvider } from "../modules/critic/provider";
|
|
32
|
+
import { CRITIC_READONLY_TOOLS } from "../modules/critic/tools";
|
|
33
|
+
import { buildEvidencePack, isWorkMutation } from "../modules/critic/evidence";
|
|
34
|
+
import { deriveSessionName, generateSessionTitle, } from "../modules/session/auto-name";
|
|
23
35
|
import { resolvePromptOverflow, overflowHint, HINT_BLOCK_TOKENS, } from "./prompt-overflow";
|
|
24
36
|
import { MAX_EMPTY_RESPONSE_RETRIES, MAX_LLM_ERROR_RETRIES, MAX_REPEATED_TOOL_CALLS, MAX_REPEATED_TOOL_CALLS_INTERACTIVE, MUTATION_CYCLE_NUDGE_THRESHOLD, } from "./agent/constants";
|
|
25
37
|
export { toolOutputCharLimit, compactToolError } from "./agent/tool-output";
|
|
26
38
|
/**
|
|
27
|
-
* Read-only
|
|
28
|
-
*
|
|
29
|
-
* (
|
|
30
|
-
*
|
|
31
|
-
* cycle is never cut short.
|
|
39
|
+
* Read-only тулты-интроспекции. Повтор одного с теми же аргументами прогресса
|
|
40
|
+
* не даст никогда, поэтому интерактивный цикл жёстко стопает после нескольких
|
|
41
|
+
* повторов (наблюдалось: `session_info` в тугом цикле после готовой задачи).
|
|
42
|
+
* Мутирующие — только nudge, чтобы легитимный edit/build/read-цикл не резать.
|
|
32
43
|
*/
|
|
33
44
|
const LOOP_PRONE_STATUS_TOOLS = new Set([
|
|
34
45
|
"session_info",
|
|
@@ -37,6 +48,13 @@ const LOOP_PRONE_STATUS_TOOLS = new Set([
|
|
|
37
48
|
"file_info",
|
|
38
49
|
"project_map",
|
|
39
50
|
]);
|
|
51
|
+
function probeIdentityOf(config) {
|
|
52
|
+
return {
|
|
53
|
+
model: config.model,
|
|
54
|
+
baseUrl: activeProviderEndpoint(config.provider).baseUrl,
|
|
55
|
+
active: config.provider?.active,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
40
58
|
/**
|
|
41
59
|
* Ключ целевого объекта мутации для детекции «перфекционизм-цикла».
|
|
42
60
|
* edit_file/write_file → путь файла; bash → нормализованная команда.
|
|
@@ -65,14 +83,23 @@ export function mutationTargetKey(name, rawArgs) {
|
|
|
65
83
|
const cmd = a.command.replace(/\s+/g, " ").trim().slice(0, 80).toLowerCase();
|
|
66
84
|
return cmd ? `bash:${cmd}` : null;
|
|
67
85
|
}
|
|
68
|
-
// Bookkeeping
|
|
69
|
-
//
|
|
70
|
-
//
|
|
86
|
+
// Bookkeeping-churn: повторы plan/todo над тем же действием с чуть разными
|
|
87
|
+
// аргументами сожгли 10+ итераций в ses_mthn3c2a после отклонения аудита
|
|
88
|
+
// (модель «чинила» план вместо работы).
|
|
71
89
|
if ((name === "plan" || name === "todo") && typeof a.action === "string") {
|
|
72
90
|
return `book:${name}:${a.action.toLowerCase()}`;
|
|
73
91
|
}
|
|
92
|
+
// У plan_step нет action — ключ по status, чтобы повторные апдейты
|
|
93
|
+
// били в тот же гард подряд-дубликатов.
|
|
94
|
+
if (name === "plan_step" && typeof a.status === "string") {
|
|
95
|
+
return `book:${name}:${a.status.toLowerCase()}`;
|
|
96
|
+
}
|
|
74
97
|
return null;
|
|
75
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Агент: владеет циклом, конфигом, стоимостью, гейтами. Состояние хода —
|
|
101
|
+
* в LoopState, шаги — в extracted-модулях.
|
|
102
|
+
*/
|
|
76
103
|
export class Agent {
|
|
77
104
|
deps;
|
|
78
105
|
systemPromptAdded = false;
|
|
@@ -83,18 +110,22 @@ export class Agent {
|
|
|
83
110
|
* переживает несколько запусков цикла — хранит lastCompactionShown. */
|
|
84
111
|
contextRenderer;
|
|
85
112
|
policyState;
|
|
86
|
-
/**
|
|
113
|
+
/** Итог reasoning-пробы (B2): старт из deps, лениво обновляется в run(). */
|
|
87
114
|
probePassed;
|
|
88
|
-
/**
|
|
115
|
+
/** Общее мутабельное reasoning-состояние (владелец — агент, делит с set_thinking). */
|
|
89
116
|
reasoningState;
|
|
90
|
-
/**
|
|
117
|
+
/** Счётчик текущей итерации (для кулдаунов тулов). */
|
|
91
118
|
_currentIteration = 0;
|
|
92
|
-
/** Kind →
|
|
93
|
-
* exceeded the system budget (resolved once before the first LLM call). */
|
|
119
|
+
/** Kind → блок-замена для instructions/project-map сверх бюджета (резолв раз до первого LLM-вызова). */
|
|
94
120
|
promptOverrides = new Map();
|
|
95
|
-
/**
|
|
121
|
+
/** Идентичность, против которой гонялась проба. Сравнение ПО ЗНАЧЕНИЮ: все
|
|
122
|
+
* write-пути мутят живой конфиг in place и передают ТОТ ЖЕ объект в
|
|
123
|
+
* `reconfigure` — сравнение объектов изменения бы не увидело. Сидируется
|
|
124
|
+
* в конструкторе (строки до мутаций). */
|
|
125
|
+
lastProbeIdentity;
|
|
126
|
+
/** Малый всегда-включённый блок про сжатые инструкции. */
|
|
96
127
|
overflowHintBlock = null;
|
|
97
|
-
/**
|
|
128
|
+
/** Гард одноразового асинхронного резолва переполнения в run(). */
|
|
98
129
|
overflowResolved = false;
|
|
99
130
|
constructor(deps) {
|
|
100
131
|
this.deps = deps;
|
|
@@ -102,18 +133,19 @@ export class Agent {
|
|
|
102
133
|
this.contextRenderer = new ContextRenderer();
|
|
103
134
|
this.policyState = createPolicyState();
|
|
104
135
|
this.probePassed = deps.reasoningProbePassed ?? false;
|
|
136
|
+
this.lastProbeIdentity = probeIdentityOf(deps.config);
|
|
105
137
|
const initialLevel = (deps.config.reasoning?.mode !== "auto" ? deps.config.reasoning?.mode : undefined) ?? "medium";
|
|
106
138
|
this.reasoningState = { level: initialLevel, overrideIteration: -100 };
|
|
107
139
|
}
|
|
108
|
-
/**
|
|
140
|
+
/** Менеджер контекста наружу (аттач картинок в REPL и др. прямой доступ). */
|
|
109
141
|
get contextManager() {
|
|
110
142
|
return this.deps.contextManager;
|
|
111
143
|
}
|
|
112
|
-
/**
|
|
144
|
+
/** Текущий эффективный уровень reasoning. */
|
|
113
145
|
get reasoningLevel() {
|
|
114
146
|
return this.reasoningState.level;
|
|
115
147
|
}
|
|
116
|
-
/**
|
|
148
|
+
/** Ставит уровень reasoning (REPL/CLI). "auto" снимает ручной override. */
|
|
117
149
|
setReasoningLevel(level) {
|
|
118
150
|
if (level === "auto") {
|
|
119
151
|
this.reasoningState.manual = false;
|
|
@@ -122,15 +154,24 @@ export class Agent {
|
|
|
122
154
|
this.reasoningState.level = level;
|
|
123
155
|
this.reasoningState.manual = true;
|
|
124
156
|
}
|
|
125
|
-
/**
|
|
157
|
+
/** Номер текущей итерации (для кулдаунов тулов). */
|
|
126
158
|
get currentIteration() {
|
|
127
159
|
return this._currentIteration;
|
|
128
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Провенанс вызывающего: живой провайдер и модель (для стоимости/логов).
|
|
163
|
+
* @returns {provider, model} реально обслужившие вызов.
|
|
164
|
+
*/
|
|
129
165
|
callerProvenance() {
|
|
166
|
+
// Сначала живое имя: sticky failover переключает активную запись
|
|
167
|
+
// менеджера, не трогая config.provider.active.
|
|
168
|
+
const live = this.deps.getProviderName?.();
|
|
169
|
+
if (live)
|
|
170
|
+
return { provider: live, model: this.deps.config.model };
|
|
130
171
|
const p = this.deps.config.provider;
|
|
131
172
|
let providerName = p?.type || "unknown";
|
|
132
|
-
//
|
|
133
|
-
//
|
|
173
|
+
// При записях провайдера — метка активной, чтобы провенанс стоимости
|
|
174
|
+
// различался (напр. "lmstudio" vs "ollama").
|
|
134
175
|
if (p?.entries && p.entries.length > 0) {
|
|
135
176
|
const entry = p.active
|
|
136
177
|
? p.entries.find((e) => e.label === p.active || e.type === p.active)
|
|
@@ -140,7 +181,7 @@ export class Agent {
|
|
|
140
181
|
}
|
|
141
182
|
return { provider: providerName, model: this.deps.config.model };
|
|
142
183
|
}
|
|
143
|
-
/**
|
|
184
|
+
/** Типизированный доступ к модулям для CLI/REPL (напр. /lsp). */
|
|
144
185
|
getModule(name) {
|
|
145
186
|
return this.deps.moduleRegistry?.get(name);
|
|
146
187
|
}
|
|
@@ -157,18 +198,46 @@ export class Agent {
|
|
|
157
198
|
}
|
|
158
199
|
}
|
|
159
200
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
201
|
+
* Ставит (или снимает) терминальную prompt-поверхность для интерактивных
|
|
202
|
+
* тулов. Делегирует исполнителю, чтобы общий ToolContext — тот, что реально
|
|
203
|
+
* получают тулы — нёс тот же инстанс.
|
|
163
204
|
*/
|
|
164
205
|
setPromptIO(io) {
|
|
165
206
|
this.deps.toolExecutor.setPromptIO(io);
|
|
166
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Дописывает системный блок на текущий прогон (гайд онбординга).
|
|
210
|
+
* Удаление по идентичности через `removePromptBlock`. Блоки читаются при
|
|
211
|
+
* каждой `buildSystemPrompt`, эффект — со следующего хода.
|
|
212
|
+
*/
|
|
213
|
+
pushPromptBlock(block) {
|
|
214
|
+
this.deps.promptBlocks.push(block);
|
|
215
|
+
}
|
|
216
|
+
/** Убирает ранее запушенный блок (по ссылке). */
|
|
217
|
+
removePromptBlock(block) {
|
|
218
|
+
const i = this.deps.promptBlocks.indexOf(block);
|
|
219
|
+
if (i >= 0)
|
|
220
|
+
this.deps.promptBlocks.splice(i, 1);
|
|
221
|
+
}
|
|
222
|
+
/** Тестовый вид текущих блоков промпта. */
|
|
223
|
+
getPromptBlocksForTest() {
|
|
224
|
+
return this.deps.promptBlocks;
|
|
225
|
+
}
|
|
226
|
+
/** Общий ToolContext (бросает, если bootstrap его не дал). */
|
|
227
|
+
get toolContext() {
|
|
228
|
+
if (!this.deps.toolCtx)
|
|
229
|
+
throw new Error("toolCtx not provided to Agent");
|
|
230
|
+
return this.deps.toolCtx;
|
|
231
|
+
}
|
|
232
|
+
/** Небросающийся вариант `toolContext` для опциональных/lifecycle-проверок. */
|
|
233
|
+
tryToolContext() {
|
|
234
|
+
return this.deps.toolCtx;
|
|
235
|
+
}
|
|
167
236
|
buildSystemPrompt() {
|
|
168
237
|
const systemBudget = Math.floor(this.deps.config.contextWindow * this.deps.config.contextBudget.systemPrompt);
|
|
169
238
|
const builder = new PromptBuilder(systemBudget);
|
|
170
|
-
//
|
|
171
|
-
//
|
|
239
|
+
// Замены переполнения (сжатые инструкции/карта) приоритетнее исходных
|
|
240
|
+
// негабаритных блоков.
|
|
172
241
|
const applyOverrides = (blocks) => blocks.map((b) => (b.kind && this.promptOverrides.has(b.kind) ? this.promptOverrides.get(b.kind) : b));
|
|
173
242
|
builder.addBlocks(applyOverrides(this.deps.promptBlocks));
|
|
174
243
|
const dynamic = this.deps.getDynamicPromptBlocks?.() ?? [];
|
|
@@ -200,11 +269,9 @@ export class Agent {
|
|
|
200
269
|
};
|
|
201
270
|
}
|
|
202
271
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* carries the system-prompt block breakdown (which blocks, priorities,
|
|
207
|
-
* token estimates, what was excluded by the budget).
|
|
272
|
+
* Пишет состояние контекста в лог сессии. Каждую итерацию — полный таймлайн
|
|
273
|
+
* роста/компакции/состава; `start`-снимок дополнительно несёт разбивку
|
|
274
|
+
* системных блоков (что, приоритеты, оценки, что срезал бюджет).
|
|
208
275
|
*/
|
|
209
276
|
logContextStat(kind, iteration, slog, blocks) {
|
|
210
277
|
const cm = this.deps.contextManager;
|
|
@@ -232,16 +299,19 @@ export class Agent {
|
|
|
232
299
|
blocks,
|
|
233
300
|
});
|
|
234
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Текущий системный промпт с подсчётом токенов (для /sysprompt, диагностики).
|
|
304
|
+
* @returns текст, число токенов, исключённые блоки.
|
|
305
|
+
*/
|
|
235
306
|
getSystemPromptInfo() {
|
|
236
307
|
const { prompt, excluded } = this.buildSystemPrompt();
|
|
237
308
|
const tokenCount = this.deps.llmProvider.countTokens(prompt);
|
|
238
309
|
return { text: prompt, tokenCount, excluded };
|
|
239
310
|
}
|
|
240
311
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* the model what happened and how to read the full source.
|
|
312
|
+
* Одноразовая (первый прогон) обработка instructions/project-map сверх
|
|
313
|
+
* бюджета: LLM суммаризирует в остаток (диск-кэш) или усекает, а малый
|
|
314
|
+
* hint-блок говорит модели что случилось и как читать полный источник.
|
|
245
315
|
*/
|
|
246
316
|
async resolvePromptOverflowOnce() {
|
|
247
317
|
const cfg = this.deps.config;
|
|
@@ -255,7 +325,7 @@ export class Agent {
|
|
|
255
325
|
includedTokens: dry.blocks.filter((b) => b.included).reduce((s, b) => s + b.tokens, 0),
|
|
256
326
|
systemBudget,
|
|
257
327
|
provider: cfg.instructions?.summarize === false ? null : this.deps.llmProvider,
|
|
258
|
-
cacheDir: join(this.deps.baseDir, "
|
|
328
|
+
cacheDir: join(projectMmaDir(this.deps.baseDir), "cache", "prompt-summaries"),
|
|
259
329
|
logger: this.deps.logger,
|
|
260
330
|
});
|
|
261
331
|
for (const r of res.replacements) {
|
|
@@ -294,26 +364,42 @@ export class Agent {
|
|
|
294
364
|
}, phase);
|
|
295
365
|
onPhase?.(phase);
|
|
296
366
|
}
|
|
297
|
-
|
|
298
|
-
|
|
367
|
+
/**
|
|
368
|
+
* Прогоняет ход агента: MoE-путь или одиночный цикл, с автонеймингом сессии.
|
|
369
|
+
* @param input сообщение пользователя; @param onChunk/onMeta/onTool/onPhase/onReasoning колбэки вывода;
|
|
370
|
+
* @returns итог прогона.
|
|
371
|
+
*/
|
|
372
|
+
async run(input, onChunk, onMeta, onTool, onPhase, onReasoning) {
|
|
373
|
+
// Сброс флага shutdown от прошлого прерывания
|
|
299
374
|
this.shutdownRequested = false;
|
|
300
|
-
//
|
|
301
|
-
// (executeSingleAgentLoop
|
|
375
|
+
// Свежий контроллер на ход — MoE-путь полагается на него для Esc
|
|
376
|
+
// (executeSingleAgentLoop пересоздаёт его же для одиночного пути).
|
|
302
377
|
this.abortController = new AbortController();
|
|
303
378
|
this.setScope();
|
|
304
|
-
|
|
379
|
+
// P5 автосужение: детерминированно пред-посеваем теги тулов из сырого
|
|
380
|
+
// сообщения ДО первого LLM-вызова (системный промпт + схемы тулов).
|
|
381
|
+
// Только добавление — mid-session гранты enable_tools не снимаются.
|
|
382
|
+
// Пропуск при выключенном сужении (enableOnDemand: false → все тулы).
|
|
383
|
+
if (this.deps.config.tools?.enableOnDemand !== false && this.deps.toolTags) {
|
|
384
|
+
const added = seedToolTags(this.deps.toolTags, input);
|
|
385
|
+
if (added.length > 0) {
|
|
386
|
+
this.deps.logger.debug(`Auto-narrowing: pre-seeded tool tags [${added.join(", ")}]`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const { config, llmProvider, toolExecutor, pluginManager, contextManager, logger, sessionManager, } = this.deps;
|
|
305
390
|
const slog = new SessionLogger(sessionManager, logger, () => this.callerProvenance());
|
|
306
391
|
if (sessionManager && !sessionManager.getActive()) {
|
|
307
392
|
sessionManager.create();
|
|
308
393
|
logger.debug(`Session started: ${sessionManager.getActive()}`);
|
|
309
394
|
}
|
|
395
|
+
const activeMeta = sessionManager?.getActiveMeta();
|
|
396
|
+
const isFirstMessage = !!activeMeta && activeMeta.messageCount === 0;
|
|
310
397
|
if (!this.systemPromptAdded &&
|
|
311
398
|
!contextManager.getActiveHistory().some((m) => m.role === "system")) {
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
// checks for existing session_start entry to avoid double-logging.
|
|
399
|
+
// Стартовую диагностику сессии (окружение, плагины, скиллы) — первой
|
|
400
|
+
// записью в session.jsonl, чтобы файл был самоописываем для пост-анализа.
|
|
401
|
+
// LSP-проба логируется отдельно из repl.ts (может завершиться позже).
|
|
402
|
+
// Дедуп: logSessionStart проверяет существующий session_start от дублей.
|
|
317
403
|
slog.logSessionStart({
|
|
318
404
|
environment: this.deps.envReport,
|
|
319
405
|
});
|
|
@@ -321,9 +407,8 @@ export class Agent {
|
|
|
321
407
|
if (lazy.length > 0) {
|
|
322
408
|
this.deps.promptBlocks.push(...lazy);
|
|
323
409
|
}
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
// the first real build so the model still receives the instructions.
|
|
410
|
+
// Одноразовый резолв instructions/project-map сверх бюджета: суммаризация
|
|
411
|
+
// (кэш) или усечение ДО первой сборки, чтобы модель инструкции получила.
|
|
327
412
|
if (!this.overflowResolved) {
|
|
328
413
|
this.overflowResolved = true;
|
|
329
414
|
try {
|
|
@@ -333,9 +418,8 @@ export class Agent {
|
|
|
333
418
|
this.deps.logger.warn(t("prompt.overflow.failed", { error: String(err?.message ?? err) }));
|
|
334
419
|
}
|
|
335
420
|
}
|
|
336
|
-
// B2:
|
|
337
|
-
//
|
|
338
|
-
// respects the reasoning mechanism (bootstrap no longer blocks on it).
|
|
421
|
+
// B2: ждём фоновую reasoning-пробу рядом с ленивыми блоками, чтобы ПЕРВЫЙ
|
|
422
|
+
// LLM-вызов уже знал, держит ли бэкенд механизм (bootstrap больше не ждёт).
|
|
339
423
|
if (this.deps.reasoningProbe) {
|
|
340
424
|
this.probePassed = await this.deps.reasoningProbe();
|
|
341
425
|
}
|
|
@@ -352,17 +436,42 @@ export class Agent {
|
|
|
352
436
|
contextManager,
|
|
353
437
|
});
|
|
354
438
|
}
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
// just-sent task along with 40+ old turns).
|
|
439
|
+
// Новое сообщение — новый интервал компакции: итерации прошлого хода не
|
|
440
|
+
// должны триггерить компакцию на итерации 1 этого (наблюдалось: ход 2
|
|
441
|
+
// скомпактился на итерации 8, снеся только что присланную задачу с 40+ ходами).
|
|
359
442
|
if (typeof contextManager.resetUserTurn === "function") {
|
|
360
443
|
contextManager.resetUserTurn();
|
|
361
444
|
}
|
|
362
445
|
contextManager.addMessage({ role: "user", content: input });
|
|
363
446
|
slog.logUser(input);
|
|
364
|
-
|
|
365
|
-
|
|
447
|
+
if (isFirstMessage && activeMeta && sessionManager) {
|
|
448
|
+
const meta = sessionManager.getActiveMeta();
|
|
449
|
+
if (meta && (meta.nameSource ?? "default") !== "manual") {
|
|
450
|
+
sessionManager.rename(meta.id, deriveSessionName(input), "auto");
|
|
451
|
+
if (config.session.autoName !== false) {
|
|
452
|
+
const chat = async (prompt, signal) => {
|
|
453
|
+
let text = "";
|
|
454
|
+
for await (const chunk of llmProvider.chat([{ role: "user", content: prompt }], undefined, signal, { maxTokens: 48, reasoningEffort: "none" })) {
|
|
455
|
+
if (chunk.type === "text" && chunk.content)
|
|
456
|
+
text += chunk.content;
|
|
457
|
+
}
|
|
458
|
+
return text;
|
|
459
|
+
};
|
|
460
|
+
void generateSessionTitle(input, chat)
|
|
461
|
+
.then((title) => {
|
|
462
|
+
if (!title)
|
|
463
|
+
return;
|
|
464
|
+
const cur = sessionManager.getActiveMeta();
|
|
465
|
+
if (cur && cur.id === meta.id && (cur.nameSource ?? "default") === "auto") {
|
|
466
|
+
sessionManager.rename(meta.id, title, "auto");
|
|
467
|
+
}
|
|
468
|
+
})
|
|
469
|
+
.catch(() => { });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// Жизненный цикл хода для плагинов: считаем тул-события вне цикла,
|
|
474
|
+
// onTurnEnd — ровно один раз (успех, ошибка или прерывание).
|
|
366
475
|
const startedAt = Date.now();
|
|
367
476
|
let toolCalls = 0;
|
|
368
477
|
const countTool = (ev) => {
|
|
@@ -394,7 +503,7 @@ export class Agent {
|
|
|
394
503
|
toolExecutor,
|
|
395
504
|
logger,
|
|
396
505
|
baseDir: this.deps.baseDir,
|
|
397
|
-
}, input, () => this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase), {
|
|
506
|
+
}, input, () => this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase, onReasoning), {
|
|
398
507
|
onMeta,
|
|
399
508
|
onTool: countTool,
|
|
400
509
|
onPhase,
|
|
@@ -402,11 +511,11 @@ export class Agent {
|
|
|
402
511
|
onEvent: (event) => slog.logMoE(event.type, event.data),
|
|
403
512
|
sessionId: this.deps.sessionManager?.getActiveMeta()?.id,
|
|
404
513
|
})
|
|
405
|
-
: await this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase);
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
// session.jsonl
|
|
409
|
-
//
|
|
514
|
+
: await this.executeSingleAgentLoop(input, slog, onChunk, onMeta, countTool, onPhase, onReasoning);
|
|
515
|
+
// В MoE-режиме одиночный цикл (единственный писатель ходов ассистента)
|
|
516
|
+
// не бежит — без этого история схлопнется user→user между ходами, а в
|
|
517
|
+
// session.jsonl не будет ответа. Пишем итог один раз и только когда MoE
|
|
518
|
+
// реально вёл ход (фолбэк уже записал сам).
|
|
410
519
|
if (moeEnabled && result.moeHandled) {
|
|
411
520
|
if (result.text) {
|
|
412
521
|
contextManager.addMessage({ role: "assistant", content: result.text });
|
|
@@ -431,7 +540,7 @@ export class Agent {
|
|
|
431
540
|
throw err;
|
|
432
541
|
}
|
|
433
542
|
}
|
|
434
|
-
async executeSingleAgentLoop(input, slog, onChunk, onMeta, onTool, onPhase) {
|
|
543
|
+
async executeSingleAgentLoop(input, slog, onChunk, onMeta, onTool, onPhase, onReasoning) {
|
|
435
544
|
const { config, llmProvider, toolExecutor, pluginManager, contextManager, hallucinationDetector, logger, } = this.deps;
|
|
436
545
|
this.abortController = new AbortController();
|
|
437
546
|
// Единый контейнер состояния цикла — заменяет ~24 разрозненных `let`.
|
|
@@ -444,7 +553,7 @@ export class Agent {
|
|
|
444
553
|
slog,
|
|
445
554
|
hallucinationDetector,
|
|
446
555
|
});
|
|
447
|
-
const tokenTracker = new TokenTracker(this.costTracker, () => this.callerProvenance()
|
|
556
|
+
const tokenTracker = new TokenTracker(this.costTracker, () => this.callerProvenance());
|
|
448
557
|
const reasoningResolver = new ReasoningEffortResolver();
|
|
449
558
|
const toolBatch = new ToolBatchExecutor({
|
|
450
559
|
config,
|
|
@@ -463,11 +572,56 @@ export class Agent {
|
|
|
463
572
|
});
|
|
464
573
|
const hallucinationGate = new HallucinationGate(hallucinationDetector);
|
|
465
574
|
const auditGate = new AuditGate(this.deps.finalAudit);
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
575
|
+
// Защита: частичные/легаси конфиги (тесты, старый config.json) могут не иметь
|
|
576
|
+
// домена `critic` — считаем выключенным, а не роняем цикл.
|
|
577
|
+
const criticConfig = {
|
|
578
|
+
enabled: config.critic?.enabled ?? false,
|
|
579
|
+
tools: config.critic?.tools ?? true,
|
|
580
|
+
maxCalls: config.critic?.maxCalls ?? 4,
|
|
581
|
+
maxRepairCycles: config.critic?.maxRepairCycles ?? 1,
|
|
582
|
+
timeoutMs: config.critic?.timeoutMs ?? 15000,
|
|
583
|
+
};
|
|
584
|
+
// Phase 2: выделенные провайдер/модель, если настроены, иначе активный
|
|
585
|
+
// провайдер (v1). Битый оверрайд — warn + фолбэк, никогда не молча.
|
|
586
|
+
const criticProvider = buildCriticProvider(config.critic, config, this.deps.llmProvider, {
|
|
587
|
+
logger,
|
|
588
|
+
getSessionId: () => this.deps.sessionManager?.getActiveMeta()?.id,
|
|
589
|
+
});
|
|
590
|
+
const criticClient = new CriticClient({
|
|
591
|
+
provider: criticProvider,
|
|
592
|
+
timeoutMs: criticConfig.timeoutMs,
|
|
593
|
+
});
|
|
594
|
+
const criticGate = new CriticGate({
|
|
595
|
+
enabled: criticConfig.enabled,
|
|
596
|
+
maxCalls: criticConfig.maxCalls,
|
|
597
|
+
maxRepairCycles: criticConfig.maxRepairCycles,
|
|
598
|
+
verify: this.deps.criticOverride?.verify ?? ((pack, signal) => criticClient.verify(pack, signal, {
|
|
599
|
+
// Phase 2 read-only цикл: проверки бегут последовательно здесь (гейт
|
|
600
|
+
// await'ится между пакетами тулов), через общий executor с read-only
|
|
601
|
+
// allowlist — записи/shell недостижимы.
|
|
602
|
+
runCheck: criticConfig.tools && this.deps.toolCtx
|
|
603
|
+
? async (tool, args) => {
|
|
604
|
+
const res = await toolExecutor.executeByName(tool, args, {
|
|
605
|
+
...this.deps.toolCtx,
|
|
606
|
+
toolAllowlist: [...CRITIC_READONLY_TOOLS],
|
|
607
|
+
});
|
|
608
|
+
return { ok: res.success, output: res.output ?? "" };
|
|
609
|
+
}
|
|
610
|
+
: undefined,
|
|
611
|
+
})),
|
|
612
|
+
buildPack: (d) => buildEvidencePack({
|
|
613
|
+
task: d.task,
|
|
614
|
+
planSummary: d.planSummary,
|
|
615
|
+
toolEvents: d.toolEvents,
|
|
616
|
+
baseDir: this.deps.baseDir,
|
|
617
|
+
}),
|
|
618
|
+
signal: this.abortController?.signal,
|
|
619
|
+
});
|
|
620
|
+
// Определения тулов учитываем в бюджете контекста (идут через body.tools, не messages).
|
|
621
|
+
// Пересчитываем каждую итерацию, чтобы `enable_tools` (мутит общий
|
|
622
|
+
// activeToolTags) мог растить видимый модели набор прямо в прогоне.
|
|
623
|
+
// Привязка к итерации держит оценку бюджета и boundedOutput в синхроне
|
|
624
|
+
// с реально отправляемым.
|
|
471
625
|
let allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
|
|
472
626
|
let boundedToolNames = new Set(allToolsForBudget.filter((t) => t.boundedOutput).map((t) => t.name));
|
|
473
627
|
let toolTokenEstimate = allToolsForBudget.reduce((sum, t) => sum + estimateTokens(t.description + JSON.stringify(t.parameters)), 0);
|
|
@@ -476,7 +630,7 @@ export class Agent {
|
|
|
476
630
|
state.iteration++;
|
|
477
631
|
this._currentIteration = state.iteration;
|
|
478
632
|
contextManager.noteIteration();
|
|
479
|
-
//
|
|
633
|
+
// Перечитываем мутабельный набор тегов — вдруг вызывали enable_tools.
|
|
480
634
|
allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
|
|
481
635
|
boundedToolNames = new Set(allToolsForBudget.filter((t) => t.boundedOutput).map((t) => t.name));
|
|
482
636
|
toolTokenEstimate = allToolsForBudget.reduce((sum, t) => sum + Math.ceil((t.description.length + JSON.stringify(t.parameters).length) / 4), 0);
|
|
@@ -494,13 +648,34 @@ export class Agent {
|
|
|
494
648
|
// Единая точка компакции: интервал → качество (с cooldown) → переполнение.
|
|
495
649
|
compactionService.compactIfNeeded(state);
|
|
496
650
|
this.refreshSystemPrompt();
|
|
497
|
-
|
|
651
|
+
// Позиционная нормализация истории перед отправкой: вклинившийся user
|
|
652
|
+
// (step-gate/auto-advance плана) или осиротевший tool ломает контракт
|
|
653
|
+
// OpenAI и даёт 400 у строгих провайдеров. Новые прогоны порядок не
|
|
654
|
+
// портят (pendingMessages), проход лечит уже испорченные resume-истории.
|
|
655
|
+
const history = repairToolCallPairing(contextManager.getActiveHistory());
|
|
498
656
|
// Монитор стабильности префикса: считаем до запроса, пишем в llm_usage
|
|
499
657
|
// после. Char-оценка (chars/4) достаточна — важна динамика ratio и
|
|
500
658
|
// причина расхождения, а не абсолютная точность токенов.
|
|
501
659
|
const promptSnapshot = buildPromptSnapshot(history, allToolsForBudget);
|
|
502
660
|
state.prefixDelta = diffPrompt(state.prevPrompt, promptSnapshot, estimateTokens);
|
|
503
661
|
state.prevPrompt = promptSnapshot;
|
|
662
|
+
// Opt-in git-style context log: snapshot отправляемого контекста.
|
|
663
|
+
if (this.deps.contextLogger) {
|
|
664
|
+
try {
|
|
665
|
+
this.deps.contextLogger.record(history, allToolsForBudget, {
|
|
666
|
+
iteration: state.iteration,
|
|
667
|
+
ts: new Date().toISOString(),
|
|
668
|
+
systemTokens: estimateTokens(promptSnapshot.system),
|
|
669
|
+
toolTokens: estimateTokens(promptSnapshot.tools),
|
|
670
|
+
historyTokens: estimateTokens(promptSnapshot.history),
|
|
671
|
+
totalTokens: contextManager.getEstimatedTokens(),
|
|
672
|
+
contextWindow: config.contextWindow,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
// Логирование контекста не должно ронять агент.
|
|
677
|
+
}
|
|
678
|
+
}
|
|
504
679
|
if (state.prefixDelta.cause === "system" ||
|
|
505
680
|
state.prefixDelta.cause === "tools" ||
|
|
506
681
|
state.prefixDelta.cause === "history" ||
|
|
@@ -520,6 +695,7 @@ export class Agent {
|
|
|
520
695
|
const toolCalls = [];
|
|
521
696
|
let sawToolCall = false;
|
|
522
697
|
let emittedReasoning = false;
|
|
698
|
+
let malformedToolArgs = 0;
|
|
523
699
|
this.emitPhase(state.iteration, "thinking", onPhase);
|
|
524
700
|
const llmStart = Date.now();
|
|
525
701
|
const baseline = tokenTracker.beginIteration(state, contextManager.getEstimatedTokens());
|
|
@@ -554,7 +730,7 @@ export class Agent {
|
|
|
554
730
|
break;
|
|
555
731
|
if (chunk.type === "text" && chunk.content) {
|
|
556
732
|
if (emittedReasoning && !textContent) {
|
|
557
|
-
|
|
733
|
+
onReasoning?.("\n\n");
|
|
558
734
|
}
|
|
559
735
|
textContent += chunk.content;
|
|
560
736
|
const textOut = pluginManager.runOnText({ iteration: state.iteration, logger, contextManager }, chunk.content);
|
|
@@ -565,7 +741,7 @@ export class Agent {
|
|
|
565
741
|
if (config.showReasoning) {
|
|
566
742
|
const metaOut = pluginManager.runOnMeta({ iteration: state.iteration, logger, contextManager }, chunk.content);
|
|
567
743
|
if (metaOut) {
|
|
568
|
-
|
|
744
|
+
onReasoning?.(metaOut);
|
|
569
745
|
}
|
|
570
746
|
emittedReasoning = true;
|
|
571
747
|
}
|
|
@@ -577,7 +753,13 @@ export class Agent {
|
|
|
577
753
|
parsedArgs = JSON.parse(chunk.toolCall.arguments);
|
|
578
754
|
}
|
|
579
755
|
catch {
|
|
756
|
+
// Битый JSON аргументов раньше молча превращался в `{}` —
|
|
757
|
+
// модель звала тул с пустыми параметрами и не понимала причины.
|
|
758
|
+
// Логируем и сообщаем модели, чтобы она повторила вызов.
|
|
580
759
|
parsedArgs = {};
|
|
760
|
+
malformedToolArgs++;
|
|
761
|
+
logger.diagnostic("warn", `Invalid JSON in tool_call arguments for "${chunk.toolCall.name}" (${chunk.toolCall.arguments.length} chars) — passing {}`);
|
|
762
|
+
slog.logError(`tool_args_parse_error: tool="${chunk.toolCall.name}" tool_call_id="${chunk.toolCall.id}" chars=${chunk.toolCall.arguments.length}`);
|
|
581
763
|
}
|
|
582
764
|
toolCalls.push({
|
|
583
765
|
id: chunk.toolCall.id,
|
|
@@ -593,18 +775,17 @@ export class Agent {
|
|
|
593
775
|
tokenTracker.recordApiUsage(state, baseline, chunk.usage);
|
|
594
776
|
}
|
|
595
777
|
if (chunk.type === "warning" && chunk.content) {
|
|
596
|
-
//
|
|
597
|
-
//
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
logger.warn(`LLM warning: ${chunk.content}`);
|
|
778
|
+
// Нефатальное уведомление провайдера (напр., ответ срезан cap'ом).
|
|
779
|
+
// Рендерим через текстовый стрим, чтобы легло после частичной строки,
|
|
780
|
+
// не ломая markdown; в контекст/историю ассистента НЕ добавляем.
|
|
781
|
+
logger.diagnostic("warn", `LLM warning: ${chunk.content}`);
|
|
601
782
|
onChunk?.(`\n\n> ⚠️ ${chunk.content}\n`);
|
|
602
783
|
}
|
|
603
784
|
}
|
|
604
785
|
}
|
|
605
786
|
catch (err) {
|
|
606
787
|
if (this.shutdownRequested || err?.name === "AbortError") {
|
|
607
|
-
logger.
|
|
788
|
+
logger.diagnostic("info", "LLM call aborted (interrupt)");
|
|
608
789
|
break;
|
|
609
790
|
}
|
|
610
791
|
// Восстановимые ошибки LLM (например, токен-лимит на середине
|
|
@@ -614,7 +795,7 @@ export class Agent {
|
|
|
614
795
|
if (err instanceof LlmError && err.recoverable && state.llmErrorRetries < MAX_LLM_ERROR_RETRIES) {
|
|
615
796
|
state.iterRecoverableLlmError = true;
|
|
616
797
|
state.llmErrorRetries++;
|
|
617
|
-
logger.warn
|
|
798
|
+
logger.diagnostic("warn", `Recoverable LLM error, feeding back (${state.llmErrorRetries}/${MAX_LLM_ERROR_RETRIES}): ${err.message}`);
|
|
618
799
|
slog.logError(err.message);
|
|
619
800
|
pluginManager.runOnError({ iteration: state.iteration, logger, contextManager }, err);
|
|
620
801
|
contextManager.addMessage({
|
|
@@ -649,6 +830,14 @@ export class Agent {
|
|
|
649
830
|
if (this.shutdownRequested) {
|
|
650
831
|
break;
|
|
651
832
|
}
|
|
833
|
+
// Сообщаем модели, что её аргументы тула были невалидным JSON: иначе она
|
|
834
|
+
// видит лишь «пустой» вызов и не знает, что нужно повторить корректно.
|
|
835
|
+
if (malformedToolArgs > 0) {
|
|
836
|
+
contextManager.addMessage({
|
|
837
|
+
role: "user",
|
|
838
|
+
content: `<system-summary>${malformedToolArgs} tool call(s) in your last response had invalid JSON arguments and were executed with empty parameters. Re-issue them with valid JSON.</system-summary>`,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
652
841
|
let llmResponse = null;
|
|
653
842
|
if (sawToolCall) {
|
|
654
843
|
llmResponse = { type: "tool_call", calls: toolCalls };
|
|
@@ -660,6 +849,22 @@ export class Agent {
|
|
|
660
849
|
llmResponse = { type: "reasoning", content: reasoningContent };
|
|
661
850
|
}
|
|
662
851
|
pluginManager.runOnAfterThink({ iteration: state.iteration, logger, contextManager }, llmResponse);
|
|
852
|
+
// Аннотация вывода модели в context-log (reasoning/текст/tool_calls).
|
|
853
|
+
// НЕ входит в отправляемый контекст — пишется отдельным [OUTPUT]-блоком.
|
|
854
|
+
if (this.deps.contextLogger && (reasoningContent || textContent || toolCalls.length > 0)) {
|
|
855
|
+
try {
|
|
856
|
+
this.deps.contextLogger.recordOutput({
|
|
857
|
+
iteration: state.iteration,
|
|
858
|
+
ts: new Date().toISOString(),
|
|
859
|
+
reasoning: reasoningContent || undefined,
|
|
860
|
+
text: textContent || undefined,
|
|
861
|
+
toolCalls: toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments })),
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
catch {
|
|
865
|
+
// Логирование контекста не должно ронять агент.
|
|
866
|
+
}
|
|
867
|
+
}
|
|
663
868
|
// Детекция повторных одинаковых тулов (структурная, не по ключевым
|
|
664
869
|
// словам). Сигнал питает reasoning policy для понижения effort на рутине.
|
|
665
870
|
if (sawToolCall) {
|
|
@@ -668,27 +873,31 @@ export class Agent {
|
|
|
668
873
|
.join("|");
|
|
669
874
|
if (signature && signature === state.lastToolSignature) {
|
|
670
875
|
state.iterRepetitive = true;
|
|
671
|
-
// Nudge
|
|
672
|
-
//
|
|
673
|
-
//
|
|
674
|
-
//
|
|
675
|
-
//
|
|
676
|
-
|
|
677
|
-
logger.info(`Repeated identical tool call detected (iteration ${state.iteration}) — nudging model to finish or change approach`);
|
|
876
|
+
// Nudge во ВСЕХ режимах: идентичный (тул,аргументы) повтор — пустая
|
|
877
|
+
// итерация (наблюдалось: qwen3.5-9b гонял ту же verify-команду подряд,
|
|
878
|
+
// когда ответ уже был в результате). Мутирующие в интерактиве —
|
|
879
|
+
// только nudge (контроль у пользователя); status-тулы, которым повтор
|
|
880
|
+
// прогресса не даст никогда, — жёсткий стоп после нескольких повторов.
|
|
881
|
+
logger.diagnostic("info", `Repeated identical tool call detected (iteration ${state.iteration}) — nudging model to finish or change approach`);
|
|
678
882
|
contextManager.addMessage({
|
|
679
883
|
role: "user",
|
|
680
884
|
content: `<system-summary>You just called the same tool with identical arguments. If the task is done, answer with a final text response NOW. If the command failed, try a different approach.</system-summary>`,
|
|
681
885
|
});
|
|
682
886
|
state.repeatedToolCount++;
|
|
887
|
+
// Жёсткий стоп — только для status-тулов, которые не могут
|
|
888
|
+
// прогрессировать от повтора. Мутирующие/побочные тулы (например,
|
|
889
|
+
// bash) остаются nudge-only: пользователь мог явно попросить
|
|
890
|
+
// выполнить команду несколько раз, и обрыв дал бы пустой ответ.
|
|
891
|
+
const allStatusTools = toolCalls.every((tc) => LOOP_PRONE_STATUS_TOOLS.has(tc.name));
|
|
683
892
|
if (this.deps.exitOnComplete) {
|
|
684
|
-
if (state.repeatedToolCount >= MAX_REPEATED_TOOL_CALLS) {
|
|
685
|
-
logger.debug("Exit-on-complete: repeated identical tool call, stopping");
|
|
893
|
+
if (state.repeatedToolCount >= MAX_REPEATED_TOOL_CALLS && allStatusTools) {
|
|
894
|
+
logger.debug("Exit-on-complete: repeated identical status tool call, stopping");
|
|
686
895
|
break;
|
|
687
896
|
}
|
|
688
897
|
}
|
|
689
898
|
else if (state.repeatedToolCount >= MAX_REPEATED_TOOL_CALLS_INTERACTIVE &&
|
|
690
|
-
|
|
691
|
-
logger.warn
|
|
899
|
+
allStatusTools) {
|
|
900
|
+
logger.diagnostic("warn", `Repeated identical status tool ${state.repeatedToolCount}× (iteration ${state.iteration}) — stopping to avoid an infinite loop`);
|
|
692
901
|
break;
|
|
693
902
|
}
|
|
694
903
|
}
|
|
@@ -705,6 +914,11 @@ export class Agent {
|
|
|
705
914
|
const cycleNudges = [];
|
|
706
915
|
if (sawToolCall) {
|
|
707
916
|
for (const tc of toolCalls) {
|
|
917
|
+
// Прогресс тест/спек-артефактов (обобщённо, по пути файла).
|
|
918
|
+
if ((tc.name === "write_file" || tc.name === "edit_file") &&
|
|
919
|
+
typeof tc.arguments.path === "string") {
|
|
920
|
+
recordArtifactWrite(state.artifactChurn, tc.arguments.path);
|
|
921
|
+
}
|
|
708
922
|
const target = mutationTargetKey(tc.name, tc.arguments);
|
|
709
923
|
if (!target)
|
|
710
924
|
continue;
|
|
@@ -716,6 +930,10 @@ export class Agent {
|
|
|
716
930
|
cycleNudges.push(target);
|
|
717
931
|
}
|
|
718
932
|
}
|
|
933
|
+
for (const tc of toolCalls) {
|
|
934
|
+
if (isWorkMutation(tc.name))
|
|
935
|
+
state.mutationsSinceCritic++;
|
|
936
|
+
}
|
|
719
937
|
}
|
|
720
938
|
if (sawToolCall) {
|
|
721
939
|
const batch = await toolBatch.execute(state, toolCalls, boundedToolNames, textContent, reasoningContent, input, onTool, onMeta, slog);
|
|
@@ -723,12 +941,23 @@ export class Agent {
|
|
|
723
941
|
break;
|
|
724
942
|
}
|
|
725
943
|
if (cycleNudges.length > 0) {
|
|
726
|
-
logger.info
|
|
944
|
+
logger.diagnostic("info", `Perfectionism cycle detected (${cycleNudges.length} target(s) at ${MUTATION_CYCLE_NUDGE_THRESHOLD}+ mutations) — nudging model to finalize`);
|
|
727
945
|
contextManager.addMessage({
|
|
728
946
|
role: "user",
|
|
729
947
|
content: `<system-summary>You have already modified or re-run the same target many times (e.g. ${cycleNudges[0]}). If the latest output is correct, STOP polishing and answer with a final text response. More identical edits will not change the result.</system-summary>`,
|
|
730
948
|
});
|
|
731
949
|
}
|
|
950
|
+
// Тест/спек-churn: серия правок тестов без единой production-правки
|
|
951
|
+
// (обобщённо — см. artifact-progress.ts).
|
|
952
|
+
const churnFile = testChurnFile(state.artifactChurn);
|
|
953
|
+
if (churnFile) {
|
|
954
|
+
state.artifactChurn.nudged = true;
|
|
955
|
+
logger.diagnostic("info", `Test/spec artifact churn: "${churnFile}" repeatedly edited with no production changes — nudging to implement or change approach`);
|
|
956
|
+
contextManager.addMessage({
|
|
957
|
+
role: "user",
|
|
958
|
+
content: `<system-summary>${t("exec.test_churn_nudge", { file: churnFile })}</system-summary>`,
|
|
959
|
+
});
|
|
960
|
+
}
|
|
732
961
|
this.contextRenderer.render(this.deps.config.ui, contextManager, onMeta);
|
|
733
962
|
// Тулы означают, что модель продолжила работать, а не переотвечает —
|
|
734
963
|
// послабление на повтор после аудита больше не действует.
|
|
@@ -745,6 +974,11 @@ export class Agent {
|
|
|
745
974
|
contextManager,
|
|
746
975
|
onMeta,
|
|
747
976
|
onChunk,
|
|
977
|
+
// Строка о неуверенности — диагностика: только verbose-режим.
|
|
978
|
+
verbose: resolveVerbosity(this.deps.config.ui) === "verbose",
|
|
979
|
+
// Заземление заявлений на диск (отсутствующие/непарсящиеся файлы,
|
|
980
|
+
// поданные как созданные/валидные). Резолвится здесь — у гейта нет fs.
|
|
981
|
+
artifacts: resolveArtifactStates(this.deps.baseDir, textContent),
|
|
748
982
|
});
|
|
749
983
|
if (verdict.action === "block") {
|
|
750
984
|
return {
|
|
@@ -786,7 +1020,7 @@ export class Agent {
|
|
|
786
1020
|
if (!textContent?.trim()) {
|
|
787
1021
|
if (state.emptyResponseRetries < MAX_EMPTY_RESPONSE_RETRIES) {
|
|
788
1022
|
state.emptyResponseRetries++;
|
|
789
|
-
logger.warn
|
|
1023
|
+
logger.diagnostic("warn", `Empty response on iteration ${state.iteration} (retry ${state.emptyResponseRetries}/${MAX_EMPTY_RESPONSE_RETRIES})`);
|
|
790
1024
|
contextManager.addMessage({
|
|
791
1025
|
role: "user",
|
|
792
1026
|
content: `<system-summary>Your previous response was empty. Answer the user's task now with a final text response or call a tool. Do not reply with reasoning only.</system-summary>`,
|
|
@@ -794,7 +1028,22 @@ export class Agent {
|
|
|
794
1028
|
continue;
|
|
795
1029
|
}
|
|
796
1030
|
state.emptyResponseExhausted = true;
|
|
797
|
-
logger.warn
|
|
1031
|
+
logger.diagnostic("warn", `Empty response retries exhausted after ${MAX_EMPTY_RESPONSE_RETRIES} attempts`);
|
|
1032
|
+
}
|
|
1033
|
+
const criticAction = await criticGate.evaluate({
|
|
1034
|
+
state,
|
|
1035
|
+
iteration: state.iteration,
|
|
1036
|
+
logger,
|
|
1037
|
+
contextManager,
|
|
1038
|
+
slog,
|
|
1039
|
+
task: input,
|
|
1040
|
+
planSummary: this.deps.planSummary?.() ?? undefined,
|
|
1041
|
+
toolEvents: gatherTurnToolEvents(contextManager),
|
|
1042
|
+
beforeFinish: true,
|
|
1043
|
+
});
|
|
1044
|
+
if (criticAction === "retry") {
|
|
1045
|
+
state.suppressRepetitionRetry = true;
|
|
1046
|
+
continue;
|
|
798
1047
|
}
|
|
799
1048
|
const auditVerdict = await auditGate.evaluate({
|
|
800
1049
|
state,
|
|
@@ -812,8 +1061,6 @@ export class Agent {
|
|
|
812
1061
|
// auditFailed уже выставлен вентилем.
|
|
813
1062
|
break;
|
|
814
1063
|
}
|
|
815
|
-
// reject → continue: аудит уже добавил контекст и взвёл suppressRepetitionRetry.
|
|
816
|
-
continue;
|
|
817
1064
|
}
|
|
818
1065
|
const tokensUsed = contextManager.getEstimatedTokens();
|
|
819
1066
|
const budget = contextManager.getBudget();
|
|
@@ -838,13 +1085,15 @@ export class Agent {
|
|
|
838
1085
|
};
|
|
839
1086
|
}
|
|
840
1087
|
return {
|
|
841
|
-
success: state.emptyResponseExhausted || state.auditFailed
|
|
1088
|
+
success: !(state.emptyResponseExhausted || state.auditFailed || state.retryLoopStopped),
|
|
842
1089
|
text: state.lastText,
|
|
843
1090
|
error: state.emptyResponseExhausted
|
|
844
1091
|
? t("error.empty_response")
|
|
845
1092
|
: state.auditFailed
|
|
846
1093
|
? t("error.audit_failed", { summary: state.lastAuditSummary })
|
|
847
|
-
:
|
|
1094
|
+
: state.retryLoopStopped
|
|
1095
|
+
? t("error.retry_loop_stopped", { count: state.consecutiveFailedBatches })
|
|
1096
|
+
: undefined,
|
|
848
1097
|
iterationCount: state.iteration,
|
|
849
1098
|
contextUsed: tokensUsed,
|
|
850
1099
|
contextLimit: budget.history,
|
|
@@ -883,16 +1132,54 @@ export class Agent {
|
|
|
883
1132
|
source: state.cacheSource,
|
|
884
1133
|
};
|
|
885
1134
|
}
|
|
1135
|
+
/** Сбрасывает контекст (следующий прогон пересоберёт системный промпт). */
|
|
886
1136
|
clearContext() {
|
|
887
1137
|
this.deps.contextManager.clear();
|
|
888
1138
|
this.systemPromptAdded = false;
|
|
889
1139
|
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Применяет новый конфиг живьём: provider-first проба контекста, пересборка
|
|
1142
|
+
* провайдера, ресайз бюджетов, обновление стоимости.
|
|
1143
|
+
* @param config новый конфиг приложения.
|
|
1144
|
+
*/
|
|
890
1145
|
async reconfigure(config) {
|
|
891
1146
|
const { TokenCounter } = await import("../llm/token-counter");
|
|
892
|
-
|
|
1147
|
+
// Provider-first контекст: при смене модели/бэкенда спрашиваем новый
|
|
1148
|
+
// провайдер о реально отдаваемом окне ДО пересборки бюджетов вокруг
|
|
1149
|
+
// (возможно протухшего) настроенного значения. Ручные правки
|
|
1150
|
+
// `contextWindow` без смены идентичности уважаются как есть.
|
|
1151
|
+
// Fail-open: ошибки пробы хранят настроенное значение.
|
|
1152
|
+
try {
|
|
1153
|
+
const nextId = probeIdentityOf(config);
|
|
1154
|
+
const prevId = this.lastProbeIdentity;
|
|
1155
|
+
const identityChanged = prevId.model !== nextId.model ||
|
|
1156
|
+
prevId.baseUrl !== nextId.baseUrl ||
|
|
1157
|
+
prevId.active !== nextId.active;
|
|
1158
|
+
if (identityChanged) {
|
|
1159
|
+
const nextEndpoint = activeProviderEndpoint(config.provider);
|
|
1160
|
+
const { probeModel, probeProviderContext } = await import("../llm/model-loader");
|
|
1161
|
+
const lmProbe = await probeModel(nextEndpoint.baseUrl, config.model);
|
|
1162
|
+
const detected = (lmProbe ? { model: lmProbe.model, actual: lmProbe.contextLength } : null) ??
|
|
1163
|
+
(await probeProviderContext(nextEndpoint.baseUrl, config.model, {
|
|
1164
|
+
apiKey: nextEndpoint.apiKey,
|
|
1165
|
+
providerType: nextEndpoint.type,
|
|
1166
|
+
}));
|
|
1167
|
+
if (detected && detected.actual !== config.contextWindow) {
|
|
1168
|
+
this.deps.logger?.info(`Context probe: auto-set contextWindow from provider — model "${detected.model}" serves ${detected.actual} tokens (configured was ${config.contextWindow}).`);
|
|
1169
|
+
config.contextWindow = detected.actual;
|
|
1170
|
+
}
|
|
1171
|
+
this.lastProbeIdentity = nextId;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
catch {
|
|
1175
|
+
// Проба — best-effort, переключение модели/провайдера не блокируем никогда.
|
|
1176
|
+
}
|
|
1177
|
+
const built = buildActiveProvider(config, this.deps.logger, {
|
|
893
1178
|
getSessionId: () => this.deps.sessionManager?.getActiveMeta()?.id,
|
|
894
1179
|
});
|
|
1180
|
+
const newProvider = built.provider;
|
|
895
1181
|
this.deps.llmProvider = newProvider;
|
|
1182
|
+
this.deps.getProviderName = () => built.manager.activeName;
|
|
896
1183
|
this.deps.toolExecutor.updateProvider(newProvider);
|
|
897
1184
|
const newTokenCounter = new TokenCounter(config.model);
|
|
898
1185
|
this.deps.contextManager.resize(config.contextWindow, config.contextBudget, newTokenCounter);
|
|
@@ -900,7 +1187,25 @@ export class Agent {
|
|
|
900
1187
|
if (this.deps.configRef)
|
|
901
1188
|
this.deps.configRef.current = config;
|
|
902
1189
|
this.costTracker.setModel(config.model);
|
|
1190
|
+
this.applyReasoningStrategy(config.provider);
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Пересчитывает стратегию reasoning из АКТИВНОЙ записи провайдера. Без этого
|
|
1194
|
+
* `/provider use` и смена модели оставляли старую стратегию (prompt-tag против
|
|
1195
|
+
* openai-effort) на весь сеанс. При "none" гасит probe-флаг, чтобы auto-режим
|
|
1196
|
+
* не полагался на устаревшую пробу.
|
|
1197
|
+
* @param provider конфиг провайдера (entries/active).
|
|
1198
|
+
*/
|
|
1199
|
+
applyReasoningStrategy(provider) {
|
|
1200
|
+
const strategy = reasoningStrategyForProvider(provider);
|
|
1201
|
+
this.deps.reasoningStrategy = strategy;
|
|
1202
|
+
if (strategy === "none")
|
|
1203
|
+
this.probePassed = false;
|
|
903
1204
|
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Переключает активного провайдера (и модель опционально).
|
|
1207
|
+
* @param name имя записи провайдера; @param model новая модель.
|
|
1208
|
+
*/
|
|
904
1209
|
setProvider(name, model) {
|
|
905
1210
|
const { manager, provider } = buildActiveProvider(this.deps.config, this.deps.logger, {
|
|
906
1211
|
getSessionId: () => this.deps.sessionManager?.getActiveMeta()?.id,
|
|
@@ -908,9 +1213,15 @@ export class Agent {
|
|
|
908
1213
|
manager.switch(name, model);
|
|
909
1214
|
const providerCfg = manager.toConfig();
|
|
910
1215
|
this.deps.config.provider = providerCfg;
|
|
1216
|
+
this.applyReasoningStrategy(providerCfg);
|
|
911
1217
|
this.deps.llmProvider = provider;
|
|
1218
|
+
this.deps.getProviderName = () => manager.activeName;
|
|
912
1219
|
this.deps.toolExecutor.updateProvider(provider);
|
|
913
1220
|
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Список записей провайдеров с флагом активности.
|
|
1223
|
+
* @returns записи с label и active.
|
|
1224
|
+
*/
|
|
914
1225
|
listProviders() {
|
|
915
1226
|
const manager = new ProviderManager(this.deps.config.provider, {
|
|
916
1227
|
contextWindow: this.deps.config.contextWindow,
|
|
@@ -924,7 +1235,7 @@ export class Agent {
|
|
|
924
1235
|
active: e.label === activeName || e.type === activeName,
|
|
925
1236
|
}));
|
|
926
1237
|
}
|
|
927
|
-
/**
|
|
1238
|
+
/** Строит (или берёт кэшированный) провайдер именованной записи — health-чеки, роутинг. */
|
|
928
1239
|
getProviderFor(name) {
|
|
929
1240
|
try {
|
|
930
1241
|
const manager = new ProviderManager(this.deps.config.provider, {
|
|
@@ -938,10 +1249,15 @@ export class Agent {
|
|
|
938
1249
|
manager.switch(name);
|
|
939
1250
|
return manager.active;
|
|
940
1251
|
}
|
|
941
|
-
catch {
|
|
1252
|
+
catch (err) {
|
|
1253
|
+
this.deps.logger.warn(t("error.provider_build", { name, error: errMsg(err) }));
|
|
942
1254
|
return undefined;
|
|
943
1255
|
}
|
|
944
1256
|
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Заменяет контекст историей (resume): системный промпт + сообщения без дубля system.
|
|
1259
|
+
* @param messages сообщения для восстановления.
|
|
1260
|
+
*/
|
|
945
1261
|
setContext(messages) {
|
|
946
1262
|
const { contextManager } = this.deps;
|
|
947
1263
|
contextManager.clear();
|
|
@@ -958,6 +1274,11 @@ export class Agent {
|
|
|
958
1274
|
});
|
|
959
1275
|
}
|
|
960
1276
|
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Останавливает прогон: abort, kill фоновых процессов, onSessionEnd.
|
|
1279
|
+
* Лог сессии НЕ закрываем — сессия ещё активна (закрытие только на
|
|
1280
|
+
* реальном конце: exit, delete).
|
|
1281
|
+
*/
|
|
961
1282
|
shutdown() {
|
|
962
1283
|
this.shutdownRequested = true;
|
|
963
1284
|
this.abortController?.abort();
|
|
@@ -967,8 +1288,8 @@ export class Agent {
|
|
|
967
1288
|
if (killed > 0) {
|
|
968
1289
|
logger.info(t("env.killed_processes", { count: String(killed) }));
|
|
969
1290
|
}
|
|
970
|
-
//
|
|
971
|
-
//
|
|
1291
|
+
// Сессионный лог тут НЕ закрываем — сессия ещё активна.
|
|
1292
|
+
// Закрытие только на реальном конце (exit, удаление сессии).
|
|
972
1293
|
pluginManager.runOnSessionEnd({
|
|
973
1294
|
logger,
|
|
974
1295
|
sessionManager: sessionManager?.getActiveMeta(),
|