micro-models-agent 2.11.0 → 2.13.1
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 +39 -0
- package/dist/assets/app.js +22 -2
- package/dist/assets/index.html +39 -9
- package/dist/assets/js/dom.js +15 -0
- package/dist/assets/js/feed.js +129 -10
- package/dist/assets/js/sidebar.js +512 -0
- package/dist/assets/js/tabs.js +30 -5
- package/dist/assets/setup.js +267 -8
- package/dist/assets/styles.css +168 -3
- package/dist/cli/cache-line.js +1 -0
- package/dist/cli/command-suggest.js +5 -4
- package/dist/cli/commands.js +231 -122
- package/dist/cli/completer.js +132 -31
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-payload.js +4 -4
- package/dist/cli/main.js +166 -61
- package/dist/cli/onboarding-commands.js +126 -0
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/relaunch.js +16 -13
- package/dist/cli/repl-commands-lsp.js +75 -0
- package/dist/cli/repl-commands-skills.js +105 -0
- package/dist/cli/repl-commands.js +589 -266
- package/dist/cli/repl.js +436 -177
- package/dist/cli/run-result.js +5 -6
- package/dist/cli/security-commands.js +37 -28
- package/dist/cli/setup-order.js +4 -55
- package/dist/cli/setup-prompt.js +3 -2
- package/dist/cli/setup.js +27 -107
- package/dist/cli/web-command.js +98 -0
- package/dist/config/budget.js +5 -5
- package/dist/config/config.js +103 -96
- package/dist/config/defaults.js +38 -8
- package/dist/config/domains.js +24 -27
- package/dist/config/env.js +46 -0
- package/dist/config/experts.js +2 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.js +56 -0
- package/dist/config/redact.js +44 -0
- package/dist/config/registry-extra.js +334 -0
- package/dist/config/registry.js +584 -0
- package/dist/config/security.js +35 -36
- package/dist/config/timeouts.js +74 -0
- package/dist/config/utils.js +16 -15
- package/dist/core/agent/artifact-progress.js +59 -0
- package/dist/core/agent/artifact-resolve.js +79 -0
- package/dist/core/agent/audit-gate.js +7 -3
- package/dist/core/agent/compaction.js +2 -2
- package/dist/core/agent/constants.js +33 -4
- package/dist/core/agent/context-log.js +454 -0
- package/dist/core/agent/context-renderer.js +5 -0
- package/dist/core/agent/critic-gate.js +132 -0
- package/dist/core/agent/hallucination-gate.js +148 -38
- package/dist/core/agent/loop-state.js +15 -0
- package/dist/core/agent/prefix-monitor.js +1 -1
- package/dist/core/agent/reasoning-resolver.js +10 -1
- package/dist/core/agent/token-tracker.js +7 -4
- package/dist/core/agent/tool-batch.js +93 -18
- package/dist/core/agent/tool-images.js +18 -0
- package/dist/core/agent/tool-output.js +65 -11
- package/dist/core/agent/tool-pairing.js +60 -0
- package/dist/core/agent-moe.js +25 -24
- package/dist/core/agent.js +429 -108
- package/dist/core/bootstrap/agent-phase.js +66 -0
- package/dist/core/bootstrap/config-phase.js +86 -0
- package/dist/core/bootstrap/modules-phase.js +269 -0
- package/dist/core/bootstrap/provider-phase.js +183 -0
- package/dist/core/bootstrap/session-phase.js +204 -0
- package/dist/core/bootstrap/state.js +94 -0
- package/dist/core/bootstrap/workspace-phase.js +115 -0
- package/dist/core/bootstrap.js +92 -660
- package/dist/core/crash-handler.js +17 -7
- package/dist/core/environment.js +37 -12
- package/dist/core/host-plan.js +43 -0
- package/dist/core/host.js +23 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompt-builder.js +17 -1
- package/dist/core/prompt-overflow.js +42 -43
- package/dist/core/session-logger.js +122 -30
- package/dist/core/system-prompt.js +117 -0
- package/dist/core/version.js +6 -4
- package/dist/core/web-host.js +34 -0
- package/dist/core/workspace.js +19 -15
- package/dist/i18n/en.json +89 -3
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +89 -3
- package/dist/index.js +7 -3
- package/dist/llm/image-utils.js +55 -64
- package/dist/llm/llm-errors.js +9 -0
- package/dist/llm/model-loader.js +10 -32
- package/dist/llm/models-dev.js +121 -0
- package/dist/llm/openai-compat.js +146 -73
- package/dist/llm/orchestrator.js +33 -11
- package/dist/llm/provider-budget.js +2 -2
- package/dist/llm/provider-context.js +233 -0
- package/dist/llm/provider.js +1 -1
- package/dist/llm/response.js +5 -0
- package/dist/llm/token-counter.js +27 -11
- package/dist/logger/app-logger.js +48 -22
- package/dist/logger/file-log.js +28 -19
- package/dist/logger/index.js +1 -0
- package/dist/main.js +888 -139
- 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/llm/orchestrator.js
CHANGED
|
@@ -85,8 +85,9 @@ Respond with JSON only (no markdown fences):
|
|
|
85
85
|
or
|
|
86
86
|
{"action": "reject", "explanation": "why"}`;
|
|
87
87
|
const DEFAULT_BASE_URL = "http://localhost:1234/v1";
|
|
88
|
-
/**
|
|
88
|
+
/** Запасное окно контекста, если ни запись провайдера, ни конфиг его не задают. */
|
|
89
89
|
const DEFAULT_ORCH_CONTEXT_WINDOW = 32768;
|
|
90
|
+
/** Клиент оркестратора: строит план, сливает результаты, роутит scope-запросы. */
|
|
90
91
|
export class OrchestratorClient {
|
|
91
92
|
config;
|
|
92
93
|
provider = null;
|
|
@@ -110,11 +111,10 @@ export class OrchestratorClient {
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
/**
|
|
113
|
-
*
|
|
114
|
-
* contextWindow/retry/rateLimits
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* and no retry/rate-limit/failover support at all.
|
|
114
|
+
* Строит провайдер оркестратора через ProviderManager: учитываются
|
|
115
|
+
* per-entry contextWindow/retry/rateLimits и failover-записи (легаси
|
|
116
|
+
* {type,baseUrl,apiKey} менеджер нормализует). Раньше провайдер строился
|
|
117
|
+
* напрямую с хардкодом окна 32768 — без ретраев/лимитов/failover вовсе.
|
|
118
118
|
*/
|
|
119
119
|
buildManagedProvider(config) {
|
|
120
120
|
const providerCfg = config.provider;
|
|
@@ -134,6 +134,10 @@ export class OrchestratorClient {
|
|
|
134
134
|
}
|
|
135
135
|
return provider;
|
|
136
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Включён ли оркестратор (задана ли модель).
|
|
139
|
+
* @returns true, если провайдер построен.
|
|
140
|
+
*/
|
|
137
141
|
isEnabled() {
|
|
138
142
|
return this.provider !== null;
|
|
139
143
|
}
|
|
@@ -171,6 +175,11 @@ export class OrchestratorClient {
|
|
|
171
175
|
}
|
|
172
176
|
}
|
|
173
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Строит MoE-план из задачи пользователя.
|
|
180
|
+
* @param userPrompt задача; @param _context неиспользуемый контекст;
|
|
181
|
+
* @returns план с сырым ответом либо ошибку.
|
|
182
|
+
*/
|
|
174
183
|
async plan(userPrompt, _context, signal) {
|
|
175
184
|
if (!this.provider)
|
|
176
185
|
return { error: "Orchestrator not enabled — no orchestrator model configured" };
|
|
@@ -180,11 +189,16 @@ export class OrchestratorClient {
|
|
|
180
189
|
];
|
|
181
190
|
const text = await this.chat(messages, signal);
|
|
182
191
|
const parsed = this.parseJSON(text);
|
|
183
|
-
if (parsed
|
|
192
|
+
if (parsed?.subtasks && parsed.subtasks.length > 0) {
|
|
184
193
|
return { plan: parsed, raw: text };
|
|
185
194
|
}
|
|
186
195
|
return { error: `Failed to parse plan from LLM output. Raw: ${text.slice(0, 500)}` };
|
|
187
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Сливает результаты сабтасков: финал либо запрос re-plan.
|
|
199
|
+
* @param input план, результаты, ошибки/варнинги верификатора;
|
|
200
|
+
* @returns финал с ответом либо replan с обновлённым планом.
|
|
201
|
+
*/
|
|
188
202
|
async verifyAndMerge(input, signal) {
|
|
189
203
|
if (!this.provider)
|
|
190
204
|
return { type: "final", finalAnswer: input.results.map((r) => r.summary).join("\n") };
|
|
@@ -195,7 +209,7 @@ export class OrchestratorClient {
|
|
|
195
209
|
];
|
|
196
210
|
const text = await this.chat(messages, signal);
|
|
197
211
|
const parsed = this.parseJSON(text);
|
|
198
|
-
if (parsed
|
|
212
|
+
if (parsed?.type) {
|
|
199
213
|
return parsed;
|
|
200
214
|
}
|
|
201
215
|
return {
|
|
@@ -205,8 +219,8 @@ export class OrchestratorClient {
|
|
|
205
219
|
};
|
|
206
220
|
}
|
|
207
221
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
222
|
+
* Решение роутера по scope-запросу субагента. Дефолт на любой ошибке
|
|
223
|
+
* парсинга — reject: scope молча не расширяется никогда.
|
|
210
224
|
*/
|
|
211
225
|
async resolveScopeRequest(subtaskId, request, signal) {
|
|
212
226
|
if (!this.provider)
|
|
@@ -223,7 +237,7 @@ export class OrchestratorClient {
|
|
|
223
237
|
if (parsed && parsed.action === "approve") {
|
|
224
238
|
const write = Array.isArray(parsed.write) ? parsed.write.map(String) : [];
|
|
225
239
|
const read = Array.isArray(parsed.read) ? parsed.read.map(String) : [];
|
|
226
|
-
//
|
|
240
|
+
// Approve без конкретных файлов бессмыслен — считаем reject.
|
|
227
241
|
if (write.length === 0 && read.length === 0) {
|
|
228
242
|
return { action: "reject", explanation: "approve carried no files" };
|
|
229
243
|
}
|
|
@@ -236,6 +250,10 @@ export class OrchestratorClient {
|
|
|
236
250
|
: `unparseable router decision: ${text.slice(0, 200)}`,
|
|
237
251
|
};
|
|
238
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Строит простой план-список шагов (легаси-поверхность).
|
|
255
|
+
* @param task задача; @returns шаги плана (фолбэк — сама задача).
|
|
256
|
+
*/
|
|
239
257
|
async createPlan(task) {
|
|
240
258
|
if (!this.provider)
|
|
241
259
|
throw new Error("Orchestrator not enabled");
|
|
@@ -252,6 +270,10 @@ export class OrchestratorClient {
|
|
|
252
270
|
return parsed;
|
|
253
271
|
return { steps: [task] };
|
|
254
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Рекомендует путь разрешения конфликта.
|
|
275
|
+
* @param context описание ситуации; @returns рекомендация текстом.
|
|
276
|
+
*/
|
|
255
277
|
async resolveConflict(context) {
|
|
256
278
|
if (!this.provider)
|
|
257
279
|
throw new Error("Orchestrator not enabled");
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/** Баланс провайдера: запрос расхода/остатка (только OpenRouter по API). */
|
|
2
2
|
/**
|
|
3
3
|
* Запрос баланса/расхода у провайдера. Поддерживается только OpenRouter —
|
|
4
4
|
* единственный, кто отдаёт эти данные по API-ключу. Zen/Go баланс не
|
|
5
5
|
* возвращают (только веб-дашборд), поэтому для них честно `unsupported`.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
import { PROVIDER_BUDGET_TIMEOUT_MS as DEFAULT_TIMEOUT_MS } from "../config/timeouts";
|
|
8
8
|
/** Приводит значение к конечному числу; иначе undefined. */
|
|
9
9
|
function num(value) {
|
|
10
10
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { lookupModelsDevContext } from "./models-dev";
|
|
2
|
+
/**
|
|
3
|
+
* Спрашивает нативный API бэкенда (LM Studio `/api/v1/models`) о длине
|
|
4
|
+
* контекста загруженной модели И заявленных capabilities. Длина — из живого
|
|
5
|
+
* инстанса (`loaded_instances[].config.context_length`), фолбэк —
|
|
6
|
+
* `max_context_length`. Capabilities читаются из `capabilities` (`vision`,
|
|
7
|
+
* `trained_for_tool_use`), если есть — это единый источник правды вместо
|
|
8
|
+
* гадания по имени.
|
|
9
|
+
*
|
|
10
|
+
* Null, если эндпоинт недоступен (не LM Studio), модели нет/снята или запрос
|
|
11
|
+
* упал — вызывающий деградирует молча.
|
|
12
|
+
*/
|
|
13
|
+
export async function probeModel(baseUrl, model, timeoutMs = 3000) {
|
|
14
|
+
const base = baseUrl.replace(/\/$/, "").replace(/\/v1\/?$/, "");
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(`${base}/api/v1/models`, {
|
|
17
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok)
|
|
20
|
+
return null;
|
|
21
|
+
const data = (await response.json());
|
|
22
|
+
const models = data.models ?? [];
|
|
23
|
+
const found = models.find((m) => m.key === model || m.name === model || (m.key ? model.includes(m.key) : false));
|
|
24
|
+
if (!found)
|
|
25
|
+
return null;
|
|
26
|
+
const loadedInstance = (found.loaded_instances ?? [])[0];
|
|
27
|
+
const contextLength = loadedInstance?.config?.context_length ?? found.max_context_length;
|
|
28
|
+
if (typeof contextLength !== "number" || contextLength <= 0)
|
|
29
|
+
return null;
|
|
30
|
+
const raw = found.capabilities;
|
|
31
|
+
let capabilities;
|
|
32
|
+
if (raw && typeof raw.vision === "boolean") {
|
|
33
|
+
capabilities = { vision: raw.vision };
|
|
34
|
+
if (typeof raw.trained_for_tool_use === "boolean") {
|
|
35
|
+
capabilities.tools = raw.trained_for_tool_use;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { model: found.key ?? found.name ?? model, contextLength, capabilities };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Тонкая context-only проекция {@link probeModel}. Для вызывающих, кому нужно
|
|
46
|
+
* только окно контекста (баннер, диагностика context-probe).
|
|
47
|
+
*/
|
|
48
|
+
export async function getLoadedContextLength(baseUrl, model, timeoutMs = 3000) {
|
|
49
|
+
const probe = await probeModel(baseUrl, model, timeoutMs);
|
|
50
|
+
return probe ? { model: probe.model, actual: probe.contextLength } : null;
|
|
51
|
+
}
|
|
52
|
+
/** Числовые context-подобные поля, что может нести запись `/models`. */
|
|
53
|
+
const CONTEXT_KEYS = [
|
|
54
|
+
"context_length",
|
|
55
|
+
"context_window",
|
|
56
|
+
"max_context_length",
|
|
57
|
+
"max_context_window",
|
|
58
|
+
"max_model_len",
|
|
59
|
+
"maxModelLen",
|
|
60
|
+
"n_ctx",
|
|
61
|
+
"num_ctx",
|
|
62
|
+
"contextLength",
|
|
63
|
+
"contextWindow",
|
|
64
|
+
];
|
|
65
|
+
function asPositiveInt(v) {
|
|
66
|
+
const n = typeof v === "string" && v.trim() !== "" ? Number(v) : v;
|
|
67
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Первое положительное среди известных context-полей объекта. OpenRouter
|
|
71
|
+
* кладёт per-endpoint значение в `top_provider.context_length`, поэтому один
|
|
72
|
+
* уровень вложенности тоже покрыт.
|
|
73
|
+
*/
|
|
74
|
+
function pickContextField(obj) {
|
|
75
|
+
for (const k of CONTEXT_KEYS) {
|
|
76
|
+
const n = asPositiveInt(obj[k]);
|
|
77
|
+
if (n !== null)
|
|
78
|
+
return n;
|
|
79
|
+
}
|
|
80
|
+
const top = obj.top_provider ?? obj.topProvider;
|
|
81
|
+
if (top && typeof top === "object") {
|
|
82
|
+
for (const k of CONTEXT_KEYS) {
|
|
83
|
+
const n = asPositiveInt(top[k]);
|
|
84
|
+
if (n !== null)
|
|
85
|
+
return n;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function matchesModel(entry, model) {
|
|
91
|
+
for (const k of ["id", "name", "model", "key"]) {
|
|
92
|
+
const v = entry[k];
|
|
93
|
+
if (typeof v === "string" && v && (v === model || model.includes(v) || v.includes(model))) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
async function fetchJson(url, timeoutMs, init) {
|
|
100
|
+
try {
|
|
101
|
+
const response = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
102
|
+
if (!response.ok)
|
|
103
|
+
return null;
|
|
104
|
+
return (await response.json());
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Context-поля живого инстанса из Ollama `/api/ps`, если есть. */
|
|
111
|
+
async function probeOllamaPs(origin, model, timeoutMs) {
|
|
112
|
+
const ps = await fetchJson(`${origin}/api/ps`, timeoutMs);
|
|
113
|
+
const running = ps?.models;
|
|
114
|
+
if (!Array.isArray(running))
|
|
115
|
+
return null;
|
|
116
|
+
const found = running.find((m) => m && typeof m === "object" && matchesModel(m, model));
|
|
117
|
+
if (!found)
|
|
118
|
+
return null;
|
|
119
|
+
return pickContextField(found);
|
|
120
|
+
}
|
|
121
|
+
function contextFromModelInfo(bag) {
|
|
122
|
+
for (const [k, v] of Object.entries(bag)) {
|
|
123
|
+
if (k.toLowerCase().includes("context_length") || k.toLowerCase().endsWith("n_ctx")) {
|
|
124
|
+
const n = asPositiveInt(v);
|
|
125
|
+
if (n !== null)
|
|
126
|
+
return n;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return pickContextField(bag);
|
|
130
|
+
}
|
|
131
|
+
/** Ollama `/api/show`: детали модели, пробуем полное и короткое имя. */
|
|
132
|
+
async function probeOllamaShow(origin, model, timeoutMs) {
|
|
133
|
+
const short = model.split(":")[0];
|
|
134
|
+
for (const name of [model, short]) {
|
|
135
|
+
const show = await fetchJson(`${origin}/api/show`, timeoutMs, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "Content-Type": "application/json" },
|
|
138
|
+
body: JSON.stringify({ name }),
|
|
139
|
+
});
|
|
140
|
+
const info = show;
|
|
141
|
+
const bag = info?.model_info ?? info?.modelInfo;
|
|
142
|
+
if (bag && typeof bag === "object") {
|
|
143
|
+
const n = contextFromModelInfo(bag);
|
|
144
|
+
if (n !== null)
|
|
145
|
+
return n;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/** Нативный Ollama: живые инстансы (`/api/ps`) + детали (`/api/show`). */
|
|
151
|
+
async function probeOllama(origin, model, timeoutMs) {
|
|
152
|
+
// `/api/show` сообщает контекст модели (напр. `llama.context_length` в
|
|
153
|
+
// `model_info`), даже когда `/api/ps` размерных полей не несёт.
|
|
154
|
+
return ((await probeOllamaPs(origin, model, timeoutMs)) ??
|
|
155
|
+
(await probeOllamaShow(origin, model, timeoutMs)));
|
|
156
|
+
}
|
|
157
|
+
/** Сервер llama.cpp: одномодельный `/props` несёт загруженный `n_ctx`. */
|
|
158
|
+
async function probeLlamaCpp(origin, timeoutMs) {
|
|
159
|
+
const props = await fetchJson(`${origin}/props`, timeoutMs);
|
|
160
|
+
if (!props || typeof props !== "object")
|
|
161
|
+
return null;
|
|
162
|
+
const bag = props;
|
|
163
|
+
const direct = pickContextField(bag);
|
|
164
|
+
if (direct !== null)
|
|
165
|
+
return direct;
|
|
166
|
+
const gen = bag.default_generation_settings;
|
|
167
|
+
if (gen && typeof gen === "object") {
|
|
168
|
+
return pickContextField(gen);
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* OpenAI-совместимый `/models` с расширенными метаданными. Серверы с голыми id
|
|
174
|
+
* (чистый OpenAI, Zen/Go) context-полей не несут → null.
|
|
175
|
+
*/
|
|
176
|
+
async function probeOpenAICompatModels(baseUrl, model, apiKey, timeoutMs) {
|
|
177
|
+
const url = `${baseUrl.replace(/\/+$/, "")}/models`;
|
|
178
|
+
const headers = { "Content-Type": "application/json" };
|
|
179
|
+
if (apiKey && apiKey !== "not-needed")
|
|
180
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
181
|
+
const data = await fetchJson(url, timeoutMs, { headers });
|
|
182
|
+
const list = data?.data;
|
|
183
|
+
if (!Array.isArray(list))
|
|
184
|
+
return null;
|
|
185
|
+
const found = list.find((m) => !!m && typeof m === "object" && matchesModel(m, model));
|
|
186
|
+
if (!found)
|
|
187
|
+
return null;
|
|
188
|
+
return pickContextField(found);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Лукап каталога models.dev — последний шанс для OpenCode Zen/Go. Их `/models`
|
|
192
|
+
* отдаёт голые id, а models.dev (открытая БД, что использует сам opencode)
|
|
193
|
+
* держит модели провайдера `opencode` под теми же id с `limit.context`.
|
|
194
|
+
* Гейт на Zen-URL/тип: локальные бэкенды ради этого в сеть не ходят.
|
|
195
|
+
*/
|
|
196
|
+
async function probeModelsDev(baseUrl, model, providerType) {
|
|
197
|
+
const isZen = providerType === "opencode-zen" ||
|
|
198
|
+
providerType === "opencode-go" ||
|
|
199
|
+
/opencode\.ai\/zen/.test(baseUrl);
|
|
200
|
+
if (!isZen)
|
|
201
|
+
return null;
|
|
202
|
+
return lookupModelsDevContext(model);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Пробует все источники по порядку, возвращает первый давший окно.
|
|
206
|
+
* @param baseUrl базовый URL провайдера; @param model имя модели;
|
|
207
|
+
* @param opts ключ/таймаут/тип; @returns окно контекста либо null (fail-open).
|
|
208
|
+
*/
|
|
209
|
+
export async function probeProviderContext(baseUrl, model, opts = {}) {
|
|
210
|
+
const timeoutMs = opts.timeoutMs ?? 3000;
|
|
211
|
+
const origin = baseUrl.replace(/\/$/, "").replace(/\/v1\/?$/, "");
|
|
212
|
+
try {
|
|
213
|
+
const lm = await probeModel(baseUrl, model, timeoutMs);
|
|
214
|
+
if (lm)
|
|
215
|
+
return { model: lm.model, actual: lm.contextLength };
|
|
216
|
+
const ollama = await probeOllama(origin, model, timeoutMs);
|
|
217
|
+
if (ollama !== null)
|
|
218
|
+
return { model, actual: ollama };
|
|
219
|
+
const llama = await probeLlamaCpp(origin, timeoutMs);
|
|
220
|
+
if (llama !== null)
|
|
221
|
+
return { model, actual: llama };
|
|
222
|
+
const compat = await probeOpenAICompatModels(baseUrl, model, opts.apiKey, timeoutMs);
|
|
223
|
+
if (compat !== null)
|
|
224
|
+
return { model, actual: compat };
|
|
225
|
+
const catalog = await probeModelsDev(baseUrl, model, opts.providerType);
|
|
226
|
+
if (catalog !== null)
|
|
227
|
+
return { model, actual: catalog };
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Fail-open: любая ошибка пробы оставляет настроенный contextWindow.
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
package/dist/llm/provider.js
CHANGED
package/dist/llm/response.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Сворачивает чанки стрима в ответ (текст склеивается, reasoning копится,
|
|
3
|
+
* аргументы тулов JSON-парсятся с фолбэком {}).
|
|
4
|
+
* @param chunks чанки стрима; @returns распарсенный ответ.
|
|
5
|
+
*/
|
|
1
6
|
export function parseChunks(chunks) {
|
|
2
7
|
let text = "";
|
|
3
8
|
let reasoning;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/** Подсчёт токенов: точный (tiktoken) и дешёвая эвристика для блоков. */
|
|
2
2
|
import { encodingForModel, getEncoding } from "js-tiktoken";
|
|
3
3
|
import { getMessageText } from "./provider";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* tiktoken for Russian text.
|
|
5
|
+
* Скриптозависимая ОЦЕНКА токенов (дешёвая, без полной токенизации). Достаточно
|
|
6
|
+
* дешёвая для каждого PromptBlock; на этапе сборки точного TokenCounter может
|
|
7
|
+
* не быть в scope. Замерено на cl100k_base: латиница И кириллица — ~3.3–5
|
|
8
|
+
* символов/токен (старый плоский len/4 для кириллицы был почти верен —
|
|
9
|
+
* гипотеза «кириллица ≈ /2» ошибочна); CJK/хангыль — выбросы ~1–1.5 токена/
|
|
10
|
+
* символ, им /1.5. В пределах ±30% от tiktoken на русском.
|
|
12
11
|
*/
|
|
13
12
|
export function estimateTokens(text) {
|
|
14
13
|
if (!text)
|
|
@@ -29,6 +28,7 @@ export function estimateTokens(text) {
|
|
|
29
28
|
}
|
|
30
29
|
return Math.ceil(cjk / 1.5 + rest / 4);
|
|
31
30
|
}
|
|
31
|
+
/** Точный счётчик токенов поверх tiktoken (энкодинг по модели). */
|
|
32
32
|
export class TokenCounter {
|
|
33
33
|
encoder;
|
|
34
34
|
constructor(model = "gpt-4o") {
|
|
@@ -36,31 +36,47 @@ export class TokenCounter {
|
|
|
36
36
|
this.encoder = encodingForModel(model);
|
|
37
37
|
}
|
|
38
38
|
catch {
|
|
39
|
-
//
|
|
39
|
+
// Фолбэк на cl100k_base для неизвестных моделей
|
|
40
40
|
this.encoder = getEncoding("cl100k_base");
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Считает токены текста.
|
|
45
|
+
* @param text текст; @returns число токенов.
|
|
46
|
+
*/
|
|
43
47
|
count(text) {
|
|
44
48
|
if (!text)
|
|
45
49
|
return 0;
|
|
46
50
|
return this.encoder.encode(text).length;
|
|
47
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Считает токены сообщений с оверхедом ролей/границ (~4/сообщение).
|
|
54
|
+
* @param messages сообщения чата; @returns суммарные токены.
|
|
55
|
+
*/
|
|
48
56
|
countMessages(messages) {
|
|
49
57
|
let total = 0;
|
|
50
58
|
for (const msg of messages) {
|
|
51
|
-
// ~4
|
|
59
|
+
// ~4 токена оверхеда на сообщение (роль, границы)
|
|
52
60
|
total += 4;
|
|
53
61
|
const text = getMessageText(msg.content);
|
|
54
62
|
total += this.count(text);
|
|
55
63
|
if (msg.role === "tool")
|
|
56
64
|
total += 2; // tool_call_id
|
|
57
65
|
}
|
|
58
|
-
total += 2; //
|
|
66
|
+
total += 2; // прайминг ассистента
|
|
59
67
|
return total;
|
|
60
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Кодирует текст в токены.
|
|
71
|
+
* @param text текст; @returns массив id токенов.
|
|
72
|
+
*/
|
|
61
73
|
encode(text) {
|
|
62
74
|
return this.encoder.encode(text);
|
|
63
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Декодирует токены в текст.
|
|
78
|
+
* @param tokens id токенов; @returns текст.
|
|
79
|
+
*/
|
|
64
80
|
decode(tokens) {
|
|
65
81
|
return this.encoder.decode(tokens);
|
|
66
82
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
// Логер: шина вывода + файловые логи (.log, app.jsonl), чистка секретов.
|
|
1
2
|
import { appendFileSync, mkdirSync, existsSync } from "fs";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { sanitizeLogMessage } from "../modules/security/data-sanitizer";
|
|
4
5
|
import { FileLogWriter } from "./file-log";
|
|
5
6
|
import { defaultOutputBus } from "../output";
|
|
6
7
|
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
8
|
+
/** Центральный логер: консоль через шину, файлы, JSONL, тегированные хелперы. */
|
|
7
9
|
export class Logger {
|
|
8
10
|
level;
|
|
9
11
|
prefix;
|
|
@@ -36,36 +38,47 @@ export class Logger {
|
|
|
36
38
|
clearSessionDir() {
|
|
37
39
|
this.sessionDir = null;
|
|
38
40
|
}
|
|
39
|
-
/**
|
|
41
|
+
/** Открывает посессионный `.log`-файл (поведение legacy v1). */
|
|
40
42
|
initSessionLog(sessionId) {
|
|
41
43
|
this.fileLog.initSessionLog(sessionId);
|
|
42
44
|
}
|
|
43
|
-
/**
|
|
45
|
+
/** Возврат к дневным лог-файлам, когда нет активной сессии. */
|
|
44
46
|
closeSessionLog() {
|
|
45
47
|
this.fileLog.closeSessionLog();
|
|
46
48
|
}
|
|
49
|
+
/** Путь к текущему лог-файлу (сессионный или дневной). */
|
|
47
50
|
getLogPath() {
|
|
48
51
|
return this.fileLog.getLogPath();
|
|
49
52
|
}
|
|
53
|
+
/** Удаляет старые логи по возрасту/количеству. */
|
|
50
54
|
cleanupOldLogs(maxDays, maxFiles) {
|
|
51
55
|
this.fileLog.cleanupOldLogs(maxDays, maxFiles);
|
|
52
56
|
}
|
|
53
|
-
//
|
|
57
|
+
// Тегированные хелперы (API legacy v1).
|
|
58
|
+
/** Пишет запрос к LLM в файловый лог. */
|
|
54
59
|
logLLMRequest(model, messagesCount, promptPreview, caller) {
|
|
55
60
|
this.fileLog.logLLMRequest(model, messagesCount, sanitizeLogMessage(promptPreview), caller);
|
|
56
61
|
}
|
|
62
|
+
/** Пишет ответ LLM (или ошибку) в файловый лог. */
|
|
57
63
|
logLLMResponse(model, responseLength, genTimeMs, error, caller) {
|
|
58
64
|
this.fileLog.logLLMResponse(model, responseLength, genTimeMs, error, caller);
|
|
59
65
|
}
|
|
66
|
+
/** Пишет вызов тула и превью результата в файловый лог. */
|
|
60
67
|
logToolCall(tool, preview, result) {
|
|
61
68
|
this.fileLog.logToolCall(tool, sanitizeLogMessage(preview), result !== undefined ? sanitizeLogMessage(result) : undefined);
|
|
62
69
|
}
|
|
70
|
+
/** Пишет вывод тула с кодом выхода в файловый лог. */
|
|
63
71
|
logToolOutput(tool, output, exitCode) {
|
|
64
72
|
this.fileLog.logToolOutput(tool, sanitizeLogMessage(output), exitCode);
|
|
65
73
|
}
|
|
74
|
+
/** Пишет реплику REPL (user/assistant/system) в файловый лог. */
|
|
66
75
|
logREPL(tag, content) {
|
|
67
76
|
this.fileLog.logREPL(tag, sanitizeLogMessage(content));
|
|
68
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Дочерний логер с расширенным префиксом; наследует уровень, sink и каталоги.
|
|
80
|
+
* @param prefix суффикс префикса. @returns новый Logger.
|
|
81
|
+
*/
|
|
69
82
|
child(prefix) {
|
|
70
83
|
const childLogger = new Logger(this.level, this.prefix ? `${this.prefix}:${prefix}` : prefix, this.sink);
|
|
71
84
|
if (this.logDir)
|
|
@@ -86,42 +99,55 @@ export class Logger {
|
|
|
86
99
|
error(msg, meta) {
|
|
87
100
|
this.log("error", msg, meta);
|
|
88
101
|
}
|
|
89
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Внутренняя диагностика петли (ретраи галлюцинаций, аудит/компакция,
|
|
104
|
+
* вердикты критика, …). Персистентность та же, что у warn/info, но событие
|
|
105
|
+
* шины несёт `data.diagnostic = true`, и чат дропает его, пока
|
|
106
|
+
* `ui.verbosity !== "verbose"`.
|
|
107
|
+
*/
|
|
108
|
+
diagnostic(level, msg, meta) {
|
|
109
|
+
this.log(level, msg, meta, true);
|
|
110
|
+
}
|
|
111
|
+
log(level, msg, meta, diagnostic) {
|
|
90
112
|
if (LEVELS[level] < LEVELS[this.level])
|
|
91
113
|
return;
|
|
92
|
-
//
|
|
114
|
+
// Чистим сообщение — вырезаем секреты.
|
|
93
115
|
const sanitizedMsg = sanitizeLogMessage(msg);
|
|
94
116
|
const sanitizedMeta = meta ? this.sanitizeMeta(meta) : undefined;
|
|
95
117
|
const ts = new Date().toISOString();
|
|
96
118
|
const metaStr = sanitizedMeta ? ` ${JSON.stringify(sanitizedMeta)}` : "";
|
|
97
119
|
const line = `${sanitizedMsg}${metaStr}`;
|
|
98
120
|
try {
|
|
99
|
-
this.sink.emit({
|
|
121
|
+
this.sink.emit({
|
|
122
|
+
level,
|
|
123
|
+
source: this.prefix || "mma",
|
|
124
|
+
kind: "log",
|
|
125
|
+
text: line,
|
|
126
|
+
data: diagnostic ? { diagnostic: true } : undefined,
|
|
127
|
+
});
|
|
100
128
|
}
|
|
101
129
|
catch {
|
|
102
130
|
/* terminal output is best-effort; file logging below must still run */
|
|
103
131
|
}
|
|
104
|
-
//
|
|
132
|
+
// Поведение legacy v1: дубль в тегированный `.log` (сессионный или дневной).
|
|
105
133
|
this.fileLog.log(level.toUpperCase(), this.prefix || "MMA", `${ts} — ${sanitizedMsg}${metaStr}`);
|
|
106
134
|
const logTarget = this.sessionDir ?? this.logDir;
|
|
107
135
|
if (logTarget) {
|
|
108
136
|
try {
|
|
109
|
-
appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
|
|
137
|
+
appendFileSync(join(logTarget, "app.jsonl"), `${JSON.stringify({
|
|
110
138
|
level,
|
|
111
139
|
ts,
|
|
112
140
|
prefix: this.prefix,
|
|
113
141
|
msg: sanitizedMsg,
|
|
114
142
|
meta: sanitizedMeta ?? null,
|
|
115
|
-
})
|
|
143
|
+
})}\n`, "utf-8");
|
|
116
144
|
}
|
|
117
145
|
catch {
|
|
118
146
|
/* file logging is best-effort */
|
|
119
147
|
}
|
|
120
148
|
}
|
|
121
149
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Sanitize metadata object to remove sensitive data
|
|
124
|
-
*/
|
|
150
|
+
/** Рекурсивно чистит секреты в объекте метаданных. */
|
|
125
151
|
sanitizeMeta(meta) {
|
|
126
152
|
const sanitized = {};
|
|
127
153
|
for (const [key, value] of Object.entries(meta)) {
|
|
@@ -138,30 +164,30 @@ export class Logger {
|
|
|
138
164
|
return sanitized;
|
|
139
165
|
}
|
|
140
166
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
167
|
+
* Структурная запись только в app.jsonl (без консоли и .log).
|
|
168
|
+
* Для машиночитаемых отчётов (окружение и т.п.), слишком шумных для терминала,
|
|
169
|
+
* но ценных для кросс-девайс диагностики.
|
|
144
170
|
*/
|
|
145
171
|
logStructured(type, data) {
|
|
146
172
|
const logTarget = this.sessionDir ?? this.logDir;
|
|
147
173
|
if (!logTarget)
|
|
148
174
|
return;
|
|
149
175
|
try {
|
|
150
|
-
appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
|
|
176
|
+
appendFileSync(join(logTarget, "app.jsonl"), `${JSON.stringify({
|
|
151
177
|
level: "info",
|
|
152
178
|
ts: new Date().toISOString(),
|
|
153
179
|
type,
|
|
154
180
|
meta: this.sanitizeMeta(data),
|
|
155
|
-
})
|
|
181
|
+
})}\n`, "utf-8");
|
|
156
182
|
}
|
|
157
183
|
catch {
|
|
158
184
|
/* file logging is best-effort */
|
|
159
185
|
}
|
|
160
186
|
}
|
|
161
187
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
188
|
+
* Запись только в app.jsonl — без консоли и .log. Для шумных подсистем
|
|
189
|
+
* (LSP, пробники): предупреждения диагностируются по файлам сессии,
|
|
190
|
+
* но не мусорят в терминале.
|
|
165
191
|
*/
|
|
166
192
|
logSilent(level, msg, meta) {
|
|
167
193
|
const logTarget = this.sessionDir ?? this.logDir;
|
|
@@ -171,13 +197,13 @@ export class Logger {
|
|
|
171
197
|
const sanitizedMsg = sanitizeLogMessage(msg);
|
|
172
198
|
const sanitizedMeta = meta ? this.sanitizeMeta(meta) : undefined;
|
|
173
199
|
try {
|
|
174
|
-
appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
|
|
200
|
+
appendFileSync(join(logTarget, "app.jsonl"), `${JSON.stringify({
|
|
175
201
|
level,
|
|
176
202
|
ts,
|
|
177
203
|
prefix: this.prefix,
|
|
178
204
|
msg: sanitizedMsg,
|
|
179
205
|
meta: sanitizedMeta ?? null,
|
|
180
|
-
})
|
|
206
|
+
})}\n`, "utf-8");
|
|
181
207
|
}
|
|
182
208
|
catch {
|
|
183
209
|
/* file logging is best-effort */
|