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
|
@@ -2,10 +2,10 @@ import { createProvider, defaultRegistry } from "./create";
|
|
|
2
2
|
import { resolveCacheCapability } from "./cache";
|
|
3
3
|
import { BUILTIN_PROVIDERS } from "./presets";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Рулит активным провайдером + моделью в рантайме. Несколько настроенных
|
|
6
|
+
* провайдеров (`ProviderConfig.entries`) с hot-swap (`switch`, `setModel`).
|
|
7
|
+
* Легаси-конфиги (`{type, baseUrl, apiKey}`) нормализуются в список из одной
|
|
8
|
+
* записи — старые конфиги продолжают работать.
|
|
9
9
|
*/
|
|
10
10
|
export class ProviderManager {
|
|
11
11
|
entries;
|
|
@@ -23,24 +23,26 @@ export class ProviderManager {
|
|
|
23
23
|
this.lastKnownFallback = config.fallback;
|
|
24
24
|
this.model = "";
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
26
|
+
/** Модель, с которой собран активный провайдер. */
|
|
27
27
|
get activeModel() {
|
|
28
28
|
return this.model;
|
|
29
29
|
}
|
|
30
|
-
/**
|
|
30
|
+
/** Имя/label активного провайдера. */
|
|
31
31
|
get activeName() {
|
|
32
32
|
return this.activeProviderName;
|
|
33
33
|
}
|
|
34
|
+
/** Активный провайдер (строится и кешируется). */
|
|
34
35
|
get active() {
|
|
35
36
|
return this.build(this.activeProviderName);
|
|
36
37
|
}
|
|
38
|
+
/** Активная запись (неизвестная — ошибка). */
|
|
37
39
|
get activeEntry() {
|
|
38
40
|
const entry = this.entryByName(this.activeProviderName);
|
|
39
41
|
if (!entry)
|
|
40
42
|
throw new Error(`Unknown active provider "${this.activeProviderName}"`);
|
|
41
43
|
return entry;
|
|
42
44
|
}
|
|
43
|
-
/**
|
|
45
|
+
/** Все записи (с метаданными пресетов), по приоритету. */
|
|
44
46
|
list() {
|
|
45
47
|
return this.entries
|
|
46
48
|
.map((e) => {
|
|
@@ -51,17 +53,18 @@ export class ProviderManager {
|
|
|
51
53
|
.sort((a, b) => a.p - b.p || a.i - b.i)
|
|
52
54
|
.map(({ e }) => e);
|
|
53
55
|
}
|
|
56
|
+
/** Имена всех записей по приоритету. */
|
|
54
57
|
listNames() {
|
|
55
58
|
return this.list().map((e) => e.label);
|
|
56
59
|
}
|
|
57
60
|
/**
|
|
58
|
-
* CODE-ONLY
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
+
* CODE-ONLY роутинг capabilities: первая запись (по приоритету), чей пресет
|
|
62
|
+
* и окно контекста закрывают `needs`. Модели не exposed — модель провайдеров
|
|
63
|
+
* не выбирает.
|
|
61
64
|
*/
|
|
62
65
|
pickFor(needs) {
|
|
63
66
|
const globalWindow = this.opts.contextWindow;
|
|
64
|
-
//
|
|
67
|
+
// Без ограничений — оставить текущий активный провайдер.
|
|
65
68
|
if (!needs.vision && !needs.tools && needs.minContext === undefined) {
|
|
66
69
|
return this.activeProviderName;
|
|
67
70
|
}
|
|
@@ -79,7 +82,7 @@ export class ProviderManager {
|
|
|
79
82
|
}
|
|
80
83
|
return undefined;
|
|
81
84
|
}
|
|
82
|
-
/** Hot-swap
|
|
85
|
+
/** Hot-swap на именованный провайдер; пересобрать активный. */
|
|
83
86
|
switch(name, model) {
|
|
84
87
|
if (!this.entryByName(name)) {
|
|
85
88
|
throw new Error(`Unknown provider "${name}"`);
|
|
@@ -89,19 +92,23 @@ export class ProviderManager {
|
|
|
89
92
|
this.model = model;
|
|
90
93
|
return this.active;
|
|
91
94
|
}
|
|
92
|
-
/**
|
|
95
|
+
/** Пересобрать активный провайдер с новой моделью. */
|
|
93
96
|
setModel(model) {
|
|
94
97
|
this.model = model;
|
|
98
|
+
// Кэш ключуется `${name}|${model}`; старые записи под прежнюю модель
|
|
99
|
+
// бесполезны и копятся. Сбрасываем — провайдер пересоберётся под новый
|
|
100
|
+
// model при следующем обращении к `active`.
|
|
101
|
+
this.resetCache();
|
|
95
102
|
return this.active;
|
|
96
103
|
}
|
|
97
|
-
/**
|
|
104
|
+
/** Сериализовать обратно в ProviderConfig (active/entries в синхроне). */
|
|
98
105
|
toConfig(fallback) {
|
|
99
106
|
const active = this.activeEntry;
|
|
100
107
|
return {
|
|
101
108
|
active: this.activeProviderName,
|
|
102
109
|
entries: this.entries,
|
|
103
|
-
//
|
|
104
|
-
//
|
|
110
|
+
// Сохранить настроенное значение — хардкод false молча ронял
|
|
111
|
+
// пользовательский provider.fallback при каждом hot-swap.
|
|
105
112
|
fallback: fallback ?? this.lastKnownFallback ?? false,
|
|
106
113
|
type: active.type,
|
|
107
114
|
baseUrl: active.baseUrl,
|
|
@@ -112,7 +119,7 @@ export class ProviderManager {
|
|
|
112
119
|
if (Array.isArray(config.entries) && config.entries.length > 0) {
|
|
113
120
|
return config.entries.map((e) => ({ ...e, label: e.label || e.type }));
|
|
114
121
|
}
|
|
115
|
-
//
|
|
122
|
+
// Легаси-форма одного провайдера (или пустой конфиг) → одна дефолтная запись.
|
|
116
123
|
return [
|
|
117
124
|
{
|
|
118
125
|
type: config.type ?? "openai-compat",
|
|
@@ -162,15 +169,39 @@ export class ProviderManager {
|
|
|
162
169
|
return { cache: resolveCacheCapability(undefined, entry.cache) };
|
|
163
170
|
return { ...spec.capabilities, cache: resolveCacheCapability(spec, entry.cache) };
|
|
164
171
|
}
|
|
165
|
-
/**
|
|
172
|
+
/** Очистить кеш сборки (при смене конфига). */
|
|
166
173
|
resetCache() {
|
|
167
174
|
this.cache.clear();
|
|
168
175
|
}
|
|
169
176
|
}
|
|
170
|
-
/**
|
|
177
|
+
/** Метаданные пресетов для CLI (label/capabilities/baseUrl). */
|
|
171
178
|
export function presetOf(type) {
|
|
172
179
|
const spec = BUILTIN_PROVIDERS.find((p) => p.type === type);
|
|
173
180
|
if (!spec)
|
|
174
181
|
return undefined;
|
|
175
182
|
return { label: spec.label, baseUrl: spec.baseUrl };
|
|
176
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Эндпоинт АКТИВНОГО провайдера для внеполосных вызовов (context probe,
|
|
186
|
+
* автозагрузка моделей, ключ кеша reasoning-пробы). Легаси-плоские поля
|
|
187
|
+
* (`type`/`baseUrl`/`apiKey`) отражают активное на момент последней записи и
|
|
188
|
+
* протухают после hot-swap `/provider use` — истина в entries. Фолбэк на
|
|
189
|
+
* плоские поля для легаси-конфигов.
|
|
190
|
+
*/
|
|
191
|
+
export function activeProviderEndpoint(config) {
|
|
192
|
+
const entries = Array.isArray(config?.entries) ? config.entries : [];
|
|
193
|
+
const active = entries.find((e) => e.label === config?.active || e.type === config?.active) ?? entries[0];
|
|
194
|
+
if (active) {
|
|
195
|
+
return { baseUrl: active.baseUrl, apiKey: active.apiKey, type: active.type };
|
|
196
|
+
}
|
|
197
|
+
return { baseUrl: config?.baseUrl ?? "", apiKey: config?.apiKey, type: config?.type ?? "openai-compat" };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Стратегия reasoning АКТИВНОЙ записи провайдера (из её пресета). Легаси-плоское
|
|
201
|
+
* `type` протухает после hot-swap `/provider use` и давало чужую стратегию
|
|
202
|
+
* (напр. `prompt-tag` при активном `opencode-go`); неизвестный тип → "none".
|
|
203
|
+
*/
|
|
204
|
+
export function reasoningStrategyForProvider(config) {
|
|
205
|
+
const type = activeProviderEndpoint(config).type;
|
|
206
|
+
return BUILTIN_PROVIDERS.find((p) => p.type === type)?.capabilities.reasoningStrategy ?? "none";
|
|
207
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Display/identity-имя записи: label, иначе type. */
|
|
2
|
+
export function providerEntryName(entry) {
|
|
3
|
+
return entry.label || entry.type;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Занято ли `name` другой записью (кроме `exceptName`).
|
|
7
|
+
* Записи адресуются по `label || type`, легаси-поиск матчит и голый `type`,
|
|
8
|
+
* так что новый/переименованный провайдер не должен коллизить ни с тем, ни с
|
|
9
|
+
* другим — иначе `provider.active` и edit/delete молча бьют не в ту запись.
|
|
10
|
+
* `exceptName` позволяет правке сохранить собственное текущее имя.
|
|
11
|
+
*/
|
|
12
|
+
export function providerNameTaken(entries, name, exceptName) {
|
|
13
|
+
return entries.some((e) => providerEntryName(e) !== exceptName &&
|
|
14
|
+
(providerEntryName(e) === name || e.type === name));
|
|
15
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { errMsg } from "../../utils";
|
|
2
|
+
/** Base URL + ключ активного провайдера (сначала entries, потом legacy). */
|
|
3
|
+
export function resolveProviderTarget(provider) {
|
|
4
|
+
const entries = provider.entries ?? [];
|
|
5
|
+
if (entries.length > 0) {
|
|
6
|
+
const active = provider.active;
|
|
7
|
+
const entry = entries.find((e) => (e.label ?? e.type) === active) ??
|
|
8
|
+
entries.find((e) => e.type === active) ??
|
|
9
|
+
entries[0];
|
|
10
|
+
return { baseUrl: entry.baseUrl, apiKey: entry.apiKey ?? "" };
|
|
11
|
+
}
|
|
12
|
+
return { baseUrl: provider.baseUrl, apiKey: provider.apiKey ?? "" };
|
|
13
|
+
}
|
|
14
|
+
/** Резолв активного провайдера из конфига, затем его preflight. */
|
|
15
|
+
export function preflightFromConfig(ctx) {
|
|
16
|
+
const target = resolveProviderTarget(ctx.config.provider);
|
|
17
|
+
return preflightProvider({
|
|
18
|
+
baseUrl: target.baseUrl,
|
|
19
|
+
apiKey: target.apiKey,
|
|
20
|
+
model: ctx.config.model,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** Минимальная дешёвая chat-проба: подтверждает, что модель загружена. */
|
|
24
|
+
export async function probeChat(cfg, timeoutMs) {
|
|
25
|
+
const url = `${cfg.baseUrl.replace(/\/+$/, "")}/chat/completions`;
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
28
|
+
try {
|
|
29
|
+
const resp = await fetch(url, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Bearer ${cfg.apiKey}`,
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
model: cfg.model,
|
|
37
|
+
messages: [{ role: "user", content: "ok" }],
|
|
38
|
+
max_tokens: 1,
|
|
39
|
+
}),
|
|
40
|
+
signal: controller.signal,
|
|
41
|
+
});
|
|
42
|
+
if (!resp.ok)
|
|
43
|
+
throw new Error(`HTTP ${resp.status}`);
|
|
44
|
+
const data = (await resp.json());
|
|
45
|
+
const msg = data.choices?.[0]?.message;
|
|
46
|
+
return msg?.content || msg?.reasoning_content || "";
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Проверить, что провайдер отвечает и модель загружена. Один chat-вызов с
|
|
54
|
+
* max_tokens=1 (стоимость пренебрежима). Не висит: ограничен timeoutMs.
|
|
55
|
+
*/
|
|
56
|
+
export async function preflightProvider(cfg, opts = {}) {
|
|
57
|
+
const timeoutMs = opts.timeoutMs ?? 3000;
|
|
58
|
+
const probe = opts.probe ?? probeChat;
|
|
59
|
+
const start = Date.now();
|
|
60
|
+
try {
|
|
61
|
+
const text = await probe(cfg, timeoutMs);
|
|
62
|
+
const ms = Date.now() - start;
|
|
63
|
+
return text ? { ok: true, reason: "ok", ms } : { ok: false, reason: "no-model", ms };
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const ms = Date.now() - start;
|
|
67
|
+
const message = errMsg(err);
|
|
68
|
+
const reason = /abort|timeout/i.test(message) ? "timeout" : "unreachable";
|
|
69
|
+
return { ok: false, reason, ms };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
// Builtin-пресеты провайдеров: capabilities, кеш-поведение, фабрики, base URL.
|
|
1
2
|
// src/modules/providers/presets.ts
|
|
2
3
|
import { OpenAICompatProvider } from "../../llm/openai-compat";
|
|
3
|
-
/**
|
|
4
|
+
/** Сборка OpenAI-совместимого провайдера (дефолт для большинства бэкендов). */
|
|
4
5
|
function openaiCompat(opts) {
|
|
5
6
|
const retry = (opts.retry ?? {
|
|
6
7
|
maxRetries: 3,
|
|
@@ -48,8 +49,37 @@ const OPENAI_COMPAT = {
|
|
|
48
49
|
},
|
|
49
50
|
create: openaiCompat,
|
|
50
51
|
};
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
/** LM Studio как first-class провайдер: тот же OpenAI-транспорт,
|
|
53
|
+
* но свой type — native discovery (`/api/v1/models`: загруженный контекст,
|
|
54
|
+
* capabilities) и фиксированный дефолтный URL едут с ним, а не прячутся
|
|
55
|
+
* внутри generic-записи `openai-compat`. */
|
|
56
|
+
const LMSTUDIO = {
|
|
57
|
+
type: "lmstudio",
|
|
58
|
+
label: "LM Studio (localhost:1234)",
|
|
59
|
+
baseUrl: "http://localhost:1234/v1",
|
|
60
|
+
capabilities: {
|
|
61
|
+
streaming: true,
|
|
62
|
+
tools: true,
|
|
63
|
+
vision: false,
|
|
64
|
+
reasoningStrategy: "prompt-tag",
|
|
65
|
+
listModels: true,
|
|
66
|
+
requiresKey: false,
|
|
67
|
+
auth: "bearer",
|
|
68
|
+
// Тот же локальный KV-кеш, что и у generic openai-compat.
|
|
69
|
+
cache: {
|
|
70
|
+
mechanism: "local",
|
|
71
|
+
report: "openai",
|
|
72
|
+
sessionHeader: false,
|
|
73
|
+
requestCachePrompt: false,
|
|
74
|
+
requestPromptCacheKey: false,
|
|
75
|
+
requestSessionId: false,
|
|
76
|
+
requestCacheControl: false,
|
|
77
|
+
requestStreamUsage: true,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
create: openaiCompat,
|
|
81
|
+
};
|
|
82
|
+
const OPENROUTER = { type: "openrouter",
|
|
53
83
|
label: "OpenRouter (openrouter.ai)",
|
|
54
84
|
baseUrl: "https://openrouter.ai/api/v1",
|
|
55
85
|
capabilities: {
|
|
@@ -127,9 +157,9 @@ const ANTHROPIC = {
|
|
|
127
157
|
requestStreamUsage: false,
|
|
128
158
|
},
|
|
129
159
|
},
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
160
|
+
// Messages API Anthropic НЕ OpenAI-совместим. Пресет — заглушка до настоящего
|
|
161
|
+
// AnthropicProvider; фабрика пока идёт по OpenAI-кодпасу (сломано), что видно
|
|
162
|
+
// через capabilities.
|
|
133
163
|
create: openaiCompat,
|
|
134
164
|
};
|
|
135
165
|
const OPENCODE_ZEN = {
|
|
@@ -184,16 +214,17 @@ const OPENCODE_GO = {
|
|
|
184
214
|
},
|
|
185
215
|
create: openaiCompat,
|
|
186
216
|
};
|
|
187
|
-
/**
|
|
217
|
+
/** Все builtin-пресеты провайдеров. */
|
|
188
218
|
export const BUILTIN_PROVIDERS = [
|
|
189
219
|
OPENAI_COMPAT,
|
|
220
|
+
LMSTUDIO,
|
|
190
221
|
OPENROUTER,
|
|
191
222
|
OPENAI,
|
|
192
223
|
ANTHROPIC,
|
|
193
224
|
OPENCODE_ZEN,
|
|
194
225
|
OPENCODE_GO,
|
|
195
226
|
];
|
|
196
|
-
/**
|
|
227
|
+
/** Фиксированные base URL хостед-провайдеров (без скан-портов / автодетекта моделей). */
|
|
197
228
|
export const HOSTED_BASE_URLS = {};
|
|
198
229
|
for (const spec of BUILTIN_PROVIDERS) {
|
|
199
230
|
if (spec.baseUrl)
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Единый источник правды type → фабрика + capabilities.
|
|
3
|
+
* Добавление провайдера — один вызов `register()`; петля агента не меняется.
|
|
4
4
|
*/
|
|
5
5
|
export class ProviderRegistry {
|
|
6
6
|
specs = new Map();
|
|
7
|
+
/** Зарегистрировать спек провайдера. */
|
|
7
8
|
register(spec) {
|
|
8
9
|
this.specs.set(spec.type, spec);
|
|
9
10
|
}
|
|
11
|
+
/** Спек по типу, иначе undefined. */
|
|
10
12
|
get(type) {
|
|
11
13
|
return this.specs.get(type);
|
|
12
14
|
}
|
|
15
|
+
/** Зарегистрирован ли тип. */
|
|
13
16
|
has(type) {
|
|
14
17
|
return this.specs.has(type);
|
|
15
18
|
}
|
|
19
|
+
/** Все спеки. */
|
|
16
20
|
list() {
|
|
17
21
|
return [...this.specs.values()];
|
|
18
22
|
}
|
|
23
|
+
/** Все зарегистрированные типы. */
|
|
19
24
|
types() {
|
|
20
25
|
return [...this.specs.keys()];
|
|
21
26
|
}
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
+
/** Начальное состояние политики. */
|
|
1
2
|
export function createPolicyState() {
|
|
2
3
|
return { iterationsSinceRaise: 0, boostRemaining: 0 };
|
|
3
4
|
}
|
|
4
5
|
const DECAY_THRESHOLD = 5;
|
|
6
|
+
/** Вычисляет reasoning-уровень: подъём на сбоях, буст после плана, спад на рутине. */
|
|
5
7
|
export function evaluateReasoningPolicy(input, state) {
|
|
6
|
-
//
|
|
8
|
+
// Сигналы подъёма (высший приоритет)
|
|
7
9
|
if (input.hallucinationRetry || input.auditRejection || input.recoverableLlmError) {
|
|
8
10
|
state.iterationsSinceRaise = 0;
|
|
9
11
|
return { level: "high", reason: input.hallucinationRetry ? "policy:hallucination-retry" : input.auditRejection ? "policy:audit-rejection" : "policy:recoverable-llm-error" };
|
|
10
12
|
}
|
|
11
|
-
//
|
|
13
|
+
// Создан план → буст на N итераций
|
|
12
14
|
if (input.planCreated) {
|
|
13
15
|
state.boostRemaining = 3;
|
|
14
16
|
state.iterationsSinceRaise = 0;
|
|
15
17
|
return { level: "high", reason: "policy:plan-created" };
|
|
16
18
|
}
|
|
17
|
-
//
|
|
19
|
+
// Активный буст от создания плана
|
|
18
20
|
if (state.boostRemaining > 0) {
|
|
19
21
|
state.boostRemaining--;
|
|
20
22
|
state.iterationsSinceRaise = 0;
|
|
21
23
|
return { level: "high", reason: "policy:plan-boost" };
|
|
22
24
|
}
|
|
23
|
-
//
|
|
25
|
+
// Сигналы понижения
|
|
24
26
|
if (input.consecutiveSuccesses >= 2) {
|
|
25
27
|
state.iterationsSinceRaise++;
|
|
26
28
|
return { level: "low", reason: "policy:routine-write" };
|
|
@@ -29,12 +31,12 @@ export function evaluateReasoningPolicy(input, state) {
|
|
|
29
31
|
state.iterationsSinceRaise++;
|
|
30
32
|
return { level: "low", reason: "policy:repetitive-tools" };
|
|
31
33
|
}
|
|
32
|
-
//
|
|
34
|
+
// Затухание: подъём держался слишком долго — возврат к базовому
|
|
33
35
|
if (state.iterationsSinceRaise >= DECAY_THRESHOLD) {
|
|
34
36
|
state.iterationsSinceRaise = 0;
|
|
35
37
|
return { level: input.baseline, reason: "policy:decay" };
|
|
36
38
|
}
|
|
37
|
-
//
|
|
39
|
+
// По умолчанию: базовый уровень
|
|
38
40
|
state.iterationsSinceRaise++;
|
|
39
41
|
return { level: input.baseline, reason: "policy:default" };
|
|
40
42
|
}
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
|
-
import {
|
|
3
|
+
import { globalMmaDir } from "../../config/paths";
|
|
4
|
+
import { readEnv } from "../../config/env";
|
|
5
|
+
import { REASONING_CACHE_TTL_MS as CACHE_TTL_MS } from "../../config/timeouts";
|
|
4
6
|
const PROBE_MESSAGES = [
|
|
5
7
|
{ role: "user", content: "Reply with exactly: ok" },
|
|
6
8
|
];
|
|
7
|
-
/**
|
|
8
|
-
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
9
|
-
/** Disk cache path: ~/.mma/reasoning-cache.json (override with MMA_REASONING_CACHE). */
|
|
9
|
+
/** Путь дискового кэша: ~/.mma/reasoning-cache.json (переопределяется MMA_REASONING_CACHE). */
|
|
10
10
|
function cachePath() {
|
|
11
|
-
return
|
|
11
|
+
return readEnv("REASONING_CACHE") ?? join(globalMmaDir(), "reasoning-cache.json");
|
|
12
12
|
}
|
|
13
|
-
/** In-memory L1
|
|
13
|
+
/** In-memory L1-кэш (быстрый путь в пределах процесса). */
|
|
14
14
|
const memCache = new Map();
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* Probe whether the backend actually respects the reasoning effort mechanism.
|
|
22
|
-
* @returns `true` = mechanism respected, `false` = mechanism ignored,
|
|
23
|
-
* `null` = transient error (caller must NOT cache this).
|
|
16
|
+
* Проверяет, уважает ли бэкенд механизм reasoning effort.
|
|
17
|
+
* Шлёт тривиальный запрос с уровнем "none": если reasoning_content всё равно
|
|
18
|
+
* появился — механизм игнорируется.
|
|
19
|
+
* @returns `true` — механизм уважается, `false` — игнорируется,
|
|
20
|
+
* `null` — транзиентная ошибка (вызывающий НЕ должен кэшировать).
|
|
24
21
|
*/
|
|
25
22
|
export async function probeReasoningSupport(provider, strategy, signal) {
|
|
26
23
|
if (strategy === "none")
|
|
27
24
|
return false;
|
|
25
|
+
// "openai-effort" — стандартное поле OpenAI. Sentinel "none", которым проба
|
|
26
|
+
// измеряет подавление, транспорт вырезает (strict OpenAI на "none" отвечает
|
|
27
|
+
// 400), поэтому измерить нечем — считаем механизм поддерживаемым. Если
|
|
28
|
+
// бэкенд поле игнорирует, это безопасно (effort просто не влияет).
|
|
29
|
+
if (strategy === "openai-effort")
|
|
30
|
+
return true;
|
|
28
31
|
try {
|
|
29
32
|
let hasReasoning = false;
|
|
30
33
|
for await (const chunk of provider.chat(PROBE_MESSAGES, undefined, signal, {
|
|
@@ -35,30 +38,31 @@ export async function probeReasoningSupport(provider, strategy, signal) {
|
|
|
35
38
|
if (chunk.type === "reasoning")
|
|
36
39
|
hasReasoning = true;
|
|
37
40
|
}
|
|
38
|
-
//
|
|
41
|
+
// reasoning_content появился вопреки "none" — механизм игнорируется
|
|
39
42
|
return !hasReasoning;
|
|
40
43
|
}
|
|
41
44
|
catch {
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
+
// Транзиентная ошибка (сеть/авторизация/стрим) — НЕ сообщаем "не поддерживается".
|
|
46
|
+
// Возврат null запрещает вызывающему кэшировать, чтобы разовый сбой
|
|
47
|
+
// не отключал reasoning-механизм на весь 24ч TTL.
|
|
45
48
|
return null;
|
|
46
49
|
}
|
|
47
50
|
}
|
|
51
|
+
/** Ключ кэша пробы: baseUrl + модель. */
|
|
48
52
|
export function cacheKey(baseUrl, model) {
|
|
49
53
|
return `${baseUrl}|${model}`;
|
|
50
54
|
}
|
|
51
55
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
56
|
+
* Читает кэшированный результат пробы. Сначала память, потом диск.
|
|
57
|
+
* Возвращает `undefined` при промахе или протухании (>24ч).
|
|
54
58
|
*/
|
|
55
59
|
export function getCachedProbeResult(key) {
|
|
56
|
-
// L1:
|
|
60
|
+
// L1: память
|
|
57
61
|
const mem = memCache.get(key);
|
|
58
62
|
if (mem && Date.now() - mem.ts < CACHE_TTL_MS) {
|
|
59
63
|
return mem.result;
|
|
60
64
|
}
|
|
61
|
-
// L2:
|
|
65
|
+
// L2: диск
|
|
62
66
|
const disk = readDiskCache();
|
|
63
67
|
const entry = disk[key];
|
|
64
68
|
if (entry) {
|
|
@@ -71,7 +75,7 @@ export function getCachedProbeResult(key) {
|
|
|
71
75
|
return undefined;
|
|
72
76
|
}
|
|
73
77
|
/**
|
|
74
|
-
*
|
|
78
|
+
* Кладет результат пробы в память и на диск.
|
|
75
79
|
*/
|
|
76
80
|
export function setCachedProbeResult(key, result) {
|
|
77
81
|
const now = new Date().toISOString();
|
|
@@ -80,6 +84,7 @@ export function setCachedProbeResult(key, result) {
|
|
|
80
84
|
disk[key] = { result, ts: now };
|
|
81
85
|
writeDiskCache(disk);
|
|
82
86
|
}
|
|
87
|
+
/** Сбрасывает кэш пробы (память + дисковый файл). */
|
|
83
88
|
export function resetProbeCache() {
|
|
84
89
|
memCache.clear();
|
|
85
90
|
const path = cachePath();
|
|
@@ -95,7 +100,7 @@ function readDiskCache() {
|
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
catch {
|
|
98
|
-
//
|
|
103
|
+
// Битый кэш — начинаем с чистого
|
|
99
104
|
}
|
|
100
105
|
return {};
|
|
101
106
|
}
|
|
@@ -106,6 +111,6 @@ function writeDiskCache(data) {
|
|
|
106
111
|
writeFileSync(path, JSON.stringify(data, null, 2), "utf-8");
|
|
107
112
|
}
|
|
108
113
|
catch {
|
|
109
|
-
// Best-effort
|
|
114
|
+
// Best-effort: сбой записи на диск не должен ронять агента
|
|
110
115
|
}
|
|
111
116
|
}
|