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/cli/repl.js
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
|
+
// Интерактивный REPL: ввод, прогоны агента, slash-команды, баннер, история.
|
|
1
2
|
import * as readline from "readline";
|
|
2
3
|
import { pc } from "../ui/colors";
|
|
3
4
|
import { LineEditor } from "../ui/line-editor";
|
|
4
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
6
|
import { dirname, join } from "path";
|
|
6
7
|
import { homedir } from "os";
|
|
7
|
-
import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, ProviderArgProvider, ModelArgProvider, } from "./completer";
|
|
8
|
+
import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, ProviderArgProvider, ModelArgProvider, FilePathProvider, ConfigKeyProvider, ConfigValueProvider, FlagProvider, CommandArgProvider, buildSlashCompletions, } from "./completer";
|
|
8
9
|
import { Renderer } from "../ui/renderer";
|
|
10
|
+
import { resolveVerbosity, glyph, toolAccent } from "../ui/theme";
|
|
11
|
+
import { getLastAction } from "../ui/action-log";
|
|
9
12
|
import { divider } from "../ui/box";
|
|
10
13
|
import { t } from "../i18n/index";
|
|
14
|
+
import { createHostBridge } from "../core/host";
|
|
15
|
+
import { planSnapshotFromModule, adaptActivePlan } from "../core/host-plan";
|
|
11
16
|
import { registerAllCommands } from "./repl-commands";
|
|
17
|
+
import { captureConfigSnapshot } from "../config/registry";
|
|
18
|
+
import { activeProviderEndpoint } from "../modules/providers/manager";
|
|
12
19
|
import { probeLspServers } from "../modules/lsp/probe";
|
|
13
20
|
import { FileOnlyLogger } from "../modules/lsp/client";
|
|
14
21
|
import { DEFAULT_LSP_CONFIG } from "../modules/lsp/config";
|
|
15
22
|
import { errMsg } from "../utils";
|
|
16
23
|
import { SessionLogger } from "../core/session-logger";
|
|
24
|
+
import { exitMeetMode } from "../modules/onboarding/mode";
|
|
17
25
|
import { formatPlanChecklist, stepContextForTool } from "../ui/plan-view";
|
|
18
26
|
import { formatCost } from "../modules/pricing/prices";
|
|
19
27
|
import { formatCacheLine } from "./cache-line";
|
|
@@ -21,6 +29,9 @@ import { probeProviders, targetsFromProviders } from "../modules/providers/healt
|
|
|
21
29
|
import { formatWarning } from "../ui/md-formatter";
|
|
22
30
|
import { defaultOutputBus, getDefaultChannel, SessionOutputSink, writeNonTtyLine, } from "../output";
|
|
23
31
|
import { createPromptIO } from "../tools/prompt-io";
|
|
32
|
+
import { MMA_DIR_NAME, projectMmaDir } from "../config/paths";
|
|
33
|
+
import { MMA_ENV, readEnv } from "../config/env";
|
|
34
|
+
/** Рендерит бар использования контекста с компакциями и качеством. */
|
|
24
35
|
function formatContextBar(used, limit, compactions, quality) {
|
|
25
36
|
const pct = Math.min(100, Math.round((used / limit) * 100));
|
|
26
37
|
const barLen = 20;
|
|
@@ -37,32 +48,15 @@ function formatContextBar(used, limit, compactions, quality) {
|
|
|
37
48
|
}
|
|
38
49
|
return line;
|
|
39
50
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
* (and any other plugin) submit messages through the SAME runAgent path as
|
|
43
|
-
* terminal input — rendering, session logging and single-flight guard are
|
|
44
|
-
* shared, never duplicated.
|
|
45
|
-
*/
|
|
46
|
-
export function createHostBridge(opts) {
|
|
47
|
-
return {
|
|
48
|
-
isBusy: opts.isBusy,
|
|
49
|
-
async submit(text, o) {
|
|
50
|
-
if (opts.isBusy())
|
|
51
|
-
throw new Error("agent is busy");
|
|
52
|
-
for (const url of o?.images ?? [])
|
|
53
|
-
opts.addPendingImage(url);
|
|
54
|
-
return opts.runAgent(text);
|
|
55
|
-
},
|
|
56
|
-
interrupt() {
|
|
57
|
-
if (opts.isBusy())
|
|
58
|
-
opts.interruptRun();
|
|
59
|
-
},
|
|
60
|
-
subscribeOutput: (fn) => defaultOutputBus.subscribe(fn),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
51
|
+
export { createHostBridge };
|
|
52
|
+
/** Интерактивный REPL терминала: владеет stdin, гоняет ходы агента, slash-команды. */
|
|
63
53
|
export class Repl {
|
|
54
|
+
/** Глобальный SIGINT-хендлер, общий на все инстансы Repl (см. registerSigint). */
|
|
55
|
+
static sigintHandler;
|
|
64
56
|
completer = new Completer();
|
|
65
57
|
modelCache = [];
|
|
58
|
+
/** Boot-снапшот управляемых ключей: `/config reset` откатывает из него. */
|
|
59
|
+
configSnapshot = new Map();
|
|
66
60
|
running = false;
|
|
67
61
|
agentRunning = false;
|
|
68
62
|
inputLocked = false;
|
|
@@ -76,17 +70,17 @@ export class Repl {
|
|
|
76
70
|
noAgentsMd;
|
|
77
71
|
pendingClipboardImage = null;
|
|
78
72
|
exitOnClose = false;
|
|
79
|
-
/**
|
|
73
|
+
/** Владелец состояния плана (bootstrap). В тестах null — план-UI деградирует. */
|
|
80
74
|
execModule;
|
|
81
75
|
/**
|
|
82
|
-
* True
|
|
83
|
-
*
|
|
84
|
-
*
|
|
76
|
+
* True, лишь когда кадр промпта ввода реально на экране. Промпт-синк канала
|
|
77
|
+
* неактивен до тех пор, чтобы стартовый баннер печатался plain, а не стирал/
|
|
78
|
+
* перерисовывал никогда не рисованный кадр.
|
|
85
79
|
*/
|
|
86
80
|
promptPresented = false;
|
|
87
81
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
82
|
+
* Ставит `runAgent`, чтобы slash-путь знал: прогон уже вернул промпт и второй
|
|
83
|
+
* раз его показывать нельзя (напр. `/run`).
|
|
90
84
|
*/
|
|
91
85
|
runHandledPrompt = false;
|
|
92
86
|
rl;
|
|
@@ -98,23 +92,31 @@ export class Repl {
|
|
|
98
92
|
logger;
|
|
99
93
|
slog;
|
|
100
94
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
95
|
+
* Расшаренный инстанс HostBridge (для плагинов; заодно движок `Host`-поверхности).
|
|
96
|
+
* Лениво строится, если PluginManager не аттачили.
|
|
97
|
+
*/
|
|
98
|
+
hostBridge;
|
|
99
|
+
/**
|
|
100
|
+
* Персистит события вывода с `data.persist === true` в активную сессию.
|
|
101
|
+
* Инертен, пока нет SessionManager; перепривязывается через `/reload`.
|
|
103
102
|
*/
|
|
104
103
|
sessionOutput;
|
|
105
104
|
envReport;
|
|
106
|
-
/**
|
|
105
|
+
/** Фоновый пробник реально загруженного контекста модели (bootstrap). */
|
|
107
106
|
contextProbe;
|
|
108
107
|
/**
|
|
109
|
-
*
|
|
110
|
-
* `ReplContext`
|
|
111
|
-
*
|
|
108
|
+
* Единый писатель терминала этого REPL. Публичен (не private), потому что
|
|
109
|
+
* `ReplContext` отдаёт его slash-командам как `ctx.output`; private-поле не
|
|
110
|
+
* удовлетворило бы публичному свойству интерфейса.
|
|
112
111
|
*/
|
|
113
112
|
output;
|
|
114
|
-
/**
|
|
113
|
+
/** Мост, дающий каналу рисовать поверх живого кадра ввода. */
|
|
115
114
|
promptSink;
|
|
116
|
-
/**
|
|
115
|
+
/** PromptIO с осознанием терминала, ставится на экзекутор тулов агента. */
|
|
117
116
|
promptIO;
|
|
117
|
+
get toolCtx() {
|
|
118
|
+
return this.agent.toolContext;
|
|
119
|
+
}
|
|
118
120
|
constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger, exitOnClose, envReport, historyPath, execModule, contextProbe, output) {
|
|
119
121
|
this.agent = agent;
|
|
120
122
|
this.config = config;
|
|
@@ -128,7 +130,7 @@ export class Repl {
|
|
|
128
130
|
this.execModule = execModule;
|
|
129
131
|
this.slog = new SessionLogger(sessionManager, logger);
|
|
130
132
|
this.bindSessionOutput();
|
|
131
|
-
this.configDir = configDir || join(homedir(),
|
|
133
|
+
this.configDir = configDir || join(homedir(), MMA_DIR_NAME);
|
|
132
134
|
this.baseDir = baseDir || process.cwd();
|
|
133
135
|
this.noAgentsMd = noAgentsMd === true;
|
|
134
136
|
this.historyPath = historyPath ?? join(homedir(), ".mma", "repl-history");
|
|
@@ -139,6 +141,11 @@ export class Repl {
|
|
|
139
141
|
return [matches, partial];
|
|
140
142
|
return [[], line];
|
|
141
143
|
};
|
|
144
|
+
// Комплитер node-readline понимает только plain-строковые совпадения.
|
|
145
|
+
const readlineCompleter = (line) => {
|
|
146
|
+
const [items, partial] = editorCompleter(line);
|
|
147
|
+
return [items.map((i) => i.value), partial];
|
|
148
|
+
};
|
|
142
149
|
if (process.stdin.isTTY) {
|
|
143
150
|
this.rl = new LineEditor({
|
|
144
151
|
input: process.stdin,
|
|
@@ -157,7 +164,7 @@ export class Repl {
|
|
|
157
164
|
history: this.history,
|
|
158
165
|
historySize: this.maxHistory,
|
|
159
166
|
tabSize: 2,
|
|
160
|
-
completer:
|
|
167
|
+
completer: readlineCompleter,
|
|
161
168
|
});
|
|
162
169
|
}
|
|
163
170
|
this.output = output ?? getDefaultChannel();
|
|
@@ -171,9 +178,9 @@ export class Repl {
|
|
|
171
178
|
this.rl.printAbove(text);
|
|
172
179
|
return;
|
|
173
180
|
}
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
181
|
+
// Не-TTY фолбэк (readline / пайпнутый stdin): кадра ввода для перерисовки
|
|
182
|
+
// сверху нет — пишем напрямую через санкционированный машинный хелпер:
|
|
183
|
+
// `printAbove` здесь не про что.
|
|
177
184
|
writeNonTtyLine(text);
|
|
178
185
|
},
|
|
179
186
|
clearScreen: () => {
|
|
@@ -182,32 +189,42 @@ export class Repl {
|
|
|
182
189
|
},
|
|
183
190
|
};
|
|
184
191
|
this.output.attachPrompt(this.promptSink);
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
192
|
+
// Промпт-поверхность с осознанием терминала для интерактивных тулов (question/approve).
|
|
193
|
+
// stdin владеет REPL и на время прогона он в `streaming`: тулы обязаны одалживать
|
|
194
|
+
// редактор под вопрос, а не открывать конкурирующий readline, который подерётся
|
|
195
|
+
// с raw-режимом LineEditor.
|
|
189
196
|
this.promptIO = createPromptIO({
|
|
190
197
|
ask: (prompt) => this.askViaEditor(prompt),
|
|
191
198
|
print: (line) => this.output.writeLine(line),
|
|
192
199
|
});
|
|
193
200
|
this.agent.setPromptIO?.(this.promptIO);
|
|
194
201
|
registerAllCommands(this);
|
|
202
|
+
this.configSnapshot = captureConfigSnapshot(this.config);
|
|
195
203
|
this.setupCompleter();
|
|
196
204
|
this.setupListeners();
|
|
197
|
-
//
|
|
205
|
+
// Отдаём агента плагинам (web UI и др.) через узкий мост.
|
|
198
206
|
if (pluginManager) {
|
|
199
207
|
this.attachHostBridge(pluginManager);
|
|
200
208
|
}
|
|
201
209
|
}
|
|
202
|
-
/** (
|
|
203
|
-
* /reload
|
|
204
|
-
*
|
|
210
|
+
/** (Пере)аттач плагинного HostBridge. Зовётся в конструкторе и после того, как
|
|
211
|
+
* /reload заменил PluginManager: без этого перезагруженный web-ui плагин
|
|
212
|
+
* молча теряет способность сабмитить сообщения. */
|
|
205
213
|
attachHostBridge(pm) {
|
|
206
214
|
const manager = pm ?? this.pluginManager;
|
|
207
215
|
if (!manager)
|
|
208
216
|
return;
|
|
209
|
-
|
|
210
|
-
|
|
217
|
+
this.hostBridge ??= this.buildHostBridge();
|
|
218
|
+
manager.setHostBridge(this.hostBridge);
|
|
219
|
+
}
|
|
220
|
+
/** Единый busy-предикат для `Host`-поверхности и моста. */
|
|
221
|
+
isAgentBusy() {
|
|
222
|
+
return this.agentRunning;
|
|
223
|
+
}
|
|
224
|
+
/** Строит HostBridge поверх обычного run-пути этого REPL. */
|
|
225
|
+
buildHostBridge() {
|
|
226
|
+
return createHostBridge({
|
|
227
|
+
isBusy: () => this.isAgentBusy(),
|
|
211
228
|
addPendingImage: (url) => {
|
|
212
229
|
this.agent.contextManager?.addPendingImage?.({
|
|
213
230
|
type: "image_url",
|
|
@@ -216,9 +233,14 @@ export class Repl {
|
|
|
216
233
|
},
|
|
217
234
|
runAgent: (text) => this.runAgent(text),
|
|
218
235
|
interruptRun: () => this.agent.shutdown(),
|
|
219
|
-
})
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Лениво резолвит общий мост, чтобы `Host`-методы работали без плагинов. */
|
|
239
|
+
getHostBridge() {
|
|
240
|
+
this.hostBridge ??= this.buildHostBridge();
|
|
241
|
+
return this.hostBridge;
|
|
220
242
|
}
|
|
221
|
-
/** (
|
|
243
|
+
/** (Пере)привязка синка вывода сессии после конструктора или `/reload`. */
|
|
222
244
|
bindSessionOutput() {
|
|
223
245
|
this.sessionOutput?.dispose();
|
|
224
246
|
this.sessionOutput = this.sessionManager
|
|
@@ -242,19 +264,20 @@ export class Repl {
|
|
|
242
264
|
Object.assign(this.config, freshConfig);
|
|
243
265
|
const result = await bootstrap(this.configDir, this.baseDir, false, false);
|
|
244
266
|
this.agent = result.agent;
|
|
245
|
-
//
|
|
246
|
-
//
|
|
267
|
+
// Переустанавливаем промпт-поверхность на свежезабутстрапленного агента: замыкание
|
|
268
|
+
// `askViaEditor` этого REPL принадлежит всё ещё этому инстансу.
|
|
247
269
|
this.agent.setPromptIO?.(this.promptIO);
|
|
248
270
|
this.sessionManager = result.sessionManager;
|
|
249
271
|
this.skillsModule = result.skillsModule;
|
|
250
272
|
this.pluginManager = result.pluginManager;
|
|
251
273
|
this.execModule = result.execModule;
|
|
252
274
|
this.bindSessionOutput();
|
|
253
|
-
//
|
|
254
|
-
//
|
|
275
|
+
// У свежего PluginManager моста нет — перерегистрируем, иначе плагины вроде
|
|
276
|
+
// web-ui молча теряют управление агентом.
|
|
255
277
|
this.attachHostBridge(result.pluginManager);
|
|
256
278
|
this.setupCompleter();
|
|
257
279
|
}
|
|
280
|
+
/** Грузит историю ввода из файла. */
|
|
258
281
|
loadHistory() {
|
|
259
282
|
if (existsSync(this.historyPath)) {
|
|
260
283
|
try {
|
|
@@ -266,10 +289,11 @@ export class Repl {
|
|
|
266
289
|
}
|
|
267
290
|
}
|
|
268
291
|
}
|
|
292
|
+
/** Сохраняет историю ввода. Падение каталога переживает молча, не бросает. */
|
|
269
293
|
saveHistory() {
|
|
270
294
|
try {
|
|
271
|
-
//
|
|
272
|
-
//
|
|
295
|
+
// Каталога истории может не быть (напр. temp снесли в тесте или при teardown
|
|
296
|
+
// сессии). Пересоздаём; сейв бросать не должен никогда.
|
|
273
297
|
mkdirSync(dirname(this.historyPath), { recursive: true });
|
|
274
298
|
const allHistory = this.history.slice(-this.maxHistory);
|
|
275
299
|
writeFileSync(this.historyPath, allHistory.join("\n"), "utf-8");
|
|
@@ -278,33 +302,102 @@ export class Repl {
|
|
|
278
302
|
this.logger?.debug(`Failed to save REPL history: ${e.message}`);
|
|
279
303
|
}
|
|
280
304
|
}
|
|
305
|
+
/** Строит комплитер с нуля. */
|
|
281
306
|
setupCompleter() {
|
|
282
|
-
//
|
|
283
|
-
//
|
|
307
|
+
// Пересборка с нуля: /reload зовёт снова после bootstrap, и перерегистрация
|
|
308
|
+
// без reset дублировала бы каждого провайдера.
|
|
284
309
|
this.completer.reset();
|
|
285
|
-
|
|
310
|
+
// `this.commands` ключуется каноническими именами И алиасами; итерация даёт
|
|
311
|
+
// каждый дополняемый токен с описанием его команды.
|
|
312
|
+
const slashCommands = buildSlashCompletions(this.commands);
|
|
286
313
|
this.completer.registerProvider(new SlashCommandProvider(slashCommands));
|
|
287
314
|
if (this.sessionManager) {
|
|
288
|
-
this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name)
|
|
315
|
+
this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name), [
|
|
316
|
+
"/resume",
|
|
317
|
+
"/delete",
|
|
318
|
+
"/export",
|
|
319
|
+
]));
|
|
289
320
|
}
|
|
290
321
|
if (this.skillsModule) {
|
|
291
|
-
this.completer.registerProvider(new SubcommandProvider("skill", [
|
|
322
|
+
this.completer.registerProvider(new SubcommandProvider("skill", [
|
|
323
|
+
{ name: "list", description: t("skill.sub_list") },
|
|
324
|
+
{ name: "loaded", description: t("skill.sub_loaded") },
|
|
325
|
+
{ name: "load", description: t("skill.sub_load") },
|
|
326
|
+
{ name: "unload", description: t("skill.sub_unload") },
|
|
327
|
+
{ name: "search", description: t("skill.sub_search") },
|
|
328
|
+
]));
|
|
292
329
|
this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
|
|
293
330
|
}
|
|
294
|
-
//
|
|
295
|
-
this.completer.registerProvider(new SubcommandProvider("provider", [
|
|
296
|
-
|
|
331
|
+
// Комплит субкоманд + аргументов управления provider/model.
|
|
332
|
+
this.completer.registerProvider(new SubcommandProvider("provider", [
|
|
333
|
+
{ name: "list", description: t("provider.sub_list") },
|
|
334
|
+
{ name: "use", description: t("provider.sub_use") },
|
|
335
|
+
{ name: "add", description: t("provider.sub_add") },
|
|
336
|
+
]));
|
|
337
|
+
this.completer.registerProvider(new SubcommandProvider("model", [
|
|
338
|
+
{ name: "list", description: t("model.sub_list") },
|
|
339
|
+
{ name: "use", description: t("model.sub_use") },
|
|
340
|
+
]));
|
|
341
|
+
this.completer.registerProvider(new SubcommandProvider("map", [
|
|
342
|
+
{ name: "summary", description: t("map.sub_summary") },
|
|
343
|
+
{ name: "refresh", description: t("map.sub_refresh") },
|
|
344
|
+
{ name: "find", description: t("map.sub_find") },
|
|
345
|
+
]));
|
|
346
|
+
this.completer.registerProvider(new SubcommandProvider("context", [
|
|
347
|
+
{ name: "system", description: t("context.sub_system") },
|
|
348
|
+
{ name: "reserve", description: t("context.sub_reserve") },
|
|
349
|
+
]));
|
|
350
|
+
this.completer.registerProvider(new SubcommandProvider("lsp", [
|
|
351
|
+
{ name: "status", description: t("lsp.sub_status") },
|
|
352
|
+
{ name: "restart", description: t("lsp.sub_restart") },
|
|
353
|
+
{ name: "check", description: t("lsp.sub_check") },
|
|
354
|
+
]));
|
|
355
|
+
this.completer.registerProvider(new SubcommandProvider("reasoning", [
|
|
356
|
+
{ name: "show", description: t("reasoning.sub_show") },
|
|
357
|
+
{ name: "hide", description: t("reasoning.sub_hide") },
|
|
358
|
+
{ name: "level", description: t("reasoning.sub_level") },
|
|
359
|
+
]));
|
|
360
|
+
this.completer.registerProvider(new SubcommandProvider("verbose", [
|
|
361
|
+
{ name: "quiet", description: t("verbose.sub_quiet") },
|
|
362
|
+
{ name: "normal", description: t("verbose.sub_normal") },
|
|
363
|
+
{ name: "verbose", description: t("verbose.sub_verbose") },
|
|
364
|
+
]));
|
|
365
|
+
this.completer.registerProvider(new SubcommandProvider("security", [
|
|
366
|
+
{ name: "status", description: t("cli.security.status") },
|
|
367
|
+
]));
|
|
368
|
+
this.completer.registerProvider(new ConfigKeyProvider());
|
|
369
|
+
this.completer.registerProvider(new ConfigValueProvider());
|
|
370
|
+
this.completer.registerProvider(new FlagProvider());
|
|
371
|
+
this.completer.registerProvider(new CommandArgProvider(() => Array.from(this.commands.entries())
|
|
372
|
+
.filter(([key, cmd]) => key === cmd.name)
|
|
373
|
+
.map(([, cmd]) => cmd.name)));
|
|
297
374
|
const entryLabels = this.config.provider?.entries?.map((e) => e.label || e.type) ?? [];
|
|
298
375
|
if (entryLabels.length > 0) {
|
|
299
376
|
this.completer.registerProvider(new ProviderArgProvider(entryLabels));
|
|
300
377
|
}
|
|
301
378
|
this.completer.registerProvider(new ModelArgProvider(() => this.modelCache));
|
|
302
|
-
//
|
|
379
|
+
// Комплит путей якорится на workspace агента, а не process.cwd().
|
|
380
|
+
this.completer.registerProvider(new FilePathProvider(this.baseDir));
|
|
381
|
+
// Греем кеш моделей фоном (бесплатный listModels-вызов).
|
|
303
382
|
this.refreshModelCache();
|
|
304
383
|
}
|
|
384
|
+
/** Перестройка реестра + комплитера — после смены `/config locale`. */
|
|
385
|
+
reregisterCommands() {
|
|
386
|
+
this.commands.clear();
|
|
387
|
+
registerAllCommands(this);
|
|
388
|
+
this.setupCompleter();
|
|
389
|
+
}
|
|
390
|
+
/** Boot-снапшот управляемых ключей для `/config reset`. */
|
|
391
|
+
getConfigSnapshot() {
|
|
392
|
+
return this.configSnapshot;
|
|
393
|
+
}
|
|
394
|
+
/** Кешированные id моделей активного провайдера (пусто, пока офлайн). */
|
|
395
|
+
knownModels() {
|
|
396
|
+
return this.modelCache;
|
|
397
|
+
}
|
|
305
398
|
/**
|
|
306
|
-
*
|
|
307
|
-
* Best-effort:
|
|
399
|
+
* Фоновое обновление кешированного списка моделей для `/model use <Tab>`.
|
|
400
|
+
* Best-effort: при провале кеш хранит прошлое содержимое.
|
|
308
401
|
*/
|
|
309
402
|
refreshModelCache() {
|
|
310
403
|
void (async () => {
|
|
@@ -346,7 +439,7 @@ export class Repl {
|
|
|
346
439
|
}
|
|
347
440
|
}
|
|
348
441
|
if (inMultiLine) {
|
|
349
|
-
multiLineBuffer +=
|
|
442
|
+
multiLineBuffer += `\n${line}`;
|
|
350
443
|
if (!this.isMultiLineInput(multiLineBuffer)) {
|
|
351
444
|
inMultiLine = false;
|
|
352
445
|
const fullInput = multiLineBuffer.trim();
|
|
@@ -391,7 +484,7 @@ export class Repl {
|
|
|
391
484
|
this.running = false;
|
|
392
485
|
this.saveHistory();
|
|
393
486
|
this.agent.shutdown();
|
|
394
|
-
//
|
|
487
|
+
// Закрываем лог сессии на выходе из REPL.
|
|
395
488
|
this.logger?.closeSessionLog();
|
|
396
489
|
if (this.exitOnClose)
|
|
397
490
|
process.exit(0);
|
|
@@ -401,7 +494,7 @@ export class Repl {
|
|
|
401
494
|
this.running = false;
|
|
402
495
|
this.saveHistory();
|
|
403
496
|
this.agent.shutdown();
|
|
404
|
-
//
|
|
497
|
+
// Закрываем лог сессии на выходе из REPL.
|
|
405
498
|
this.logger?.closeSessionLog();
|
|
406
499
|
process.exit(0);
|
|
407
500
|
});
|
|
@@ -410,10 +503,12 @@ export class Repl {
|
|
|
410
503
|
this.rl.onKeyInput = (str, key) => this.handleSpecialKey(str, key);
|
|
411
504
|
}
|
|
412
505
|
let forceExitTimer = null;
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
//
|
|
416
|
-
//
|
|
506
|
+
// Запас на очистку (сессия, MCP, дочерние процессы) после второго Ctrl+C.
|
|
507
|
+
const FORCE_EXIT_MS = 5000;
|
|
508
|
+
// Идемпотентная регистрация: второй Repl в том же процессе не добавляет
|
|
509
|
+
// ещё один глобальный SIGINT-хендлер. Протёкшие хендлеры инстансов REPL,
|
|
510
|
+
// созданные в тестах, стреляли process.exit(0) на ЛЮБОЙ process.emit("SIGINT")
|
|
511
|
+
// в другом месте и молча убивали весь bun-раннер тестов.
|
|
417
512
|
if (typeof Repl.sigintHandler === "function") {
|
|
418
513
|
process.removeListener("SIGINT", Repl.sigintHandler);
|
|
419
514
|
}
|
|
@@ -424,7 +519,10 @@ export class Repl {
|
|
|
424
519
|
this.agentRunning = false;
|
|
425
520
|
if (forceExitTimer)
|
|
426
521
|
clearTimeout(forceExitTimer);
|
|
427
|
-
forceExitTimer = setTimeout(() =>
|
|
522
|
+
forceExitTimer = setTimeout(() => {
|
|
523
|
+
this.output.writeLine(pc.yellow(t("repl.force_exit")));
|
|
524
|
+
process.exit(1);
|
|
525
|
+
}, FORCE_EXIT_MS).unref();
|
|
428
526
|
}
|
|
429
527
|
else {
|
|
430
528
|
process.exit(0);
|
|
@@ -433,10 +531,10 @@ export class Repl {
|
|
|
433
531
|
process.on("SIGINT", Repl.sigintHandler);
|
|
434
532
|
}
|
|
435
533
|
/**
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
534
|
+
* Перепоказ промпта ввода после slash-команды. У команд нет владения жизненным
|
|
535
|
+
* циклом кадра, им нужна явная перерисовка. Путь агента осознанно исключён:
|
|
536
|
+
* finally `runAgent` зовёт `LineEditor.resumeFrame()` как единственный показ —
|
|
537
|
+
* промпт здесь дал бы двойную отрисовку.
|
|
440
538
|
*/
|
|
441
539
|
presentPrompt() {
|
|
442
540
|
if (!this.running)
|
|
@@ -445,13 +543,20 @@ export class Repl {
|
|
|
445
543
|
this.rl.prompt();
|
|
446
544
|
this.promptPresented = true;
|
|
447
545
|
}
|
|
546
|
+
/** Возврат обычного промпта после стартового вопроса, если активен LineEditor. */
|
|
547
|
+
restoreStartupPrompt() {
|
|
548
|
+
if (this.rl instanceof LineEditor) {
|
|
549
|
+
this.rl.setPrompt(this.inputPrompt());
|
|
550
|
+
this.rl.prompt();
|
|
551
|
+
this.promptPresented = true;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
448
554
|
/**
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
* mode we show the effective level.
|
|
555
|
+
* Ярлык reasoning в промпте ввода. В `auto` эффективный уровень обычно решает
|
|
556
|
+
* политика поитерационно, так что показываем лишь факт автоматики. При явном
|
|
557
|
+
* оверрайде юзера (`/reasoning <level>`, персистентный) или активном
|
|
558
|
+
* `set_thinking`-оверрайде агента (транзиентный) — `auto→<level>`. В фикс.
|
|
559
|
+
* режиме — эффективный уровень.
|
|
455
560
|
*/
|
|
456
561
|
reasoningLabel() {
|
|
457
562
|
const mode = this.config.reasoning?.mode ?? "default";
|
|
@@ -464,16 +569,15 @@ export class Repl {
|
|
|
464
569
|
return `auto→${this.agent.reasoningLevel}`;
|
|
465
570
|
return "auto";
|
|
466
571
|
}
|
|
467
|
-
/**
|
|
572
|
+
/** Локализованный промпт ввода со встроенным режимом/уровнем reasoning. */
|
|
468
573
|
inputPrompt(continuation = false) {
|
|
469
574
|
const key = continuation ? "repl.you_reasoning_cont" : "repl.you_reasoning";
|
|
470
575
|
return pc.cyan(t(key, { level: this.reasoningLabel() }));
|
|
471
576
|
}
|
|
472
577
|
/**
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
* present the prompt here.
|
|
578
|
+
* Учёт промпта после slash-команды. Команда, прогнавшая прогон (напр. `/run`),
|
|
579
|
+
* уже вернула себе промпт через `runAgent`: повторный показ двоил бы рендер,
|
|
580
|
+
* так что скипаем ровно раз. Все прочие команды показывают промпт здесь.
|
|
477
581
|
*/
|
|
478
582
|
afterCommand() {
|
|
479
583
|
if (this.runHandledPrompt) {
|
|
@@ -482,12 +586,12 @@ export class Repl {
|
|
|
482
586
|
}
|
|
483
587
|
this.presentPrompt();
|
|
484
588
|
}
|
|
485
|
-
|
|
589
|
+
/** Спецклавиши: двойной Esc — аборт прогона, Ctrl+V — вставка картинки, Ctrl+O — детали. */
|
|
590
|
+
handleSpecialKey(_str, key) {
|
|
486
591
|
if (key.name === "escape") {
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
// the case where two separate escape keypresses land in the window.
|
|
592
|
+
// Декодер keypress Bun схлопывает быстрый двойной Esc в один keypress, чей
|
|
593
|
+
// `sequence` содержит два ESC-байта ("\x1b\x1b"). Подсчёт байтов (не событий)
|
|
594
|
+
// ловит и схлопнутый случай, и два раздельных Esc в окне.
|
|
491
595
|
const escBytes = key.sequence ? (key.sequence.match(/\x1b/g) || []).length : 1;
|
|
492
596
|
const now = Date.now();
|
|
493
597
|
const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
|
|
@@ -505,8 +609,34 @@ export class Repl {
|
|
|
505
609
|
this.pasteClipboardImage();
|
|
506
610
|
return true;
|
|
507
611
|
}
|
|
612
|
+
if (key.ctrl && key.name === "o" && !this.agentRunning) {
|
|
613
|
+
this.reprintLastAction();
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
508
616
|
return false;
|
|
509
617
|
}
|
|
618
|
+
/** Ctrl+O — допечатка полных деталей последнего действия. */
|
|
619
|
+
reprintLastAction() {
|
|
620
|
+
const last = getLastAction();
|
|
621
|
+
if (!last) {
|
|
622
|
+
this.output.writeLine(pc.dim(t("repl.last_action_none")));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const mark = last.status === "error"
|
|
626
|
+
? pc.red(glyph.failure)
|
|
627
|
+
: last.status === "warn"
|
|
628
|
+
? pc.yellow(glyph.warning)
|
|
629
|
+
: pc.green(glyph.success);
|
|
630
|
+
const accent = toolAccent(last.tool);
|
|
631
|
+
const head = `${accent(glyph.action)} ${accent(last.label)}${last.args ? ` ${pc.dim("·")} ${pc.dim(last.args)}` : ""} ${mark} ${pc.dim(`${last.durationMs}ms`)}`;
|
|
632
|
+
const lines = [head, ...last.body.map((l) => ` ${l}`)];
|
|
633
|
+
this.output.writeLine(`\n${lines.join("\n")}`);
|
|
634
|
+
if (this.rl instanceof LineEditor) {
|
|
635
|
+
this.rl.reset();
|
|
636
|
+
this.rl.prompt();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
/** Вставка картинки из буфера обмена (Ctrl+V). */
|
|
510
640
|
async pasteClipboardImage() {
|
|
511
641
|
try {
|
|
512
642
|
const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
|
|
@@ -528,9 +658,10 @@ export class Repl {
|
|
|
528
658
|
}
|
|
529
659
|
}
|
|
530
660
|
catch {
|
|
531
|
-
//
|
|
661
|
+
// Чтение буфера не удалось — игнорим, пусть терминал вставит текст штатно.
|
|
532
662
|
}
|
|
533
663
|
}
|
|
664
|
+
/** Проверка незакрытого многострочного ввода (хвостовой `\` или `{` без `}`). */
|
|
534
665
|
isMultiLineInput(line) {
|
|
535
666
|
if (line.endsWith("\\"))
|
|
536
667
|
return true;
|
|
@@ -540,6 +671,7 @@ export class Repl {
|
|
|
540
671
|
return true;
|
|
541
672
|
return false;
|
|
542
673
|
}
|
|
674
|
+
/** Прогон хода агента: стрим в рендерер, футер, бары, возврат промпта. */
|
|
543
675
|
async runAgent(input) {
|
|
544
676
|
if (this.agentRunning)
|
|
545
677
|
return;
|
|
@@ -556,27 +688,27 @@ export class Repl {
|
|
|
556
688
|
this.pendingClipboardImage = null;
|
|
557
689
|
}
|
|
558
690
|
this.logger?.logREPL("user", input);
|
|
559
|
-
//
|
|
560
|
-
//
|
|
691
|
+
// Кадр промпта сейчас снимется; гасим синк до любого стримленного вывода,
|
|
692
|
+
// чтобы ничего не пыталось рисовать поверх него.
|
|
561
693
|
this.promptPresented = false;
|
|
562
|
-
//
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
// reset(), which would otherwise draw a frame only to erase it.
|
|
694
|
+
// Отдаём терминал каналу на время прогона: редактор перестаёт рисовать кадр
|
|
695
|
+
// и игнорит клавиши, и стрим не портит строку ввода. suspendFrame() идёт до
|
|
696
|
+
// reset(), который иначе нарисовал бы кадр лишь затем, чтобы стереть.
|
|
566
697
|
if (this.rl instanceof LineEditor) {
|
|
567
698
|
this.rl.suspendFrame();
|
|
568
699
|
this.rl.setInputEnabled(false);
|
|
569
700
|
this.rl.reset();
|
|
570
701
|
}
|
|
571
702
|
this.output.beginStreaming();
|
|
572
|
-
//
|
|
573
|
-
// Renderer.text()
|
|
574
|
-
//
|
|
703
|
+
// Разделитель + ярлык агента, сырьём (без trailing newline): следующий чанк
|
|
704
|
+
// Renderer.text() добавляет свой ведущий "\n", и writeLine здесь вставил бы
|
|
705
|
+
// пустую строку ("Agent:\n\n...").
|
|
575
706
|
this.output.writeRaw(`\r${divider()}\n${pc.green(t("repl.agent"))}`);
|
|
576
707
|
const renderer = new Renderer({
|
|
577
708
|
spinner: this.config.ui?.spinner ?? true,
|
|
578
709
|
baseDir: this.baseDir,
|
|
579
710
|
channel: this.output,
|
|
711
|
+
verbosity: resolveVerbosity(this.config.ui),
|
|
580
712
|
});
|
|
581
713
|
renderer.showLoader();
|
|
582
714
|
const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
|
|
@@ -585,7 +717,7 @@ export class Repl {
|
|
|
585
717
|
}
|
|
586
718
|
else {
|
|
587
719
|
renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta, ev.costUsd);
|
|
588
|
-
if (ev.tool === "plan" || ev.tool === "todo") {
|
|
720
|
+
if (ev.tool === "plan" || ev.tool === "todo" || ev.tool === "plan_step") {
|
|
589
721
|
this.renderPlan(renderer);
|
|
590
722
|
}
|
|
591
723
|
}
|
|
@@ -596,7 +728,7 @@ export class Repl {
|
|
|
596
728
|
else {
|
|
597
729
|
renderer.thinkingEnd();
|
|
598
730
|
}
|
|
599
|
-
});
|
|
731
|
+
}, (reasoning) => renderer.reasoning(reasoning));
|
|
600
732
|
renderer.flush();
|
|
601
733
|
if (result.provider && result.model) {
|
|
602
734
|
renderer.footer(result.model, result.provider, result.llmDurationMs ?? result.durationMs ?? 0);
|
|
@@ -607,8 +739,8 @@ export class Repl {
|
|
|
607
739
|
this.output.writeLine(pc.red(`${t("error.prefix")}${result.error}`), "stderr");
|
|
608
740
|
}
|
|
609
741
|
this.showContextBar(result);
|
|
610
|
-
//
|
|
611
|
-
//
|
|
742
|
+
// Закрываем ход агента: разделитель перед следующим промптом визуально
|
|
743
|
+
// разводит соседние сообщения юзера/агента.
|
|
612
744
|
this.output.writeLine(`\n${divider()}`);
|
|
613
745
|
}
|
|
614
746
|
finally {
|
|
@@ -616,26 +748,32 @@ export class Repl {
|
|
|
616
748
|
this.output.endStreaming();
|
|
617
749
|
if (this.rl instanceof LineEditor) {
|
|
618
750
|
this.rl.setInputEnabled(true);
|
|
619
|
-
//
|
|
620
|
-
//
|
|
621
|
-
//
|
|
622
|
-
//
|
|
623
|
-
//
|
|
751
|
+
// Единственный показ промпта на пути агента: resumeFrame() перерисовывает
|
|
752
|
+
// кадр, так что слушатель `line` после runAgent() промпт НЕ показывает
|
|
753
|
+
// (после slash-команд — показывает). Промпт переставляем первым, чтобы
|
|
754
|
+
// уровень reasoning, сменённый set_thinking в прогоне, отразился на
|
|
755
|
+
// возобновлённом кадре.
|
|
624
756
|
this.rl.setPrompt(this.inputPrompt());
|
|
625
757
|
this.rl.resumeFrame();
|
|
626
758
|
this.promptPresented = true;
|
|
627
759
|
}
|
|
628
760
|
else {
|
|
629
|
-
//
|
|
630
|
-
//
|
|
761
|
+
// У пайпнутого/не-TTY фолбэка кадра для resume нет; показываем readline-
|
|
762
|
+
// промпт здесь ради старого пост-прогонного поведения.
|
|
631
763
|
this.presentPrompt();
|
|
632
764
|
}
|
|
765
|
+
const onboardingCtx = this.agent.tryToolContext();
|
|
766
|
+
if (onboardingCtx?.onboarding?.done) {
|
|
767
|
+
exitMeetMode(this);
|
|
768
|
+
this.slog.logOnboarding("meet_done", { model: this.config.model });
|
|
769
|
+
this.output.writeLine(pc.dim(t("repl.meet_done")));
|
|
770
|
+
}
|
|
633
771
|
this.runHandledPrompt = true;
|
|
634
772
|
}
|
|
635
773
|
}
|
|
636
|
-
/**
|
|
637
|
-
*
|
|
638
|
-
*
|
|
774
|
+
/** Печать чеклиста плана, трекаемого ExecutionModule.
|
|
775
|
+
* Зовётся только на plan/todo-событиях (событие изменения), не поллится:
|
|
776
|
+
* дисковый план прошлой сессии не протечёт в чат. */
|
|
639
777
|
renderPlan(renderer) {
|
|
640
778
|
const plan = this.execModule?.getActivePlan();
|
|
641
779
|
if (!plan)
|
|
@@ -643,6 +781,7 @@ export class Repl {
|
|
|
643
781
|
renderer.planBlock(formatPlanChecklist(plan));
|
|
644
782
|
}
|
|
645
783
|
commands = new Map();
|
|
784
|
+
/** Регистрирует slash-команду (имя + алиасы). */
|
|
646
785
|
registerCommand(cmd) {
|
|
647
786
|
this.commands.set(cmd.name, cmd);
|
|
648
787
|
if (cmd.aliases) {
|
|
@@ -651,12 +790,12 @@ export class Repl {
|
|
|
651
790
|
}
|
|
652
791
|
}
|
|
653
792
|
}
|
|
793
|
+
/** Эксклюзивный ввод: визард владеет терминалом через свои `rl.question()`. */
|
|
654
794
|
async withExclusiveInput(fn) {
|
|
655
|
-
//
|
|
656
|
-
//
|
|
657
|
-
//
|
|
658
|
-
//
|
|
659
|
-
// `inputLocked` makes `promptSink.isActive()` false, routing writes direct.
|
|
795
|
+
// Между вопросами активного кадра нет, так что меню/результаты скана пишутся
|
|
796
|
+
// напрямую: обёртка beginModal/endModal сложила бы ВЕСЬ транскрипт в очередь,
|
|
797
|
+
// и юзер видел бы голые `: ` промпты до конца. `inputLocked` гасит
|
|
798
|
+
// `promptSink.isActive()`, пуская записи напрямую.
|
|
660
799
|
this.inputLocked = true;
|
|
661
800
|
try {
|
|
662
801
|
await fn();
|
|
@@ -666,16 +805,14 @@ export class Repl {
|
|
|
666
805
|
}
|
|
667
806
|
}
|
|
668
807
|
/**
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
* agent's own renderer keeps working afterwards.
|
|
808
|
+
* Одалживает терминал под один вопрос интерактивного тула и возвращает обратно.
|
|
809
|
+
* На время прогона LineEditor suspended с выключенным вводом; включение ввода +
|
|
810
|
+
* resume кадра даёт `question()` читать клавиши, пока агент блокирован, а `finally`
|
|
811
|
+
* восстанавливает ровно стрим-состояние, и рендерер агента работает дальше как ни в чём.
|
|
674
812
|
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
* queued during the question flush plainly instead of corrupting the frame.
|
|
813
|
+
* Порядок важен: `beginModal()` сначала сбрасывает вывод, ещё буферный у стрим-канала,
|
|
814
|
+
* чтобы напечатался plain до возврата кадра; `endModal()` идёт лишь после повторного
|
|
815
|
+
* suspend кадра, чтобы события, накопленные за вопрос, сбросились plain, а не побили кадр.
|
|
679
816
|
*/
|
|
680
817
|
async askViaEditor(prompt) {
|
|
681
818
|
const editor = this.rl instanceof LineEditor ? this.rl : null;
|
|
@@ -700,16 +837,60 @@ export class Repl {
|
|
|
700
837
|
this.output.endModal();
|
|
701
838
|
}
|
|
702
839
|
}
|
|
703
|
-
|
|
840
|
+
/** Host/HostBridge: true, пока агент крутит ход. */
|
|
841
|
+
isBusy() {
|
|
842
|
+
return this.isAgentBusy();
|
|
843
|
+
}
|
|
844
|
+
/** Host/HostBridge: сабмит через общий мост (тот же run-путь). */
|
|
845
|
+
submit(text, opts) {
|
|
846
|
+
return this.getHostBridge().submit(text, opts);
|
|
847
|
+
}
|
|
848
|
+
/** Host/HostBridge: прерывание текущего прогона (в idle — no-op). */
|
|
849
|
+
interrupt() {
|
|
850
|
+
this.getHostBridge().interrupt();
|
|
851
|
+
}
|
|
852
|
+
/** Host/HostBridge: подписка на общую шину вывода. */
|
|
853
|
+
subscribeOutput(fn) {
|
|
854
|
+
return defaultOutputBus.subscribe(fn);
|
|
855
|
+
}
|
|
856
|
+
/** Host: прогон slash-команды через ту же handler-map, что и ручной ввод. */
|
|
857
|
+
async executeCommand(line) {
|
|
858
|
+
const trimmed = line.trim();
|
|
859
|
+
if (!trimmed.startsWith("/"))
|
|
860
|
+
return;
|
|
861
|
+
await this.dispatchCommand(trimmed);
|
|
862
|
+
}
|
|
863
|
+
/** Host: снапшот план/сессия/модель для UI-поверхностей. */
|
|
864
|
+
getState() {
|
|
865
|
+
const meta = this.sessionManager?.getActiveMeta();
|
|
866
|
+
return {
|
|
867
|
+
busy: this.agentRunning,
|
|
868
|
+
sessionId: meta?.id,
|
|
869
|
+
sessionName: meta?.name,
|
|
870
|
+
model: this.config.model,
|
|
871
|
+
provider: this.config.provider?.active,
|
|
872
|
+
plan: planSnapshotFromModule(adaptActivePlan(this.execModule?.getActivePlan())),
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
async dispatchCommand(input) {
|
|
704
876
|
const parts = input.split(/\s+/);
|
|
705
877
|
const name = parts[0].slice(1);
|
|
706
878
|
const args = parts.slice(1);
|
|
707
|
-
//
|
|
708
|
-
//
|
|
879
|
+
// Сбрасываем протухший run-флаг прошлого прямого прогона, чтобы решение
|
|
880
|
+
// presentPrompt() этой команды строилось на её собственном действии.
|
|
709
881
|
this.runHandledPrompt = false;
|
|
710
882
|
const cmd = this.commands.get(name);
|
|
711
883
|
if (!cmd) {
|
|
712
|
-
|
|
884
|
+
// Бывшие алиасы, уехавшие в субкоманду: указываем на новый синтаксис,
|
|
885
|
+
// а не на голое "unknown command".
|
|
886
|
+
const formerAliases = { use: "resume" };
|
|
887
|
+
const suggestion = formerAliases[name];
|
|
888
|
+
if (suggestion) {
|
|
889
|
+
this.output.writeLine(pc.red(t("cli.unknown_command", { input: `/${name}`, suggestion: `/${suggestion}` })));
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
this.output.writeLine(`${pc.red(t("cli.unknown_cmd", { name }))} ${t("cli.help_hint")}`);
|
|
893
|
+
}
|
|
713
894
|
return;
|
|
714
895
|
}
|
|
715
896
|
try {
|
|
@@ -721,8 +902,30 @@ export class Repl {
|
|
|
721
902
|
})), "stderr");
|
|
722
903
|
}
|
|
723
904
|
}
|
|
724
|
-
|
|
905
|
+
/** Показ помощи: общая или по команде (`/help <команда>`). */
|
|
906
|
+
showHelp(command) {
|
|
725
907
|
const { COMMAND_GROUPS } = require("./repl-commands");
|
|
908
|
+
if (command) {
|
|
909
|
+
const name = command.replace(/^\//, "");
|
|
910
|
+
const cmd = this.commands.get(name);
|
|
911
|
+
if (!cmd) {
|
|
912
|
+
this.output.writeLine(pc.red(t("repl.help_unknown", { name })));
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
this.output.writeLine(pc.bold(pc.cyan(`/${cmd.name}`)));
|
|
916
|
+
this.output.writeLine(` ${cmd.description}`);
|
|
917
|
+
if (cmd.usage)
|
|
918
|
+
this.output.writeLine(` ${pc.dim(cmd.usage)}`);
|
|
919
|
+
if (cmd.aliases?.length) {
|
|
920
|
+
this.output.writeLine(pc.dim(` ${t("repl.help_aliases", { names: cmd.aliases.join(", ") })}`));
|
|
921
|
+
}
|
|
922
|
+
if (cmd.examples?.length) {
|
|
923
|
+
this.output.writeLine(pc.dim(` ${t("repl.help_examples")}`));
|
|
924
|
+
for (const ex of cmd.examples)
|
|
925
|
+
this.output.writeLine(pc.dim(` ${ex}`));
|
|
926
|
+
}
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
726
929
|
const order = ["general", "agent", "session", "skill"];
|
|
727
930
|
const seen = new Set();
|
|
728
931
|
for (const groupKey of order) {
|
|
@@ -743,7 +946,7 @@ export class Repl {
|
|
|
743
946
|
this.output.writeLine(pc.bold(t(`repl.group.${groupKey}`)));
|
|
744
947
|
for (const cmd of cmds) {
|
|
745
948
|
const aliases = cmd.aliases?.length ? ` (${pc.dim(cmd.aliases.join(", "))})` : "";
|
|
746
|
-
this.output.writeLine(` ${pc.cyan(
|
|
949
|
+
this.output.writeLine(` ${pc.cyan(`/${cmd.name}`)}${aliases} ${pc.dim(cmd.description)}`);
|
|
747
950
|
if (cmd.usage) {
|
|
748
951
|
this.output.writeLine(` ${pc.dim(cmd.usage)}`);
|
|
749
952
|
}
|
|
@@ -764,7 +967,11 @@ export class Repl {
|
|
|
764
967
|
const ctxLine = formatContextBar(result.contextUsed, result.contextLimit, result.compactionCount, result.contextQuality);
|
|
765
968
|
this.output.writeLine(ctxLine);
|
|
766
969
|
if (result.totalTokens !== undefined && result.totalTokens > 0) {
|
|
767
|
-
const apiLine = pc.dim(
|
|
970
|
+
const apiLine = pc.dim(t("repl.api_line", {
|
|
971
|
+
prompt: String(result.promptTokens),
|
|
972
|
+
completion: String(result.completionTokens),
|
|
973
|
+
total: String(result.totalTokens),
|
|
974
|
+
}));
|
|
768
975
|
this.output.writeLine(apiLine);
|
|
769
976
|
}
|
|
770
977
|
if (result.totalCost !== undefined && result.totalCost > 0) {
|
|
@@ -778,7 +985,7 @@ export class Repl {
|
|
|
778
985
|
else if (ui?.showCompaction && result.compactionCount !== undefined) {
|
|
779
986
|
if (result.compactionCount > this.lastCompactionShown) {
|
|
780
987
|
this.lastCompactionShown = result.compactionCount;
|
|
781
|
-
this.output.writeLine(pc.dim(`\n ⟳
|
|
988
|
+
this.output.writeLine(pc.dim(`\n ⟳ ${t("repl.compacted", { count: String(result.compactionCount) })}`));
|
|
782
989
|
}
|
|
783
990
|
}
|
|
784
991
|
}
|
|
@@ -793,13 +1000,18 @@ export class Repl {
|
|
|
793
1000
|
const resBudget = Math.floor(ctx * this.config.contextBudget.responseReserve);
|
|
794
1001
|
const histBudget = ctx - sysBudget - resBudget;
|
|
795
1002
|
row(t("repl.model"), pc.white(this.config.model));
|
|
796
|
-
|
|
1003
|
+
// Плоские поля протухают после `/provider use` — истина в entries.
|
|
1004
|
+
const endpoint = activeProviderEndpoint(this.config.provider);
|
|
1005
|
+
row(t("repl.provider"), `${endpoint.type} → ${pc.dim(endpoint.baseUrl)}`);
|
|
797
1006
|
row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(sys:${sysBudget} res:${resBudget} hist:${histBudget})`)}`);
|
|
798
1007
|
const si = this.agent.getSystemPromptInfo();
|
|
799
1008
|
row(t("repl.sysprompt_label"), pc.dim(t("repl.sysprompt_size", { used: si.tokenCount, budget: sysBudget })));
|
|
800
1009
|
if (this.skillsModule) {
|
|
801
1010
|
const budget = this.skillsModule.getBudget();
|
|
802
|
-
row(t("repl.skills_label"),
|
|
1011
|
+
row(t("repl.skills_label"), t("repl.skills_available", {
|
|
1012
|
+
count: String(this.skillsModule.getAvailable().length),
|
|
1013
|
+
budget: String(budget.total),
|
|
1014
|
+
}));
|
|
803
1015
|
}
|
|
804
1016
|
if (this.pluginManager) {
|
|
805
1017
|
const infos = this.pluginManager.getPluginInfos().filter(({ plugin }) => !plugin.isBuiltin);
|
|
@@ -811,7 +1023,7 @@ export class Repl {
|
|
|
811
1023
|
return `${plugin.name}${version}${origin}`;
|
|
812
1024
|
})
|
|
813
1025
|
.join(", ");
|
|
814
|
-
row(t("repl.plugins_label"),
|
|
1026
|
+
row(t("repl.plugins_label"), t("repl.plugins_active", { count: String(infos.length), names: pluginNames }));
|
|
815
1027
|
}
|
|
816
1028
|
}
|
|
817
1029
|
const mcpServers = this.config.mcpServers || {};
|
|
@@ -820,8 +1032,8 @@ export class Repl {
|
|
|
820
1032
|
const names = enabledServers.map(([name]) => name).join(", ");
|
|
821
1033
|
row(t("repl.mcp_label"), `${pc.white(String(enabledServers.length))} ${pc.dim(`(${names})`)}`);
|
|
822
1034
|
}
|
|
823
|
-
//
|
|
824
|
-
//
|
|
1035
|
+
// Показываем workspace агента (каталог `-d`), а не process cwd: `bun run`
|
|
1036
|
+
// ставит cwd в корень пакета, где агент НЕ работает.
|
|
825
1037
|
row(t("repl.work_dir"), pc.dim(this.baseDir));
|
|
826
1038
|
if (this.noAgentsMd) {
|
|
827
1039
|
row(t("repl.agents_label"), pc.red(t("repl.disabled")));
|
|
@@ -829,7 +1041,7 @@ export class Repl {
|
|
|
829
1041
|
else {
|
|
830
1042
|
const agentsMdCandidates = [
|
|
831
1043
|
join(this.baseDir, "AGENTS.md"),
|
|
832
|
-
join(this.baseDir, "
|
|
1044
|
+
join(projectMmaDir(this.baseDir), "AGENTS.md"),
|
|
833
1045
|
join(this.configDir, "AGENTS.md"),
|
|
834
1046
|
];
|
|
835
1047
|
const foundAgents = agentsMdCandidates.filter((p) => existsSync(p));
|
|
@@ -845,17 +1057,17 @@ export class Repl {
|
|
|
845
1057
|
const meta = this.sessionManager?.getActiveMeta();
|
|
846
1058
|
if (meta) {
|
|
847
1059
|
const sessionPath = join(this.configDir, "sessions", meta.id);
|
|
848
|
-
row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
|
|
1060
|
+
row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} ${t("repl.msgs")} ${pc.dim(sessionPath)}`);
|
|
849
1061
|
}
|
|
850
|
-
// LSP
|
|
1062
|
+
// Статус LSP пробнится фоном — баннер блокировать нельзя.
|
|
851
1063
|
const isTty = process.stdout.isTTY === true;
|
|
852
1064
|
const lspEnabled = isTty && (this.config.lsp ?? DEFAULT_LSP_CONFIG).enabled !== false;
|
|
853
|
-
// Plain
|
|
1065
|
+
// Plain-баннер без рамки: построчно, с переносом по ширине.
|
|
854
1066
|
for (const line of info) {
|
|
855
1067
|
this.output.writeLine(pc.dim(line).trimEnd());
|
|
856
1068
|
}
|
|
857
1069
|
this.output.writeLine("");
|
|
858
|
-
//
|
|
1070
|
+
// Хинт про legacy-конфиг.
|
|
859
1071
|
try {
|
|
860
1072
|
const { existsSync: exists } = await import("fs");
|
|
861
1073
|
const { join: pathJoin } = await import("path");
|
|
@@ -866,26 +1078,76 @@ export class Repl {
|
|
|
866
1078
|
this.output.writeLine("");
|
|
867
1079
|
}
|
|
868
1080
|
}
|
|
869
|
-
catch {
|
|
870
|
-
|
|
871
|
-
|
|
1081
|
+
catch {
|
|
1082
|
+
// Хинт best-effort: ошибка чтения его просто скипает.
|
|
1083
|
+
}
|
|
1084
|
+
// Единственный показ промпта на старте: баннер выше печатался plain
|
|
1085
|
+
// (promptPresented был false), так что стирать кадр здесь нечего.
|
|
872
1086
|
this.rl.prompt();
|
|
873
1087
|
this.promptPresented = true;
|
|
1088
|
+
try {
|
|
1089
|
+
if (readEnv("START_MEET") === "1" || readEnv("START_INIT") === "1") {
|
|
1090
|
+
const mode = readEnv("START_MEET") === "1" ? "meet" : "init";
|
|
1091
|
+
delete process.env[MMA_ENV.START_MEET];
|
|
1092
|
+
delete process.env[MMA_ENV.START_INIT];
|
|
1093
|
+
this.slog.logOnboarding(`${mode}_start`, { model: this.config.model });
|
|
1094
|
+
if (mode === "meet") {
|
|
1095
|
+
const { runMeet } = await import("../modules/onboarding/meet");
|
|
1096
|
+
await runMeet(this);
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
const { runInit } = await import("../modules/onboarding/init");
|
|
1100
|
+
await runInit(this);
|
|
1101
|
+
this.slog.logOnboarding("init_done", { model: this.config.model });
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
else if (readEnv("POST_SETUP") === "1" && process.stdout.isTTY === true) {
|
|
1105
|
+
const { preflightFromConfig } = await import("../modules/providers/preflight");
|
|
1106
|
+
const pre = await preflightFromConfig(this);
|
|
1107
|
+
if (pre.ok) {
|
|
1108
|
+
this.output.writeLine(pc.dim(`${t("repl.meet_offer")} ${t("repl.meet_offer_hint")}`));
|
|
1109
|
+
const answer = (await new Promise((resolve) => this.rl.question("[Y/n] ", resolve)))
|
|
1110
|
+
.trim()
|
|
1111
|
+
.toLowerCase();
|
|
1112
|
+
const yes = answer === "" ||
|
|
1113
|
+
answer === "y" ||
|
|
1114
|
+
answer === "yes" ||
|
|
1115
|
+
answer === "да" ||
|
|
1116
|
+
answer === "д";
|
|
1117
|
+
if (yes) {
|
|
1118
|
+
this.slog.logOnboarding("meet_start", { model: this.config.model });
|
|
1119
|
+
const { runMeet } = await import("../modules/onboarding/meet");
|
|
1120
|
+
await runMeet(this, { preflight: async () => pre });
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
this.restoreStartupPrompt();
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
this.output.writeLine(pc.yellow(t("onboarding.provider_down", { cmd: "meet" })));
|
|
1128
|
+
this.output.writeLine(t("onboarding.provider_down_hint", { cmd: "meet" }));
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
catch (err) {
|
|
1133
|
+
this.logger?.debug(`Onboarding startup skipped: ${String(err)}`);
|
|
1134
|
+
this.restoreStartupPrompt();
|
|
1135
|
+
}
|
|
874
1136
|
if (lspEnabled) {
|
|
875
1137
|
this.probeLspBanner()
|
|
876
1138
|
.then((lspSummary) => {
|
|
877
1139
|
if (lspSummary) {
|
|
878
1140
|
const line = `${pc.green(t("repl.agent"))}${pc.yellow(t("repl.lsp_label"))} ${lspSummary}`;
|
|
879
|
-
//
|
|
1141
|
+
// Строкой промпта владеет канал: ручной erase + prompt()-хак не нужен.
|
|
880
1142
|
this.output.writeLine(`${divider()}\n${line}`);
|
|
881
1143
|
}
|
|
882
1144
|
})
|
|
883
1145
|
.catch(() => { });
|
|
884
1146
|
}
|
|
885
1147
|
if ((this.config.provider.entries?.length ?? 0) > 1) {
|
|
886
|
-
// Phase 4.4:
|
|
887
|
-
//
|
|
888
|
-
//
|
|
1148
|
+
// Phase 4.4: фоновый health-пробник всех entries (бесплатные listModels).
|
|
1149
|
+
// Упавшие провайдеры — предупреждениями: фейловер (Phase 4.2) молча
|
|
1150
|
+
// замаскировал бы мёртвый primary.
|
|
889
1151
|
void probeProviders(targetsFromProviders((this.config.provider.entries ?? [])
|
|
890
1152
|
.map((e) => ({
|
|
891
1153
|
name: e.label || e.type,
|
|
@@ -901,26 +1163,22 @@ export class Repl {
|
|
|
901
1163
|
})
|
|
902
1164
|
.catch(() => { });
|
|
903
1165
|
}
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
//
|
|
1166
|
+
// Контекст-пробник (фоном): сверяем контекст, с которым модель реально
|
|
1167
|
+
// загружена, против настроенного окна. Тот же неблокирующий баннер-паттерн,
|
|
1168
|
+
// что у LSP-пробника выше.
|
|
907
1169
|
if (this.contextProbe) {
|
|
908
1170
|
void this.contextProbe
|
|
909
1171
|
.then((probe) => {
|
|
910
1172
|
if (!probe)
|
|
911
1173
|
return;
|
|
912
|
-
|
|
913
|
-
ts: new Date().toISOString(),
|
|
914
|
-
type: "context_probe",
|
|
915
|
-
content: `model=${probe.model} actual=${probe.actual} configured=${this.config.contextWindow}`,
|
|
916
|
-
});
|
|
1174
|
+
// Запись пробника персистит bootstrap (все режимы); здесь только строка баннера.
|
|
917
1175
|
const line = probe.actual < this.config.contextWindow
|
|
918
1176
|
? `${pc.yellow("⚠")} ${t("repl.context_probe_small", { model: probe.model, actual: probe.actual, configured: this.config.contextWindow })}`
|
|
919
1177
|
: probe.actual > this.config.contextWindow
|
|
920
1178
|
? `${pc.dim(t("repl.context_probe_big", { model: probe.model, actual: probe.actual }))}`
|
|
921
1179
|
: null;
|
|
922
1180
|
if (line) {
|
|
923
|
-
//
|
|
1181
|
+
// Строкой промпта владеет канал: ручной erase + prompt()-хак не нужен.
|
|
924
1182
|
this.output.writeLine(`${divider()}\n${line}`);
|
|
925
1183
|
}
|
|
926
1184
|
})
|
|
@@ -928,16 +1186,16 @@ export class Repl {
|
|
|
928
1186
|
}
|
|
929
1187
|
}
|
|
930
1188
|
/**
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
1189
|
+
* Пробнит LSP-серверы, применимые к текущему проекту, и форматирует однострочную
|
|
1190
|
+
* сводку баннера, напр. "✓ typescript (1.2s)" или "✗ css — timeout".
|
|
1191
|
+
* Null, если LSP выключен или серверов нет.
|
|
934
1192
|
*/
|
|
935
1193
|
async probeLspBanner() {
|
|
936
1194
|
const config = this.config.lsp ?? DEFAULT_LSP_CONFIG;
|
|
937
1195
|
try {
|
|
938
1196
|
const lspOnlyLogger = this.logger ? new FileOnlyLogger(this.logger) : undefined;
|
|
939
1197
|
const summary = await probeLspServers(config, this.baseDir, { logger: lspOnlyLogger });
|
|
940
|
-
//
|
|
1198
|
+
// Результаты LSP-пробника — в сессию, для пост-мортем видимости.
|
|
941
1199
|
this.slog.logSessionStart({
|
|
942
1200
|
environment: this.envReport,
|
|
943
1201
|
lspProbe: {
|
|
@@ -966,13 +1224,14 @@ export class Repl {
|
|
|
966
1224
|
return pc.yellow(t("repl.lsp_unknown"));
|
|
967
1225
|
}
|
|
968
1226
|
}
|
|
1227
|
+
/** Останов REPL: сейв истории, шатдаун агента, закрытие лога сессии. */
|
|
969
1228
|
stop() {
|
|
970
1229
|
this.running = false;
|
|
971
1230
|
this.saveHistory();
|
|
972
1231
|
this.agent.shutdown();
|
|
973
1232
|
this.sessionOutput?.dispose();
|
|
974
1233
|
this.sessionOutput = undefined;
|
|
975
|
-
//
|
|
1234
|
+
// Закрываем лог сессии на останове REPL.
|
|
976
1235
|
this.logger?.closeSessionLog();
|
|
977
1236
|
this.rl.close();
|
|
978
1237
|
}
|