micro-models-agent 2.10.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/assets/app.js +22 -4
- package/dist/assets/index.html +29 -0
- package/dist/assets/js/dom.js +2 -1
- package/dist/assets/js/feed.js +38 -26
- package/dist/assets/js/format.js +1 -1
- package/dist/assets/js/rail.js +114 -0
- package/dist/assets/js/settings-controls.js +2 -2
- package/dist/assets/js/settings.js +246 -51
- package/dist/assets/js/store.js +3 -0
- package/dist/assets/js/tabs.js +104 -2
- package/dist/assets/styles.css +215 -66
- package/dist/cli/cache-line.js +1 -0
- package/dist/cli/command-suggest.js +5 -4
- package/dist/cli/commands.js +231 -122
- package/dist/cli/completer.js +132 -31
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-payload.js +4 -4
- package/dist/cli/main.js +166 -61
- package/dist/cli/onboarding-commands.js +126 -0
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/relaunch.js +16 -13
- package/dist/cli/repl-commands-lsp.js +75 -0
- package/dist/cli/repl-commands-skills.js +105 -0
- package/dist/cli/repl-commands.js +589 -266
- package/dist/cli/repl.js +436 -177
- package/dist/cli/run-result.js +5 -6
- package/dist/cli/security-commands.js +37 -28
- package/dist/cli/setup-order.js +4 -55
- package/dist/cli/setup-prompt.js +3 -2
- package/dist/cli/setup.js +27 -107
- package/dist/cli/web-command.js +98 -0
- package/dist/config/budget.js +5 -5
- package/dist/config/config.js +103 -96
- package/dist/config/defaults.js +38 -8
- package/dist/config/domains.js +24 -27
- package/dist/config/env.js +46 -0
- package/dist/config/experts.js +2 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.js +56 -0
- package/dist/config/redact.js +44 -0
- package/dist/config/registry-extra.js +334 -0
- package/dist/config/registry.js +584 -0
- package/dist/config/security.js +35 -36
- package/dist/config/timeouts.js +74 -0
- package/dist/config/utils.js +16 -15
- package/dist/core/agent/artifact-progress.js +59 -0
- package/dist/core/agent/artifact-resolve.js +79 -0
- package/dist/core/agent/audit-gate.js +7 -3
- package/dist/core/agent/compaction.js +2 -2
- package/dist/core/agent/constants.js +33 -4
- package/dist/core/agent/context-log.js +454 -0
- package/dist/core/agent/context-renderer.js +5 -0
- package/dist/core/agent/critic-gate.js +132 -0
- package/dist/core/agent/hallucination-gate.js +148 -38
- package/dist/core/agent/loop-state.js +15 -0
- package/dist/core/agent/prefix-monitor.js +1 -1
- package/dist/core/agent/reasoning-resolver.js +10 -1
- package/dist/core/agent/token-tracker.js +7 -4
- package/dist/core/agent/tool-batch.js +93 -18
- package/dist/core/agent/tool-images.js +18 -0
- package/dist/core/agent/tool-output.js +65 -11
- package/dist/core/agent/tool-pairing.js +60 -0
- package/dist/core/agent-moe.js +25 -24
- package/dist/core/agent.js +429 -108
- package/dist/core/bootstrap/agent-phase.js +66 -0
- package/dist/core/bootstrap/config-phase.js +86 -0
- package/dist/core/bootstrap/modules-phase.js +269 -0
- package/dist/core/bootstrap/provider-phase.js +183 -0
- package/dist/core/bootstrap/session-phase.js +204 -0
- package/dist/core/bootstrap/state.js +94 -0
- package/dist/core/bootstrap/workspace-phase.js +115 -0
- package/dist/core/bootstrap.js +92 -660
- package/dist/core/crash-handler.js +17 -7
- package/dist/core/environment.js +37 -12
- package/dist/core/host-plan.js +43 -0
- package/dist/core/host.js +23 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompt-builder.js +17 -1
- package/dist/core/prompt-overflow.js +42 -43
- package/dist/core/session-logger.js +122 -30
- package/dist/core/system-prompt.js +117 -0
- package/dist/core/version.js +6 -4
- package/dist/core/web-host.js +34 -0
- package/dist/core/workspace.js +19 -15
- package/dist/i18n/en.json +106 -21
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +109 -21
- package/dist/index.js +7 -3
- package/dist/llm/image-utils.js +55 -64
- package/dist/llm/llm-errors.js +9 -0
- package/dist/llm/model-loader.js +10 -32
- package/dist/llm/models-dev.js +121 -0
- package/dist/llm/openai-compat.js +146 -73
- package/dist/llm/orchestrator.js +33 -11
- package/dist/llm/provider-budget.js +2 -2
- package/dist/llm/provider-context.js +233 -0
- package/dist/llm/provider.js +1 -1
- package/dist/llm/response.js +5 -0
- package/dist/llm/token-counter.js +27 -11
- package/dist/logger/app-logger.js +48 -22
- package/dist/logger/file-log.js +28 -19
- package/dist/logger/index.js +1 -0
- package/dist/main.js +24285 -22447
- package/dist/migration/backup.js +14 -3
- package/dist/migration/detect.js +12 -3
- package/dist/migration/index.js +1 -0
- package/dist/modules/artifacts/store.js +9 -8
- package/dist/modules/browser/actions.js +6 -0
- package/dist/modules/browser/bridge-client.js +14 -12
- package/dist/modules/browser/bridge-path.js +4 -4
- package/dist/modules/browser/console-buffer.js +37 -0
- package/dist/modules/browser/cookie-store.js +5 -0
- package/dist/modules/browser/driver-types.js +1 -0
- package/dist/modules/browser/driver.js +19 -11
- package/dist/modules/browser/index.js +3 -1
- package/dist/modules/browser/module.js +2 -1
- package/dist/modules/browser/session.js +10 -30
- package/dist/modules/browser/snapshot.js +19 -13
- package/dist/modules/browser/types.js +1 -0
- package/dist/modules/certification/cli.js +28 -20
- package/dist/modules/certification/fact-checker.js +9 -1
- package/dist/modules/certification/loader.js +9 -2
- package/dist/modules/certification/manifest.js +83 -21
- package/dist/modules/certification/runner.js +23 -13
- package/dist/modules/certification/scenarios.js +20 -20
- package/dist/modules/certification/syntax-scenarios.js +1 -2
- package/dist/modules/context/chunk-query.js +11 -9
- package/dist/modules/context/fact-extractor.js +37 -39
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +106 -80
- package/dist/modules/critic/client.js +176 -0
- package/dist/modules/critic/evidence.js +81 -0
- package/dist/modules/critic/provider.js +45 -0
- package/dist/modules/critic/tools.js +78 -0
- package/dist/modules/critic/types.js +1 -0
- package/dist/modules/execution/audit-runners.js +28 -34
- package/dist/modules/execution/auditor.js +53 -49
- package/dist/modules/execution/execution-plugin.js +142 -102
- package/dist/modules/execution/index.js +1 -1
- package/dist/modules/execution/input-from.js +37 -13
- package/dist/modules/execution/module.js +368 -191
- package/dist/modules/execution/moe-executor.js +53 -26
- package/dist/modules/execution/plan-actions.js +320 -202
- package/dist/modules/execution/plan-checks.js +35 -0
- package/dist/modules/execution/plan-coverage.js +36 -33
- package/dist/modules/execution/plan-steps.js +11 -3
- package/dist/modules/execution/plan-store.js +83 -17
- package/dist/modules/execution/plan-tool.js +89 -35
- package/dist/modules/execution/plan-validator.js +30 -10
- package/dist/modules/execution/planner.js +22 -13
- package/dist/modules/execution/stuck-detector.js +196 -93
- package/dist/modules/execution/tracker.js +22 -8
- package/dist/modules/execution/verifier.js +69 -48
- package/dist/modules/execution/windows-commands.js +12 -12
- package/dist/modules/hallucination/artifact-claim.js +93 -0
- package/dist/modules/hallucination/confidence.js +13 -11
- package/dist/modules/hallucination/consistency.js +10 -2
- package/dist/modules/hallucination/detector.js +10 -5
- package/dist/modules/hallucination/factual.js +43 -26
- package/dist/modules/hallucination/index.js +1 -0
- package/dist/modules/hallucination/js-identifiers.js +44 -45
- package/dist/modules/hallucination/llm-judge.js +17 -12
- package/dist/modules/hallucination/success-claim.js +59 -0
- package/dist/modules/indexer/cache.js +52 -4
- package/dist/modules/indexer/index.js +1 -0
- package/dist/modules/indexer/map-command.js +2 -4
- package/dist/modules/indexer/map-select.js +16 -16
- package/dist/modules/indexer/module.js +47 -12
- package/dist/modules/indexer/project-profile.js +8 -7
- package/dist/modules/indexer/symbols.js +15 -15
- package/dist/modules/indexer/walker.js +159 -29
- package/dist/modules/lsp/check-tool.js +7 -5
- package/dist/modules/lsp/client.js +39 -39
- package/dist/modules/lsp/command.js +17 -24
- package/dist/modules/lsp/config.js +12 -11
- package/dist/modules/lsp/index.js +1 -0
- package/dist/modules/lsp/module.js +48 -46
- package/dist/modules/lsp/probe.js +13 -14
- package/dist/modules/lsp/project-root.js +14 -17
- package/dist/modules/lsp/startup-check.js +18 -16
- package/dist/modules/mcp/client.js +59 -62
- package/dist/modules/mcp/index.js +1 -0
- package/dist/modules/mcp/module.js +66 -37
- package/dist/modules/mcp/registry.js +5 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +12 -8
- package/dist/modules/memory/search.js +4 -1
- package/dist/modules/memory/store.js +40 -2
- package/dist/modules/onboarding/init.js +47 -0
- package/dist/modules/onboarding/meet.js +30 -0
- package/dist/modules/onboarding/mode.js +30 -0
- package/dist/modules/onboarding/prompts.js +43 -0
- package/dist/modules/pipelines/engine.js +10 -0
- package/dist/modules/pipelines/index.js +1 -0
- package/dist/modules/pipelines/parser.js +4 -1
- package/dist/modules/pipelines/template.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
- package/dist/modules/plugins/builtin/notify.js +2 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +18 -3
- package/dist/modules/plugins/manager.js +37 -17
- package/dist/modules/pricing/index.js +10 -11
- package/dist/modules/pricing/prices.js +12 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +27 -31
- package/dist/modules/processes/registry.js +44 -25
- package/dist/modules/processes/runner.js +4 -4
- package/dist/modules/providers/capabilities.js +28 -0
- package/dist/modules/providers/create.js +3 -4
- package/dist/modules/providers/factory.js +7 -7
- package/dist/modules/providers/fallback.js +12 -12
- package/dist/modules/providers/health.js +4 -4
- package/dist/modules/providers/manager.js +50 -19
- package/dist/modules/providers/naming.js +15 -0
- package/dist/modules/providers/preflight.js +71 -0
- package/dist/modules/providers/presets.js +39 -8
- package/dist/modules/providers/registry.js +7 -2
- package/dist/modules/reasoning/policy.js +8 -6
- package/dist/modules/reasoning/probe.js +30 -25
- package/dist/modules/security/audit-log.js +19 -36
- package/dist/modules/security/audit-notifier.js +35 -75
- package/dist/modules/security/command-validator.js +99 -51
- package/dist/modules/security/content-scanner.js +8 -8
- package/dist/modules/security/data-sanitizer.js +9 -16
- package/dist/modules/security/encryption.js +32 -52
- package/dist/modules/security/index.js +2 -1
- package/dist/modules/security/network-validator.js +120 -43
- package/dist/modules/security/path-validator.js +61 -46
- package/dist/modules/security/protected-paths.js +73 -0
- package/dist/modules/security/rate-limiter.js +17 -49
- package/dist/modules/security/security-policies.js +25 -57
- package/dist/modules/security/session-encryption.js +32 -71
- package/dist/modules/security/session-isolation.js +15 -28
- package/dist/modules/session/auto-name.js +63 -0
- package/dist/modules/session/export.js +98 -0
- package/dist/modules/session/index.js +1 -0
- package/dist/modules/session/manager.js +79 -28
- package/dist/modules/session/module.js +4 -3
- package/dist/modules/session/store.js +57 -20
- package/dist/modules/setup/answers.js +35 -0
- package/dist/modules/setup/apply.js +79 -0
- package/dist/modules/setup/discovery.js +72 -0
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/loader.js +9 -1
- package/dist/modules/skills/module.js +11 -0
- package/dist/modules/updater/changelog-reader.js +15 -16
- package/dist/modules/updater/checker.js +9 -4
- package/dist/modules/updater/dev-detect.js +7 -6
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +17 -15
- package/dist/modules/user-profile/compressor.js +2 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +10 -1
- package/dist/modules/webui/answer-api.js +29 -0
- package/dist/modules/webui/assets.js +50 -0
- package/dist/modules/webui/body.js +21 -0
- package/dist/modules/webui/commands-settings.js +209 -0
- package/dist/modules/webui/commands-shared.js +60 -0
- package/dist/modules/webui/commands.js +174 -0
- package/dist/modules/webui/config-api.js +134 -0
- package/dist/modules/webui/file-api.js +202 -0
- package/dist/modules/webui/forward.js +159 -0
- package/dist/modules/webui/history-api.js +112 -0
- package/dist/modules/webui/host-state.js +60 -0
- package/dist/modules/webui/i18n-api.js +9 -0
- package/dist/modules/webui/limits.js +6 -0
- package/dist/modules/webui/log-forward.js +20 -0
- package/dist/modules/webui/memory-commands.js +64 -0
- package/dist/modules/webui/model-cache.js +18 -0
- package/dist/modules/webui/module.js +148 -0
- package/dist/modules/webui/persist.js +24 -0
- package/dist/modules/webui/prompt-io.js +97 -0
- package/dist/modules/webui/providers-api.js +337 -0
- package/dist/modules/webui/providers-view.js +56 -0
- package/dist/modules/webui/reasoning-view.js +15 -0
- package/dist/modules/webui/run-agent.js +81 -0
- package/dist/modules/webui/server.js +284 -0
- package/dist/modules/webui/sessions-api.js +178 -0
- package/dist/modules/webui/settings-api.js +561 -0
- package/dist/modules/webui/setup-api.js +201 -0
- package/dist/modules/webui/state.js +74 -0
- package/dist/modules/webui/util.js +261 -0
- package/dist/output/bus.js +7 -2
- package/dist/output/channel.js +80 -28
- package/dist/output/format.js +2 -1
- package/dist/output/index.js +1 -0
- package/dist/output/json-sink.js +5 -3
- package/dist/output/machine.js +4 -3
- package/dist/output/session-sink.js +4 -4
- package/dist/tools/approve.js +4 -0
- package/dist/tools/attach-image.js +5 -4
- package/dist/tools/auto-fixer.js +26 -44
- package/dist/tools/auto-tags.js +83 -0
- package/dist/tools/bash.js +193 -116
- package/dist/tools/browser.js +76 -31
- package/dist/tools/chunk-query.js +14 -6
- package/dist/tools/create-dir.js +9 -5
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/download-file.js +32 -7
- package/dist/tools/edit-file.js +52 -14
- package/dist/tools/enable-tools.js +6 -6
- package/dist/tools/executor.js +132 -27
- package/dist/tools/file-info.js +3 -1
- package/dist/tools/filter-tools.js +3 -3
- package/dist/tools/glob-tool.js +4 -1
- package/dist/tools/grep-tool.js +13 -10
- package/dist/tools/hidden-tools-block.js +6 -6
- package/dist/tools/index.js +16 -8
- package/dist/tools/list-dir.js +4 -2
- package/dist/tools/load-skill.js +7 -0
- package/dist/tools/mcp-call.js +15 -4
- package/dist/tools/meet-done.js +14 -0
- package/dist/tools/move-file.js +14 -7
- package/dist/tools/path-utils.js +29 -29
- package/dist/tools/pipeline-run.js +29 -7
- package/dist/tools/preview.js +1 -1
- package/dist/tools/process-ids.js +31 -0
- package/dist/tools/process-kill.js +9 -16
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +11 -16
- package/dist/tools/process-wait.js +63 -0
- package/dist/tools/prompt-io.js +4 -3
- package/dist/tools/question.js +172 -25
- package/dist/tools/read-file.js +55 -34
- package/dist/tools/recall.js +14 -8
- package/dist/tools/registry.js +6 -5
- package/dist/tools/remember.js +8 -4
- package/dist/tools/scope-request.js +11 -6
- package/dist/tools/search-history.js +5 -2
- package/dist/tools/session-info.js +2 -0
- package/dist/tools/set-thinking.js +8 -4
- package/dist/tools/subagent.js +59 -39
- package/dist/tools/syntax-validator.js +292 -90
- package/dist/tools/user-input.js +22 -18
- package/dist/tools/web-browse.js +9 -3
- package/dist/tools/web-fetch.js +12 -23
- package/dist/tools/web-search.js +7 -5
- package/dist/tools/write-file.js +45 -15
- package/dist/ui/action-log.js +13 -0
- package/dist/ui/box.js +4 -3
- package/dist/ui/colors.js +2 -0
- package/dist/ui/diff.js +6 -5
- package/dist/ui/index.js +1 -0
- package/dist/ui/line-editor.js +230 -122
- package/dist/ui/line-math.js +10 -2
- package/dist/ui/md-formatter.js +57 -37
- package/dist/ui/output.js +8 -3
- package/dist/ui/plan-view.js +21 -10
- package/dist/ui/renderer.js +393 -67
- package/dist/ui/spinner.js +16 -5
- package/dist/ui/table.js +15 -17
- package/dist/ui/theme.js +143 -0
- package/dist/utils/child-run.js +20 -0
- package/dist/utils/error.js +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/retry.js +2 -2
- package/dist/utils/truncate.js +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,29 @@
|
|
|
1
|
+
// Slash-команды REPL: /config, /model, /provider, сессии, сахар над mutateConfig.
|
|
1
2
|
import { pc } from "../ui/colors";
|
|
3
|
+
import { resolveVerbosity } from "../ui/theme";
|
|
2
4
|
import { readMmaVersion } from "../core/version";
|
|
3
5
|
import { renderTable } from "../ui/table";
|
|
4
|
-
import { t } from "../i18n/index";
|
|
6
|
+
import { t, setLocale } from "../i18n/index";
|
|
5
7
|
import { runSetup } from "./setup";
|
|
8
|
+
import { applyAnswersToConfig } from "./setup-order";
|
|
6
9
|
import { saveConfig } from "../config/config";
|
|
7
10
|
import { HOSTED_BASE_URLS } from "../modules/providers/presets";
|
|
8
|
-
import {
|
|
11
|
+
import { providerNameTaken } from "../modules/providers/naming";
|
|
12
|
+
import { activeProviderEndpoint } from "../modules/providers/manager";
|
|
13
|
+
import { budgetBreakdownLines } from "../config/budget";
|
|
14
|
+
import { setConfigValue, getConfigValue, resolveConfigKey, canonicalKeys, keysForGroup, configDefs, formatConfigValue, } from "../config/registry";
|
|
15
|
+
import { formatExport, redactExport, sanitizeFileName, extensionFor, } from "../modules/session/export";
|
|
9
16
|
import { getMessageText } from "../llm/provider";
|
|
10
|
-
import { estimateTokens } from "../llm/token-counter";
|
|
11
17
|
import { join, dirname } from "path";
|
|
12
|
-
import { existsSync } from "fs";
|
|
18
|
+
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
13
19
|
import { runMapAction } from "../modules/indexer/map-command";
|
|
20
|
+
import { registerOnboardingCommands } from "./onboarding-commands";
|
|
14
21
|
import { errMsg } from "../utils";
|
|
22
|
+
import { globalExportsDir } from "../config/paths";
|
|
23
|
+
import { registerLspCommands } from "./repl-commands-lsp";
|
|
24
|
+
import { registerSkillCommands } from "./repl-commands-skills";
|
|
15
25
|
const version = readMmaVersion();
|
|
26
|
+
/** Группы команд для `/help` (general/agent/session/skill). */
|
|
16
27
|
export const COMMAND_GROUPS = {
|
|
17
28
|
help: "general",
|
|
18
29
|
exit: "general",
|
|
@@ -22,25 +33,33 @@ export const COMMAND_GROUPS = {
|
|
|
22
33
|
config: "agent",
|
|
23
34
|
status: "agent",
|
|
24
35
|
reasoning: "agent",
|
|
36
|
+
verbose: "agent",
|
|
25
37
|
provider: "agent",
|
|
26
38
|
model: "agent",
|
|
27
39
|
context: "agent",
|
|
28
40
|
reload: "agent",
|
|
29
41
|
wizard: "agent",
|
|
42
|
+
meet: "agent",
|
|
43
|
+
init: "agent",
|
|
44
|
+
memory: "agent",
|
|
30
45
|
sysprompt: "agent",
|
|
31
46
|
map: "agent",
|
|
32
47
|
lsp: "agent",
|
|
48
|
+
security: "agent",
|
|
33
49
|
sessions: "session",
|
|
34
50
|
new: "session",
|
|
35
51
|
resume: "session",
|
|
36
52
|
rename: "session",
|
|
37
53
|
delete: "session",
|
|
54
|
+
export: "session",
|
|
38
55
|
skill: "skill",
|
|
39
56
|
plugins: "agent",
|
|
40
57
|
};
|
|
58
|
+
/** Регистрирует все команды REPL (builtin + mma + онбординг + сессии + скиллы). */
|
|
41
59
|
export function registerAllCommands(ctx) {
|
|
42
60
|
registerBuiltinCommands(ctx);
|
|
43
61
|
registerMmaCommands(ctx);
|
|
62
|
+
registerOnboardingCommands(ctx);
|
|
44
63
|
registerSessionCommands(ctx);
|
|
45
64
|
registerSkillCommands(ctx);
|
|
46
65
|
}
|
|
@@ -49,7 +68,8 @@ function registerBuiltinCommands(ctx) {
|
|
|
49
68
|
name: "help",
|
|
50
69
|
description: t("repl.help"),
|
|
51
70
|
usage: t("repl.help_usage"),
|
|
52
|
-
|
|
71
|
+
examples: ["/help", "/help config"],
|
|
72
|
+
action: (args) => ctx.showHelp(args[0]),
|
|
53
73
|
});
|
|
54
74
|
ctx.registerCommand({
|
|
55
75
|
name: "exit",
|
|
@@ -64,6 +84,7 @@ function registerBuiltinCommands(ctx) {
|
|
|
64
84
|
usage: t("repl.clear_usage"),
|
|
65
85
|
action: () => {
|
|
66
86
|
ctx.output.clearScreen();
|
|
87
|
+
ctx.output.writeLine(pc.dim(t("repl.clear_hint")));
|
|
67
88
|
},
|
|
68
89
|
});
|
|
69
90
|
}
|
|
@@ -142,40 +163,292 @@ function registerRunCommands(ctx) {
|
|
|
142
163
|
},
|
|
143
164
|
});
|
|
144
165
|
}
|
|
166
|
+
const SUGAR_HINTS = {
|
|
167
|
+
model: "/model use <name>",
|
|
168
|
+
"provider.active": "/provider use <name>",
|
|
169
|
+
contextWindow: "/context <size>",
|
|
170
|
+
"contextBudget.systemPrompt": "/context system <share>",
|
|
171
|
+
"contextBudget.responseReserve": "/context reserve <share>",
|
|
172
|
+
showReasoning: "/reasoning show | hide",
|
|
173
|
+
"reasoning.mode": "/reasoning level <level>",
|
|
174
|
+
"ui.verbosity": "/verbose <level>",
|
|
175
|
+
};
|
|
176
|
+
function changeHint(def) {
|
|
177
|
+
if (def.readOnly)
|
|
178
|
+
return t("config.read_only_hint");
|
|
179
|
+
return SUGAR_HINTS[def.key] ?? `/config ${def.key} <value>`;
|
|
180
|
+
}
|
|
181
|
+
function configValueHint(def) {
|
|
182
|
+
if (!def)
|
|
183
|
+
return "";
|
|
184
|
+
switch (def.valueType) {
|
|
185
|
+
case "enum":
|
|
186
|
+
return t("config.hint_enum", { values: (def.enumValues ?? []).join(", ") });
|
|
187
|
+
case "bool":
|
|
188
|
+
return t("config.hint_bool");
|
|
189
|
+
case "share":
|
|
190
|
+
return t("config.hint_share");
|
|
191
|
+
case "int":
|
|
192
|
+
return t("config.hint_int", { min: String(def.min ?? 1) });
|
|
193
|
+
default:
|
|
194
|
+
return t("config.hint_string");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function editDistance(a, b) {
|
|
198
|
+
const dp = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
199
|
+
for (let i = 1; i <= a.length; i++) {
|
|
200
|
+
let prev = dp[0];
|
|
201
|
+
dp[0] = i;
|
|
202
|
+
for (let j = 1; j <= b.length; j++) {
|
|
203
|
+
const tmp = dp[j];
|
|
204
|
+
dp[j] = Math.min(dp[j] + 1, dp[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
205
|
+
prev = tmp;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return dp[b.length];
|
|
209
|
+
}
|
|
210
|
+
function nearestConfigKey(input) {
|
|
211
|
+
const names = canonicalKeys();
|
|
212
|
+
let best = null;
|
|
213
|
+
for (const name of names) {
|
|
214
|
+
const d = editDistance(input.toLowerCase(), name.toLowerCase());
|
|
215
|
+
if (best === null || d < best.dist)
|
|
216
|
+
best = { name, dist: d };
|
|
217
|
+
}
|
|
218
|
+
if (!best)
|
|
219
|
+
return null;
|
|
220
|
+
const threshold = Math.max(2, Math.floor(best.name.length / 3));
|
|
221
|
+
return best.dist <= threshold ? best.name : null;
|
|
222
|
+
}
|
|
223
|
+
function renderConfigError(ctx, result) {
|
|
224
|
+
const key = result.key ?? result.input;
|
|
225
|
+
switch (result.reason) {
|
|
226
|
+
case "unknown-key": {
|
|
227
|
+
const suggestion = nearestConfigKey(result.input);
|
|
228
|
+
const suffix = suggestion
|
|
229
|
+
? t("config.suggest", { name: suggestion })
|
|
230
|
+
: t("config.valid_keys", { keys: canonicalKeys().join(", ") });
|
|
231
|
+
ctx.output.writeLine(pc.yellow(t("config.unknown_key", { key: result.input, suggestion: suffix })), "stderr");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
case "read-only":
|
|
235
|
+
ctx.output.writeLine(pc.yellow(t("config.read_only", { key })), "stderr");
|
|
236
|
+
return;
|
|
237
|
+
case "range":
|
|
238
|
+
ctx.output.writeLine(pc.yellow(t("config.range", { key })), "stderr");
|
|
239
|
+
return;
|
|
240
|
+
case "combined":
|
|
241
|
+
ctx.output.writeLine(pc.yellow(t("config.combined")), "stderr");
|
|
242
|
+
return;
|
|
243
|
+
default:
|
|
244
|
+
ctx.output.writeLine(pc.yellow(t("config.invalid_value", { key, hint: configValueHint(result.def) })), "stderr");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function applyConfigSideEffects(ctx, result) {
|
|
248
|
+
if (result.key === "ui.verbosity") {
|
|
249
|
+
// Зеркало стартового правила (`main.ts`): структурные info-логи и диагностика
|
|
250
|
+
// петли видимы в чате только в verbose.
|
|
251
|
+
const verbose = result.value === "verbose";
|
|
252
|
+
ctx.output.setSuppressInfoLogs(!verbose);
|
|
253
|
+
ctx.output.setShowDiagnostics(verbose);
|
|
254
|
+
}
|
|
255
|
+
else if (result.key === "reasoning.mode") {
|
|
256
|
+
ctx.agent.setReasoningLevel(result.value);
|
|
257
|
+
}
|
|
258
|
+
else if (result.key === "locale") {
|
|
259
|
+
setLocale(result.value);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function persistConfig(ctx) {
|
|
263
|
+
const configPath = join(ctx.configDir, "config.json");
|
|
264
|
+
saveConfig(ctx.config, configPath, dirname(configPath));
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Единый путь записи каждой мутации настроек. Сахарные команды тоже идут сюда,
|
|
268
|
+
* чтобы валидация, сайд-эффекты, персист и reconfigure не расходились.
|
|
269
|
+
*/
|
|
270
|
+
async function mutateConfig(ctx, key, raw) {
|
|
271
|
+
const result = setConfigValue(ctx.config, key, raw);
|
|
272
|
+
if (!result.ok)
|
|
273
|
+
return result;
|
|
274
|
+
applyConfigSideEffects(ctx, result);
|
|
275
|
+
persistConfig(ctx);
|
|
276
|
+
if (result.reconfigure)
|
|
277
|
+
await ctx.agent.reconfigure(ctx.config);
|
|
278
|
+
if (result.key === "locale")
|
|
279
|
+
ctx.reregisterCommands();
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
function reportSaved(ctx, result) {
|
|
283
|
+
ctx.output.writeLine(pc.green(t("config.saved", { key: result.key })));
|
|
284
|
+
}
|
|
285
|
+
function rawFromValue(value) {
|
|
286
|
+
if (typeof value === "boolean")
|
|
287
|
+
return value ? "on" : "off";
|
|
288
|
+
return String(value);
|
|
289
|
+
}
|
|
290
|
+
function printConfigTable(ctx, defs) {
|
|
291
|
+
const list = defs ?? configDefs();
|
|
292
|
+
const rows = list.map((def) => [
|
|
293
|
+
def.key,
|
|
294
|
+
formatConfigValue(def.get(ctx.config)),
|
|
295
|
+
def.domain,
|
|
296
|
+
changeHint(def),
|
|
297
|
+
]);
|
|
298
|
+
ctx.output.writeLine(pc.bold(t("config.header")));
|
|
299
|
+
for (const line of renderTable([
|
|
300
|
+
t("config.col_setting"),
|
|
301
|
+
t("config.col_value"),
|
|
302
|
+
t("config.col_domain"),
|
|
303
|
+
t("config.col_change"),
|
|
304
|
+
], rows)) {
|
|
305
|
+
ctx.output.writeLine(line);
|
|
306
|
+
}
|
|
307
|
+
const meta = ctx.sessionManager?.getActiveMeta();
|
|
308
|
+
if (meta) {
|
|
309
|
+
ctx.output.writeLine(pc.dim(t("config.session_line", {
|
|
310
|
+
name: meta.name,
|
|
311
|
+
id: meta.id,
|
|
312
|
+
count: String(meta.messageCount),
|
|
313
|
+
})));
|
|
314
|
+
}
|
|
315
|
+
ctx.output.writeLine(pc.dim(t("config.usage_hint")));
|
|
316
|
+
}
|
|
317
|
+
function confirm(ctx, prompt) {
|
|
318
|
+
return new Promise((resolve) => {
|
|
319
|
+
void ctx.withExclusiveInput(async () => {
|
|
320
|
+
await new Promise((done) => {
|
|
321
|
+
ctx.rl.question(prompt, (answer) => {
|
|
322
|
+
resolve(/^y(es)?$/i.test(answer.trim()));
|
|
323
|
+
done();
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
async function runConfigReset(ctx, rest) {
|
|
330
|
+
const snapshot = ctx.getConfigSnapshot();
|
|
331
|
+
const yes = rest.includes("--yes");
|
|
332
|
+
const all = rest.includes("--all");
|
|
333
|
+
const target = rest.find((a) => !a.startsWith("--"));
|
|
334
|
+
if (all) {
|
|
335
|
+
const changed = canonicalKeys().filter((k) => {
|
|
336
|
+
const def = resolveConfigKey(k);
|
|
337
|
+
if (!def || !snapshot.has(k))
|
|
338
|
+
return false;
|
|
339
|
+
return rawFromValue(def.get(ctx.config)) !== rawFromValue(snapshot.get(k));
|
|
340
|
+
});
|
|
341
|
+
if (changed.length === 0) {
|
|
342
|
+
ctx.output.writeLine(t("config.reset_none"));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (!yes) {
|
|
346
|
+
if (!process.stdin.isTTY) {
|
|
347
|
+
ctx.output.writeLine(pc.yellow(t("config.reset_all_needs_yes")), "stderr");
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const ok = await confirm(ctx, t("config.reset_all_confirm", { count: String(changed.length) }));
|
|
351
|
+
if (!ok) {
|
|
352
|
+
ctx.output.writeLine(t("config.reset_cancelled"));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
let anyReconfig = false;
|
|
357
|
+
let localeChanged = false;
|
|
358
|
+
for (const k of changed) {
|
|
359
|
+
const result = setConfigValue(ctx.config, k, rawFromValue(snapshot.get(k)));
|
|
360
|
+
if (result.ok) {
|
|
361
|
+
applyConfigSideEffects(ctx, result);
|
|
362
|
+
anyReconfig = anyReconfig || result.reconfigure;
|
|
363
|
+
localeChanged = localeChanged || result.key === "locale";
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
persistConfig(ctx);
|
|
367
|
+
if (anyReconfig)
|
|
368
|
+
await ctx.agent.reconfigure(ctx.config);
|
|
369
|
+
if (localeChanged)
|
|
370
|
+
ctx.reregisterCommands();
|
|
371
|
+
ctx.output.writeLine(pc.green(t("config.reset_all_done", { count: String(changed.length) })));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (!target) {
|
|
375
|
+
ctx.output.writeLine(t("config.reset_usage"));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const def = resolveConfigKey(target);
|
|
379
|
+
if (!def) {
|
|
380
|
+
renderConfigError(ctx, { reason: "unknown-key", input: target });
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (def.readOnly || !snapshot.has(def.key)) {
|
|
384
|
+
ctx.output.writeLine(t("config.reset_unchanged", { key: def.key }));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (rawFromValue(def.get(ctx.config)) === rawFromValue(snapshot.get(def.key))) {
|
|
388
|
+
ctx.output.writeLine(t("config.reset_unchanged", { key: def.key }));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const result = await mutateConfig(ctx, def.key, rawFromValue(snapshot.get(def.key)));
|
|
392
|
+
if (!result.ok) {
|
|
393
|
+
renderConfigError(ctx, result);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
ctx.output.writeLine(pc.green(t("config.reset_ok", { key: result.key, value: result.display })));
|
|
397
|
+
}
|
|
398
|
+
function printReasoningStatus(ctx) {
|
|
399
|
+
const display = ctx.config.showReasoning ? t("reasoning.on") : t("reasoning.off");
|
|
400
|
+
const mode = ctx.config.reasoning?.mode ?? "auto";
|
|
401
|
+
const auto = mode === "auto" ? t("reasoning.auto_on") : t("reasoning.auto_off");
|
|
402
|
+
ctx.output.writeLine(t("reasoning.status", { display, level: mode, auto }));
|
|
403
|
+
}
|
|
145
404
|
function registerConfigCommands(ctx) {
|
|
146
405
|
ctx.registerCommand({
|
|
147
406
|
name: "config",
|
|
148
407
|
description: t("repl.config"),
|
|
149
408
|
usage: t("repl.config_usage"),
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
//
|
|
409
|
+
examples: ["/config", "/config model", "/config reasoning.mode high", "/config reset ui.verbosity"],
|
|
410
|
+
action: async (args) => {
|
|
411
|
+
// Роутинг субкоманды `/config migrate`. Отдельная регистрация с пробелом в
|
|
412
|
+
// имени недостижима: диспетчер режет ввод по пробелам, и "/config migrate"
|
|
413
|
+
// всегда резолвился в plain "config".
|
|
154
414
|
if ((args[0] ?? "").trim().toLowerCase() === "migrate") {
|
|
155
415
|
return runConfigMigrate(ctx);
|
|
156
416
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const res = Math.floor(ctxW * cfg.contextBudget.responseReserve);
|
|
161
|
-
ctx.output.writeLine(`${t("repl.model")} ${cfg.model}`);
|
|
162
|
-
const providers = ctx.agent.listProviders();
|
|
163
|
-
const active = providers.find((p) => p.active);
|
|
164
|
-
if (active) {
|
|
165
|
-
ctx.output.writeLine(`${t("repl.provider")} ${active.label} (${pc.dim(active.type)}) → ${pc.dim(active.baseUrl)}`);
|
|
417
|
+
if (args.length === 0) {
|
|
418
|
+
printConfigTable(ctx);
|
|
419
|
+
return;
|
|
166
420
|
}
|
|
167
|
-
|
|
168
|
-
ctx.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
421
|
+
if (args[0] === "reset") {
|
|
422
|
+
await runConfigReset(ctx, args.slice(1));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const name = args[0];
|
|
426
|
+
if (args.length === 1) {
|
|
427
|
+
const group = keysForGroup(name);
|
|
428
|
+
if (group.length > 0) {
|
|
429
|
+
printConfigTable(ctx, group);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const got = getConfigValue(ctx.config, name);
|
|
433
|
+
if (!got.ok) {
|
|
434
|
+
renderConfigError(ctx, got);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
ctx.output.writeLine(`${got.key} = ${got.display}`);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const raw = args.slice(1).join(" ");
|
|
441
|
+
const result = await mutateConfig(ctx, name, raw);
|
|
442
|
+
if (!result.ok) {
|
|
443
|
+
renderConfigError(ctx, result);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
reportSaved(ctx, result);
|
|
447
|
+
if (result.key === "model") {
|
|
448
|
+
const known = ctx.knownModels();
|
|
449
|
+
if (known.length > 0 && !known.includes(String(result.value))) {
|
|
450
|
+
ctx.output.writeLine(pc.yellow(t("model.not_in_list", { name: String(result.value) })));
|
|
451
|
+
}
|
|
179
452
|
}
|
|
180
453
|
},
|
|
181
454
|
});
|
|
@@ -183,23 +456,60 @@ function registerConfigCommands(ctx) {
|
|
|
183
456
|
name: "reasoning",
|
|
184
457
|
description: t("repl.reasoning"),
|
|
185
458
|
usage: t("repl.reasoning_usage"),
|
|
186
|
-
|
|
459
|
+
examples: ["/reasoning", "/reasoning show", "/reasoning level high"],
|
|
460
|
+
action: async (args) => {
|
|
461
|
+
const sub = args[0]?.trim().toLowerCase();
|
|
462
|
+
if (!sub) {
|
|
463
|
+
printReasoningStatus(ctx);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (sub === "show" || sub === "on" || sub === "hide" || sub === "off") {
|
|
467
|
+
const on = sub === "show" || sub === "on";
|
|
468
|
+
const result = await mutateConfig(ctx, "showReasoning", on ? "on" : "off");
|
|
469
|
+
if (!result.ok) {
|
|
470
|
+
renderConfigError(ctx, result);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
ctx.output.writeLine(pc.green(t("reasoning.display_set", { state: on ? t("reasoning.on") : t("reasoning.off") })));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (sub === "level") {
|
|
477
|
+
const lvl = args[1]?.trim().toLowerCase();
|
|
478
|
+
if (!lvl) {
|
|
479
|
+
ctx.output.writeLine(t("repl.reasoning_usage"));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const result = await mutateConfig(ctx, "reasoning.mode", lvl);
|
|
483
|
+
if (!result.ok) {
|
|
484
|
+
renderConfigError(ctx, result);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
ctx.output.writeLine(pc.green(t("repl.reasoning_level_set", { level: lvl })));
|
|
488
|
+
if (lvl !== "auto")
|
|
489
|
+
ctx.output.writeLine(pc.dim(t("reasoning.auto_off_hint")));
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
ctx.output.writeLine(pc.yellow(t("reasoning.unknown_sub", { sub })));
|
|
493
|
+
ctx.output.writeLine(t("repl.reasoning_usage"));
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
ctx.registerCommand({
|
|
497
|
+
name: "verbose",
|
|
498
|
+
description: t("repl.verbose"),
|
|
499
|
+
usage: t("repl.verbose_usage"),
|
|
500
|
+
examples: ["/verbose", "/verbose normal"],
|
|
501
|
+
action: async (args) => {
|
|
187
502
|
const level = args[0]?.trim().toLowerCase();
|
|
188
|
-
const VALID_LEVELS = ["auto", "none", "low", "medium", "high", "max"];
|
|
189
503
|
if (!level) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const status = ctx.config.showReasoning ? pc.green(t("repl.show")) : pc.dim(t("repl.hide"));
|
|
193
|
-
ctx.output.writeLine(t("repl.reasoning_status", { status }));
|
|
194
|
-
}
|
|
195
|
-
else if (VALID_LEVELS.includes(level)) {
|
|
196
|
-
// Set reasoning effort level
|
|
197
|
-
ctx.agent.setReasoningLevel(level);
|
|
198
|
-
ctx.output.writeLine(pc.green(t("repl.reasoning_level_set", { level })));
|
|
504
|
+
ctx.output.writeLine(t("verbose.status", { level: resolveVerbosity(ctx.config.ui) }));
|
|
505
|
+
return;
|
|
199
506
|
}
|
|
200
|
-
|
|
201
|
-
|
|
507
|
+
const result = await mutateConfig(ctx, "ui.verbosity", level);
|
|
508
|
+
if (!result.ok) {
|
|
509
|
+
renderConfigError(ctx, result);
|
|
510
|
+
return;
|
|
202
511
|
}
|
|
512
|
+
ctx.output.writeLine(pc.green(t("repl.verbose_set", { level: result.display })));
|
|
203
513
|
},
|
|
204
514
|
});
|
|
205
515
|
ctx.registerCommand({
|
|
@@ -209,17 +519,13 @@ function registerConfigCommands(ctx) {
|
|
|
209
519
|
action: () => {
|
|
210
520
|
const providers = ctx.agent.listProviders();
|
|
211
521
|
const active = providers.find((p) => p.active);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
ctx.output.writeLine(`${t("repl.provider")} ${active.label} (${pc.dim(active.type)}) @ ${pc.dim(active.baseUrl)}`);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
ctx.output.writeLine(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
|
|
218
|
-
}
|
|
522
|
+
const providerLabel = active ? active.label : ctx.config.provider.type;
|
|
523
|
+
const parts = [ctx.config.model, providerLabel];
|
|
219
524
|
const meta = ctx.sessionManager?.getActiveMeta();
|
|
220
|
-
if (meta)
|
|
221
|
-
|
|
222
|
-
}
|
|
525
|
+
if (meta)
|
|
526
|
+
parts.push(`${meta.name} (${meta.messageCount} ${t("repl.msgs")})`);
|
|
527
|
+
parts.push(`${ctx.config.contextWindow} tok`);
|
|
528
|
+
ctx.output.writeLine(parts.join(pc.dim(" · ")));
|
|
223
529
|
},
|
|
224
530
|
});
|
|
225
531
|
ctx.registerCommand({
|
|
@@ -232,13 +538,7 @@ function registerConfigCommands(ctx) {
|
|
|
232
538
|
await ctx.withExclusiveInput(async () => {
|
|
233
539
|
const answers = await runSetup(ctx.rl);
|
|
234
540
|
const configPath = join(ctx.configDir, "config.json");
|
|
235
|
-
ctx.config
|
|
236
|
-
ctx.config.provider.baseUrl = answers.apiBase;
|
|
237
|
-
ctx.config.provider.apiKey = answers.apiKey;
|
|
238
|
-
ctx.config.model = answers.model;
|
|
239
|
-
ctx.config.contextWindow = answers.contextWindow;
|
|
240
|
-
ctx.config.maxToolIterations = answers.maxToolIterations;
|
|
241
|
-
ctx.config.locale = answers.locale;
|
|
541
|
+
applyAnswersToConfig(ctx.config, answers);
|
|
242
542
|
saveConfig(ctx.config, configPath, dirname(configPath));
|
|
243
543
|
await ctx.agent.reconfigure(ctx.config);
|
|
244
544
|
ctx.output.writeLine(pc.green(t("cli.config_saved")));
|
|
@@ -248,21 +548,28 @@ function registerConfigCommands(ctx) {
|
|
|
248
548
|
ctx.registerCommand({
|
|
249
549
|
name: "sysprompt",
|
|
250
550
|
description: t("repl.sysprompt_desc"),
|
|
251
|
-
usage: t("repl.
|
|
252
|
-
|
|
551
|
+
usage: t("repl.sysprompt_usage"),
|
|
552
|
+
examples: ["/sysprompt", "/sysprompt --full"],
|
|
553
|
+
action: (args) => {
|
|
253
554
|
const info = ctx.agent.getSystemPromptInfo();
|
|
555
|
+
const full = args.includes("--full");
|
|
254
556
|
ctx.output.writeLine(pc.bold(t("repl.sysprompt_tokens", { count: String(info.tokenCount) })));
|
|
255
|
-
ctx.output.writeLine(pc.dim("─".repeat(60)));
|
|
256
|
-
for (const line of info.text.split("\n")) {
|
|
257
|
-
ctx.output.writeLine(line);
|
|
258
|
-
}
|
|
259
|
-
ctx.output.writeLine(pc.dim("─".repeat(60)));
|
|
260
557
|
if (info.excluded.length > 0) {
|
|
261
558
|
ctx.output.writeLine(pc.yellow(t("repl.excluded_blocks", { count: String(info.excluded.length) })));
|
|
262
559
|
for (const e of info.excluded) {
|
|
263
560
|
ctx.output.writeLine(pc.dim(` - ${e.slice(0, 80)}${e.length > 80 ? "..." : ""}`));
|
|
264
561
|
}
|
|
265
562
|
}
|
|
563
|
+
if (full) {
|
|
564
|
+
ctx.output.writeLine(pc.dim("─".repeat(60)));
|
|
565
|
+
for (const line of info.text.split("\n")) {
|
|
566
|
+
ctx.output.writeLine(line);
|
|
567
|
+
}
|
|
568
|
+
ctx.output.writeLine(pc.dim("─".repeat(60)));
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
ctx.output.writeLine(pc.dim(t("repl.sysprompt_full_hint")));
|
|
572
|
+
}
|
|
266
573
|
},
|
|
267
574
|
});
|
|
268
575
|
ctx.registerCommand({
|
|
@@ -286,8 +593,9 @@ function registerProviderCommands(ctx) {
|
|
|
286
593
|
const list = () => {
|
|
287
594
|
const providers = ctx.agent.listProviders();
|
|
288
595
|
if (providers.length === 0) {
|
|
289
|
-
|
|
290
|
-
ctx.output.writeLine(
|
|
596
|
+
const endpoint = activeProviderEndpoint(ctx.config.provider);
|
|
597
|
+
ctx.output.writeLine(`${t("repl.provider_current")} ${endpoint.type}`);
|
|
598
|
+
ctx.output.writeLine(` ${endpoint.baseUrl}`);
|
|
291
599
|
return;
|
|
292
600
|
}
|
|
293
601
|
ctx.output.writeLine(t("repl.provider_current"));
|
|
@@ -302,14 +610,14 @@ function registerProviderCommands(ctx) {
|
|
|
302
610
|
ctx.output.writeLine(t("repl.provider_usage"));
|
|
303
611
|
return;
|
|
304
612
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
ctx
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
catch (e) {
|
|
311
|
-
ctx.output.writeLine(pc.red(e.message), "stderr");
|
|
613
|
+
const result = await mutateConfig(ctx, "provider.active", name);
|
|
614
|
+
if (!result.ok) {
|
|
615
|
+
renderConfigError(ctx, result);
|
|
616
|
+
return;
|
|
312
617
|
}
|
|
618
|
+
ctx.refreshModelCache();
|
|
619
|
+
ctx.output.writeLine(pc.green(t("repl.provider_set", { name })));
|
|
620
|
+
reportSaved(ctx, result);
|
|
313
621
|
};
|
|
314
622
|
const add = async (args) => {
|
|
315
623
|
const flags = {};
|
|
@@ -333,6 +641,13 @@ function registerProviderCommands(ctx) {
|
|
|
333
641
|
ctx.output.writeLine(t("repl.provider_usage"));
|
|
334
642
|
return;
|
|
335
643
|
}
|
|
644
|
+
for (const flag of ["priority", "context-window", "rpm", "parallel"]) {
|
|
645
|
+
const v = flags[flag];
|
|
646
|
+
if (v !== undefined && !Number.isFinite(Number(v))) {
|
|
647
|
+
ctx.output.writeLine(pc.red(t("repl.provider_add_invalid_number", { flag: `--${flag}`, value: v })), "stderr");
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
336
651
|
const entries = Array.isArray(ctx.config.provider.entries) ? ctx.config.provider.entries : [];
|
|
337
652
|
if (entries.length === 0) {
|
|
338
653
|
entries.push({
|
|
@@ -347,6 +662,10 @@ function registerProviderCommands(ctx) {
|
|
|
347
662
|
ctx.output.writeLine(pc.red(t("cli.provider_no_url", { name })), "stderr");
|
|
348
663
|
return;
|
|
349
664
|
}
|
|
665
|
+
if (providerNameTaken(entries, name)) {
|
|
666
|
+
ctx.output.writeLine(pc.red(t("cli.provider_name_conflict", { name })), "stderr");
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
350
669
|
entries.push({
|
|
351
670
|
type: name,
|
|
352
671
|
label: name,
|
|
@@ -367,8 +686,7 @@ function registerProviderCommands(ctx) {
|
|
|
367
686
|
});
|
|
368
687
|
ctx.config.provider.entries = entries;
|
|
369
688
|
ctx.config.provider.active = ctx.config.provider.active || ctx.config.provider.type;
|
|
370
|
-
|
|
371
|
-
saveConfig(ctx.config, configPath, dirname(configPath));
|
|
689
|
+
persistConfig(ctx);
|
|
372
690
|
await ctx.agent.reconfigure(ctx.config);
|
|
373
691
|
ctx.output.writeLine(pc.green(t("cli.provider_added", { name })));
|
|
374
692
|
ctx.output.writeLine(t("repl.provider_switch_hint", { name }));
|
|
@@ -382,10 +700,12 @@ function registerProviderCommands(ctx) {
|
|
|
382
700
|
name: "provider",
|
|
383
701
|
description: t("repl.provider_list"),
|
|
384
702
|
usage: t("repl.provider_usage"),
|
|
703
|
+
examples: ["/provider list", "/provider use openrouter", "/provider add openai --key sk-..."],
|
|
385
704
|
action: async (args) => {
|
|
386
705
|
const subcmd = args[0];
|
|
387
706
|
const handler = subcmd ? handlers[subcmd] : handlers.list;
|
|
388
707
|
if (!handler) {
|
|
708
|
+
ctx.output.writeLine(pc.yellow(t("repl.unknown_sub", { cmd: "provider", sub: subcmd })));
|
|
389
709
|
ctx.output.writeLine(t("repl.provider_usage"));
|
|
390
710
|
return;
|
|
391
711
|
}
|
|
@@ -439,11 +759,17 @@ function registerProviderCommands(ctx) {
|
|
|
439
759
|
ctx.output.writeLine(t("repl.model_usage"));
|
|
440
760
|
return;
|
|
441
761
|
}
|
|
442
|
-
ctx
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
762
|
+
const result = await mutateConfig(ctx, "model", name);
|
|
763
|
+
if (!result.ok) {
|
|
764
|
+
renderConfigError(ctx, result);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
446
767
|
ctx.output.writeLine(pc.green(t("repl.model_set", { name })));
|
|
768
|
+
const known = ctx.knownModels();
|
|
769
|
+
if (known.length > 0 && !known.includes(name)) {
|
|
770
|
+
ctx.output.writeLine(pc.yellow(t("model.not_in_list", { name })));
|
|
771
|
+
}
|
|
772
|
+
reportSaved(ctx, result);
|
|
447
773
|
};
|
|
448
774
|
const modelHandlers = {
|
|
449
775
|
list: listModels,
|
|
@@ -453,10 +779,12 @@ function registerProviderCommands(ctx) {
|
|
|
453
779
|
name: "model",
|
|
454
780
|
description: t("repl.model_list"),
|
|
455
781
|
usage: t("repl.model_usage"),
|
|
782
|
+
examples: ["/model list", "/model use qwen/qwen3.5-9b"],
|
|
456
783
|
action: async (args) => {
|
|
457
784
|
const subcmd = args[0];
|
|
458
785
|
const handler = subcmd ? modelHandlers[subcmd] : modelHandlers.list;
|
|
459
786
|
if (!handler) {
|
|
787
|
+
ctx.output.writeLine(pc.yellow(t("repl.unknown_sub", { cmd: "model", sub: subcmd })));
|
|
460
788
|
ctx.output.writeLine(t("repl.model_usage"));
|
|
461
789
|
return;
|
|
462
790
|
}
|
|
@@ -467,12 +795,8 @@ function registerProviderCommands(ctx) {
|
|
|
467
795
|
name: "context",
|
|
468
796
|
description: t("cli.manage_context"),
|
|
469
797
|
usage: t("repl.context_usage"),
|
|
798
|
+
examples: ["/context", "/context 64000", "/context system 10%", "/context reserve 0.2"],
|
|
470
799
|
action: async (args) => {
|
|
471
|
-
const save = async () => {
|
|
472
|
-
const configPath = join(ctx.configDir, "config.json");
|
|
473
|
-
saveConfig(ctx.config, configPath, dirname(configPath));
|
|
474
|
-
await ctx.agent.reconfigure(ctx.config);
|
|
475
|
-
};
|
|
476
800
|
if (args.length === 0) {
|
|
477
801
|
for (const line of budgetBreakdownLines(ctx.config))
|
|
478
802
|
ctx.output.writeLine(line);
|
|
@@ -480,28 +804,62 @@ function registerProviderCommands(ctx) {
|
|
|
480
804
|
return;
|
|
481
805
|
}
|
|
482
806
|
if (args[0] === "system" || args[0] === "reserve") {
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
ctx.output.writeLine(pc.yellow(t("cli.context_invalid_fraction")));
|
|
807
|
+
const raw = args[1];
|
|
808
|
+
if (!raw) {
|
|
809
|
+
ctx.output.writeLine(t("repl.context_usage"));
|
|
487
810
|
return;
|
|
488
811
|
}
|
|
489
|
-
|
|
490
|
-
|
|
812
|
+
const key = args[0] === "system"
|
|
813
|
+
? "contextBudget.systemPrompt"
|
|
814
|
+
: "contextBudget.responseReserve";
|
|
815
|
+
const result = await mutateConfig(ctx, key, raw);
|
|
816
|
+
if (!result.ok) {
|
|
817
|
+
renderConfigError(ctx, result);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
ctx.output.writeLine(pc.green(t("cli.context_fraction_set", { key: args[0], value: result.display })));
|
|
491
821
|
return;
|
|
492
822
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
ctx.output.writeLine(t("
|
|
823
|
+
if (!/^\d+$/.test(args[0])) {
|
|
824
|
+
ctx.output.writeLine(pc.yellow(t("context.unknown_arg", { arg: args[0] })));
|
|
825
|
+
ctx.output.writeLine(t("repl.context_usage"));
|
|
496
826
|
return;
|
|
497
827
|
}
|
|
498
|
-
ctx
|
|
499
|
-
|
|
500
|
-
|
|
828
|
+
const result = await mutateConfig(ctx, "contextWindow", args[0]);
|
|
829
|
+
if (!result.ok) {
|
|
830
|
+
renderConfigError(ctx, result);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
ctx.output.writeLine(pc.green(t("cli.context_set", { size: String(result.value) })));
|
|
834
|
+
reportSaved(ctx, result);
|
|
501
835
|
},
|
|
502
836
|
});
|
|
503
837
|
}
|
|
504
838
|
function registerAgentCommands(ctx) {
|
|
839
|
+
const securityStatus = () => {
|
|
840
|
+
const s = ctx.config.security;
|
|
841
|
+
const flag = (v) => (v ? t("reasoning.on") : t("reasoning.off"));
|
|
842
|
+
const sep = pc.dim(" · ");
|
|
843
|
+
ctx.output.writeLine(`security${sep}${flag(s?.enabled)}`);
|
|
844
|
+
ctx.output.writeLine(`↳ bash ${flag(s?.bash.enabled)}${sep}paths ${flag(s?.paths.enabled)}${sep}network ${flag(s?.network.enabled)}`);
|
|
845
|
+
ctx.output.writeLine(`↳ contentScan ${flag(s?.contentScan.enabled)}${sep}encryption ${flag(s?.sessionEncryption.enabled)}`);
|
|
846
|
+
};
|
|
847
|
+
const securityHandlers = {
|
|
848
|
+
status: securityStatus,
|
|
849
|
+
};
|
|
850
|
+
ctx.registerCommand({
|
|
851
|
+
name: "security",
|
|
852
|
+
description: t("repl.security"),
|
|
853
|
+
action: (args) => {
|
|
854
|
+
const sub = args[0]?.trim().toLowerCase();
|
|
855
|
+
const handler = sub ? securityHandlers[sub] : securityHandlers.status;
|
|
856
|
+
if (!handler) {
|
|
857
|
+
ctx.output.writeLine(pc.yellow(t("repl.unknown_sub", { cmd: "security", sub })));
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
handler();
|
|
861
|
+
},
|
|
862
|
+
});
|
|
505
863
|
ctx.registerCommand({
|
|
506
864
|
name: "reload",
|
|
507
865
|
description: t("repl.reload"),
|
|
@@ -512,7 +870,8 @@ function registerAgentCommands(ctx) {
|
|
|
512
870
|
ctx.output.writeLine(pc.green(t("repl.reloaded")));
|
|
513
871
|
ctx.output.writeLine(`${t("repl.model")} ${ctx.config.model}`);
|
|
514
872
|
ctx.output.writeLine(`${t("repl.context")} ${ctx.config.contextWindow}`);
|
|
515
|
-
|
|
873
|
+
const endpoint = activeProviderEndpoint(ctx.config.provider);
|
|
874
|
+
ctx.output.writeLine(`${t("repl.provider")} ${endpoint.type} @ ${endpoint.baseUrl}`);
|
|
516
875
|
},
|
|
517
876
|
});
|
|
518
877
|
ctx.registerCommand({
|
|
@@ -545,74 +904,7 @@ function registerAgentCommands(ctx) {
|
|
|
545
904
|
if (!showAll) {
|
|
546
905
|
const builtinCount = infos.filter(({ plugin }) => plugin.isBuiltin).length;
|
|
547
906
|
if (builtinCount > 0) {
|
|
548
|
-
ctx.output.writeLine(pc.dim(`
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
},
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
function registerLspCommands(ctx) {
|
|
555
|
-
ctx.registerCommand({
|
|
556
|
-
name: "lsp",
|
|
557
|
-
description: t("repl.lsp"),
|
|
558
|
-
usage: t("repl.lsp_usage"),
|
|
559
|
-
action: async (args) => {
|
|
560
|
-
const subcommand = args[0] || "status";
|
|
561
|
-
// Access LSP module via the agent's module registry
|
|
562
|
-
const lspModule = ctx.agent.getModule("lsp");
|
|
563
|
-
if (!lspModule) {
|
|
564
|
-
ctx.output.writeLine(pc.yellow(t("repl.lsp_not_available")));
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
switch (subcommand) {
|
|
568
|
-
case "status": {
|
|
569
|
-
const disabled = lspModule.getDisabledServers();
|
|
570
|
-
const failures = lspModule.getFailureCounts();
|
|
571
|
-
const config = ctx.config.lsp;
|
|
572
|
-
ctx.output.writeLine(pc.bold(t("repl.lsp_status_header")));
|
|
573
|
-
ctx.output.writeLine(`${t("repl.lsp_enabled")} ${config?.enabled ? pc.green("yes") : pc.red("no")}`);
|
|
574
|
-
if (disabled.length > 0) {
|
|
575
|
-
ctx.output.writeLine(pc.yellow(`${t("repl.lsp_disabled_servers")} ${disabled.join(", ")}`));
|
|
576
|
-
}
|
|
577
|
-
if (failures.size > 0) {
|
|
578
|
-
ctx.output.writeLine(pc.dim(t("repl.lsp_failure_counts")));
|
|
579
|
-
for (const [server, count] of failures) {
|
|
580
|
-
ctx.output.writeLine(pc.dim(` ${server}: ${count}`));
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
if (disabled.length === 0 && failures.size === 0) {
|
|
584
|
-
ctx.output.writeLine(pc.green(t("repl.lsp_all_ok")));
|
|
585
|
-
}
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
|
-
case "restart": {
|
|
589
|
-
ctx.output.writeLine(pc.yellow(t("repl.lsp_restarting")));
|
|
590
|
-
lspModule.resetDisabledServers();
|
|
591
|
-
ctx.output.writeLine(pc.green(t("repl.lsp_restarted")));
|
|
592
|
-
break;
|
|
593
|
-
}
|
|
594
|
-
case "check": {
|
|
595
|
-
const path = args[1];
|
|
596
|
-
if (!path) {
|
|
597
|
-
ctx.output.writeLine(pc.yellow(t("repl.lsp_check_usage")));
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
ctx.output.writeLine(pc.dim(t("repl.lsp_checking", { path })));
|
|
601
|
-
// Запуск тула lsp_check через публичный API агента (не через приватные deps)
|
|
602
|
-
try {
|
|
603
|
-
const result = await ctx.agent.runTool("lsp_check", { path });
|
|
604
|
-
if (result?.output) {
|
|
605
|
-
ctx.output.writeLine(result.output);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
catch (e) {
|
|
609
|
-
ctx.output.writeLine(pc.red(t("repl.lsp_check_error", { error: errMsg(e) })), "stderr");
|
|
610
|
-
}
|
|
611
|
-
break;
|
|
612
|
-
}
|
|
613
|
-
default: {
|
|
614
|
-
ctx.output.writeLine(pc.dim(t("repl.lsp_usage")));
|
|
615
|
-
break;
|
|
907
|
+
ctx.output.writeLine(pc.dim(` ${t("repl.plugins_hidden", { count: String(builtinCount) })}`));
|
|
616
908
|
}
|
|
617
909
|
}
|
|
618
910
|
},
|
|
@@ -633,7 +925,23 @@ function registerSessionCommands(ctx) {
|
|
|
633
925
|
description: t("repl.sessions"),
|
|
634
926
|
aliases: ["ls"],
|
|
635
927
|
usage: t("repl.sessions_usage"),
|
|
636
|
-
action: () => {
|
|
928
|
+
action: (args) => {
|
|
929
|
+
if (args[0] === "prune") {
|
|
930
|
+
const dry = args.includes("--dry-run");
|
|
931
|
+
const active = ctx.sessionManager.getActive();
|
|
932
|
+
const candidates = ctx.sessionManager.list().filter((s) => s.id !== active && ctx.sessionManager.isEmptySession(s.id));
|
|
933
|
+
if (candidates.length === 0) {
|
|
934
|
+
ctx.output.writeLine(t("session.prune_empty"));
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (!dry)
|
|
938
|
+
ctx.sessionManager.pruneEmpty(active ?? undefined);
|
|
939
|
+
ctx.output.writeLine(t(dry ? "session.prune_dry" : "session.pruned", { n: candidates.length }));
|
|
940
|
+
for (const s of candidates) {
|
|
941
|
+
ctx.output.writeLine(pc.dim(` ${s.id.slice(0, 12)} ${s.name}`));
|
|
942
|
+
}
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
637
945
|
const sessions = ctx.sessionManager.list();
|
|
638
946
|
const active = ctx.sessionManager.getActive();
|
|
639
947
|
if (sessions.length === 0) {
|
|
@@ -668,6 +976,14 @@ function registerSessionCommands(ctx) {
|
|
|
668
976
|
const name = args.join(" ") || undefined;
|
|
669
977
|
const meta = ctx.sessionManager.create(name);
|
|
670
978
|
ctx.agent.clearContext();
|
|
979
|
+
// План следует за сессией: новая — без плана, трекер сбрасывается.
|
|
980
|
+
const exec = ctx.agent.getModule("execution");
|
|
981
|
+
try {
|
|
982
|
+
exec?.switchSession(meta.id);
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
// fail-open: сессия создана, план остался как был
|
|
986
|
+
}
|
|
671
987
|
ctx.output.clearScreen();
|
|
672
988
|
ctx.output.writeLine(`${t("session.created", { name: meta.name })} (${pc.dim(meta.id.slice(0, 12))})`);
|
|
673
989
|
ctx.output.writeLine(pc.dim(` ${t("session.chat_cleared")}\n`));
|
|
@@ -676,8 +992,9 @@ function registerSessionCommands(ctx) {
|
|
|
676
992
|
ctx.registerCommand({
|
|
677
993
|
name: "resume",
|
|
678
994
|
description: t("repl.resume"),
|
|
679
|
-
aliases: ["switch"
|
|
995
|
+
aliases: ["switch"],
|
|
680
996
|
usage: t("repl.resume_usage"),
|
|
997
|
+
examples: ["/resume", "/resume my-session"],
|
|
681
998
|
action: (args) => {
|
|
682
999
|
const query = args.join(" ");
|
|
683
1000
|
const sessions = ctx.sessionManager.list();
|
|
@@ -701,6 +1018,17 @@ function registerSessionCommands(ctx) {
|
|
|
701
1018
|
ctx.sessionManager.setActive(match.id);
|
|
702
1019
|
const history = ctx.sessionManager.loadHistory();
|
|
703
1020
|
ctx.agent.setContext(history);
|
|
1021
|
+
// План следует за сессией: трекер переключается на план этой сессии
|
|
1022
|
+
// (или пустеет, если у неё плана нет).
|
|
1023
|
+
const exec = ctx.agent.getModule("execution");
|
|
1024
|
+
let planLine = null;
|
|
1025
|
+
try {
|
|
1026
|
+
exec?.switchSession(match.id);
|
|
1027
|
+
planLine = exec?.getPlanSummary() ?? null;
|
|
1028
|
+
}
|
|
1029
|
+
catch {
|
|
1030
|
+
// fail-open: история загружена, план остался как был
|
|
1031
|
+
}
|
|
704
1032
|
ctx.output.clearScreen();
|
|
705
1033
|
ctx.output.writeLine(pc.bold(pc.green(t("session.resumed", { name: match.name }))) +
|
|
706
1034
|
" " +
|
|
@@ -718,12 +1046,12 @@ function registerSessionCommands(ctx) {
|
|
|
718
1046
|
ctx.output.writeLine("");
|
|
719
1047
|
for (const msg of history) {
|
|
720
1048
|
if (msg.role === "user") {
|
|
721
|
-
ctx.output.writeLine(pc.cyan(t("session.user_label")
|
|
1049
|
+
ctx.output.writeLine(pc.cyan(`${t("session.user_label")}:`));
|
|
722
1050
|
ctx.output.writeLine(getMessageText(msg.content));
|
|
723
1051
|
ctx.output.writeLine("");
|
|
724
1052
|
}
|
|
725
1053
|
else if (msg.role === "assistant") {
|
|
726
|
-
ctx.output.writeLine(pc.green(t("session.assistant_label")
|
|
1054
|
+
ctx.output.writeLine(pc.green(`${t("session.assistant_label")}:`));
|
|
727
1055
|
ctx.output.writeLine(getMessageText(msg.content));
|
|
728
1056
|
ctx.output.writeLine("");
|
|
729
1057
|
}
|
|
@@ -731,6 +1059,7 @@ function registerSessionCommands(ctx) {
|
|
|
731
1059
|
}
|
|
732
1060
|
ctx.output.writeLine(pc.dim("─".repeat(50)));
|
|
733
1061
|
ctx.output.writeLine(pc.dim(` ${t("session.chat_loaded")}`));
|
|
1062
|
+
ctx.output.writeLine(pc.dim(` ◇ ${planLine ?? t("plan.no_active")}`));
|
|
734
1063
|
},
|
|
735
1064
|
});
|
|
736
1065
|
ctx.registerCommand({
|
|
@@ -770,105 +1099,99 @@ function registerSessionCommands(ctx) {
|
|
|
770
1099
|
return;
|
|
771
1100
|
}
|
|
772
1101
|
ctx.sessionManager.delete(match.id);
|
|
1102
|
+
// План следует за сессией: слот удалённой сессии не оставляем мусором.
|
|
1103
|
+
try {
|
|
1104
|
+
ctx.agent
|
|
1105
|
+
.getModule("execution")
|
|
1106
|
+
?.forgetSession(match.id);
|
|
1107
|
+
}
|
|
1108
|
+
catch {
|
|
1109
|
+
/* best-effort */
|
|
1110
|
+
}
|
|
773
1111
|
ctx.output.writeLine(`${t("session.deleted", { id: match.id })}: ${match.name}`);
|
|
774
1112
|
},
|
|
775
1113
|
});
|
|
776
|
-
}
|
|
777
|
-
function registerSkillCommands(ctx) {
|
|
778
|
-
if (!ctx.skillsModule)
|
|
779
|
-
return;
|
|
780
|
-
const listSkills = () => {
|
|
781
|
-
const available = ctx.skillsModule.getAvailable();
|
|
782
|
-
if (available.length === 0) {
|
|
783
|
-
ctx.output.writeLine(t("repl.no_skills"));
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
ctx.output.writeLine(pc.bold(t("repl.available_skills")));
|
|
787
|
-
for (const skill of available) {
|
|
788
|
-
const tokens = estimateTokens(skill.content);
|
|
789
|
-
const loaded = ctx.skillsModule.getLoaded().some((s) => s.name === skill.name);
|
|
790
|
-
const marker = loaded ? pc.green(" [loaded]") : "";
|
|
791
|
-
ctx.output.writeLine(` ${pc.cyan(skill.name)}${marker} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
792
|
-
}
|
|
793
|
-
};
|
|
794
|
-
const listLoaded = () => {
|
|
795
|
-
const loaded = ctx.skillsModule.getLoaded();
|
|
796
|
-
const budget = ctx.skillsModule.getBudget();
|
|
797
|
-
if (loaded.length === 0) {
|
|
798
|
-
ctx.output.writeLine(t("repl.no_loaded"));
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
ctx.output.writeLine(pc.bold(t("repl.loaded_skills")));
|
|
802
|
-
for (const skill of loaded) {
|
|
803
|
-
const tokens = estimateTokens(skill.content);
|
|
804
|
-
ctx.output.writeLine(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
805
|
-
}
|
|
806
|
-
ctx.output.writeLine(pc.dim(`\n ${t("repl.budget", { used: budget.used, total: budget.total, remaining: budget.remaining })}`));
|
|
807
|
-
};
|
|
808
|
-
const loadSkill = (args) => {
|
|
809
|
-
const arg = args.join(" ");
|
|
810
|
-
if (!arg) {
|
|
811
|
-
ctx.output.writeLine(t("repl.skill_load_usage"));
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
const result = ctx.skillsModule.loadByName(arg);
|
|
815
|
-
if (result.success) {
|
|
816
|
-
ctx.output.writeLine(pc.green(result.message));
|
|
817
|
-
}
|
|
818
|
-
else {
|
|
819
|
-
ctx.output.writeLine(pc.red(result.message), "stderr");
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
const unloadSkill = (args) => {
|
|
823
|
-
const arg = args.join(" ");
|
|
824
|
-
if (!arg) {
|
|
825
|
-
ctx.output.writeLine(t("repl.skill_unload_usage"));
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
if (ctx.skillsModule.unload(arg)) {
|
|
829
|
-
ctx.output.writeLine(pc.green(t("repl.skill_unloaded", { name: arg })));
|
|
830
|
-
}
|
|
831
|
-
else {
|
|
832
|
-
ctx.output.writeLine(pc.red(t("repl.skill_not_loaded", { name: arg })), "stderr");
|
|
833
|
-
}
|
|
834
|
-
};
|
|
835
|
-
const searchSkills = (args) => {
|
|
836
|
-
const arg = args.join(" ");
|
|
837
|
-
if (!arg) {
|
|
838
|
-
ctx.output.writeLine(t("repl.skill_search_usage"));
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
const results = ctx.skillsModule.search(arg);
|
|
842
|
-
if (results.length === 0) {
|
|
843
|
-
ctx.output.writeLine(t("repl.no_skill_match", { query: arg }));
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
ctx.output.writeLine(pc.bold(t("repl.skills_matching", { query: arg })));
|
|
847
|
-
for (const skill of results) {
|
|
848
|
-
const tokens = estimateTokens(skill.content);
|
|
849
|
-
ctx.output.writeLine(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
const skillHandlers = {
|
|
853
|
-
list: listSkills,
|
|
854
|
-
loaded: listLoaded,
|
|
855
|
-
load: loadSkill,
|
|
856
|
-
unload: unloadSkill,
|
|
857
|
-
search: searchSkills,
|
|
858
|
-
};
|
|
859
1114
|
ctx.registerCommand({
|
|
860
|
-
name: "
|
|
861
|
-
description: t("repl.
|
|
862
|
-
usage: t("repl.
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1115
|
+
name: "export",
|
|
1116
|
+
description: t("repl.export"),
|
|
1117
|
+
usage: t("repl.export_usage"),
|
|
1118
|
+
examples: [
|
|
1119
|
+
"/export",
|
|
1120
|
+
"/export my-session --format md",
|
|
1121
|
+
"/export --format json --out -",
|
|
1122
|
+
],
|
|
1123
|
+
action: async (args) => {
|
|
1124
|
+
let format = "md";
|
|
1125
|
+
let out;
|
|
1126
|
+
let redact = true;
|
|
1127
|
+
let compact = false;
|
|
1128
|
+
const positional = [];
|
|
1129
|
+
for (let i = 0; i < args.length; i++) {
|
|
1130
|
+
const a = args[i];
|
|
1131
|
+
if (a === "--format") {
|
|
1132
|
+
const v = args[++i];
|
|
1133
|
+
if (!v) {
|
|
1134
|
+
ctx.output.writeLine(t("export.missing_value", { flag: a }));
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
format = v;
|
|
1138
|
+
}
|
|
1139
|
+
else if (a === "--out") {
|
|
1140
|
+
const v = args[++i];
|
|
1141
|
+
if (v === undefined) {
|
|
1142
|
+
ctx.output.writeLine(t("export.missing_value", { flag: a }));
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
out = v;
|
|
1146
|
+
}
|
|
1147
|
+
else if (a === "--no-redact") {
|
|
1148
|
+
redact = false;
|
|
1149
|
+
}
|
|
1150
|
+
else if (a === "--compact") {
|
|
1151
|
+
compact = true;
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
positional.push(a);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (format !== "md" && format !== "json" && format !== "jsonl") {
|
|
1158
|
+
ctx.output.writeLine(pc.yellow(t("export.invalid_format", { format })), "stderr");
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const query = positional.join(" ");
|
|
1162
|
+
const sessions = ctx.sessionManager.list();
|
|
1163
|
+
const match = query
|
|
1164
|
+
? sessions.find((s) => s.id === query ||
|
|
1165
|
+
s.id.startsWith(query) ||
|
|
1166
|
+
s.name.toLowerCase().includes(query.toLowerCase()))
|
|
1167
|
+
: sessions.find((s) => s.id === ctx.sessionManager.getActive());
|
|
1168
|
+
if (!match) {
|
|
1169
|
+
ctx.output.writeLine(query ? t("session.no_match", { query }) : t("session.no_active"));
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
const messages = ctx.sessionManager.getHistory(match.id);
|
|
1173
|
+
const log = ctx.sessionManager.loadSessionLog(match.id);
|
|
1174
|
+
if (format === "jsonl" ? log.length === 0 : messages.length === 0) {
|
|
1175
|
+
ctx.output.writeLine(pc.yellow(t("export.empty")));
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const text = redactExport(formatExport({ meta: match, messages, log }, format, { compact }), redact);
|
|
1179
|
+
if (out === "-") {
|
|
1180
|
+
ctx.output.writeLine(text);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const baseDir = out ? dirname(out) : globalExportsDir();
|
|
1184
|
+
const path = out ?? join(baseDir, `${sanitizeFileName(match.name)}-${match.id.slice(0, 8)}.${extensionFor(format)}`);
|
|
1185
|
+
try {
|
|
1186
|
+
mkdirSync(baseDir, { recursive: true });
|
|
1187
|
+
writeFileSync(path, text, "utf-8");
|
|
1188
|
+
}
|
|
1189
|
+
catch (e) {
|
|
1190
|
+
ctx.output.writeLine(pc.red(t("export.write_failed", { error: errMsg(e) })), "stderr");
|
|
869
1191
|
return;
|
|
870
1192
|
}
|
|
871
|
-
|
|
1193
|
+
ctx.output.writeLine(pc.green(t("export.written", { path })));
|
|
1194
|
+
ctx.output.writeLine(pc.dim(redact ? t("export.redacted") : t("export.raw")));
|
|
872
1195
|
},
|
|
873
1196
|
});
|
|
874
1197
|
}
|
|
@@ -889,7 +1212,7 @@ async function runConfigMigrate(ctx) {
|
|
|
889
1212
|
const { config } = loadCfg({ configDir, projectConfigPath: join(configDir, ".mmrc") });
|
|
890
1213
|
saveConfig(config, configPath, configDir);
|
|
891
1214
|
const { renameSync, readdirSync } = await import("fs");
|
|
892
|
-
renameSync(configPath, configPath
|
|
1215
|
+
renameSync(configPath, `${configPath}.bak`);
|
|
893
1216
|
const domainFiles = readdirSync(join(configDir, "config")).filter((f) => f.endsWith(".json"));
|
|
894
1217
|
ctx.output.writeLine(pc.green(t("config.migrate_done", { count: String(domainFiles.length) })));
|
|
895
1218
|
}
|