micro-models-agent 0.52.1 → 0.54.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/README.md +358 -358
- package/dist/certification/certifications.json +494 -0
- package/dist/cli/commands.js +447 -0
- package/dist/cli/completer.js +167 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/main.js +153 -0
- package/dist/cli/plugin-commands.js +36 -0
- package/dist/cli/repl-commands.js +761 -0
- package/dist/cli/repl.js +702 -0
- package/dist/cli/run-result.js +33 -0
- package/dist/cli/security-commands.js +164 -0
- package/dist/cli/setup.js +237 -0
- package/dist/config/config.js +276 -0
- package/dist/config/defaults.js +141 -0
- package/dist/config/domains.js +179 -0
- package/dist/config/experts.js +15 -0
- package/dist/config/index.js +4 -0
- package/dist/config/security.js +213 -0
- package/dist/config/types.js +1 -0
- package/dist/core/agent-moe.js +102 -0
- package/dist/core/agent.js +1018 -0
- package/dist/core/bootstrap.js +481 -0
- package/dist/core/crash-handler.js +51 -0
- package/dist/core/environment.js +199 -0
- package/dist/core/index.js +2 -0
- package/dist/core/prompt-builder.js +76 -0
- package/dist/core/session-logger.js +251 -0
- package/dist/core/types.js +1 -0
- package/dist/core/version.js +26 -0
- package/dist/core/workspace.js +76 -0
- package/dist/i18n/en.json +679 -0
- package/dist/i18n/index.js +46 -0
- package/dist/i18n/ru.json +679 -0
- package/dist/index.js +22 -0
- package/dist/llm/image-utils.js +143 -0
- package/dist/llm/index.js +4 -0
- package/dist/llm/model-loader.js +78 -0
- package/dist/llm/openai-compat.js +497 -0
- package/dist/llm/orchestrator.js +200 -0
- package/dist/llm/provider.js +10 -0
- package/dist/llm/response.js +39 -0
- package/dist/llm/token-counter.js +39 -0
- package/dist/llm/types.js +1 -0
- package/dist/logger/app-logger.js +189 -0
- package/dist/logger/file-log.js +151 -0
- package/dist/logger/index.js +1 -0
- package/dist/main.js +304 -157
- package/dist/migration/backup.js +45 -0
- package/dist/migration/detect.js +50 -0
- package/dist/migration/index.js +2 -0
- package/dist/modules/artifacts/store.js +61 -0
- package/dist/modules/browser/actions.js +76 -0
- package/dist/modules/browser/bridge-client.js +199 -0
- package/dist/modules/browser/bridge-path.js +10 -0
- package/dist/modules/browser/bridge-server.mjs +202 -202
- package/dist/modules/browser/cookie-store.js +24 -0
- package/dist/modules/browser/driver.js +136 -0
- package/dist/modules/browser/index.js +7 -0
- package/dist/modules/browser/module.js +29 -0
- package/dist/modules/browser/session.js +342 -0
- package/dist/modules/browser/snapshot.js +148 -0
- package/dist/modules/browser/types.js +12 -0
- package/dist/modules/certification/cli.js +213 -0
- package/dist/modules/certification/fact-checker.js +82 -0
- package/dist/modules/certification/loader.js +106 -0
- package/dist/modules/certification/manifest.js +58 -0
- package/dist/modules/certification/runner.js +245 -0
- package/dist/modules/certification/scenarios.js +407 -0
- package/dist/modules/certification/types.js +1 -0
- package/dist/modules/context/chunk-query.js +100 -0
- package/dist/modules/context/fact-extractor.js +168 -0
- package/dist/modules/context/history.js +15 -0
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +440 -0
- package/dist/modules/execution/audit-runners.js +206 -0
- package/dist/modules/execution/auditor.js +218 -0
- package/dist/modules/execution/execution-plugin.js +431 -0
- package/dist/modules/execution/index.js +8 -0
- package/dist/modules/execution/module.js +625 -0
- package/dist/modules/execution/moe-executor.js +304 -0
- package/dist/modules/execution/plan-coverage.js +68 -0
- package/dist/modules/execution/plan-persister.js +46 -0
- package/dist/modules/execution/plan-store.js +196 -0
- package/dist/modules/execution/plan-tool.js +677 -0
- package/dist/modules/execution/plan-validator.js +153 -0
- package/dist/modules/execution/planner.js +94 -0
- package/dist/modules/execution/stuck-detector.js +746 -0
- package/dist/modules/execution/tracker.js +69 -0
- package/dist/modules/execution/types.js +1 -0
- package/dist/modules/execution/verifier.js +235 -0
- package/dist/modules/execution/windows-commands.js +41 -0
- package/dist/modules/hallucination/confidence.js +66 -0
- package/dist/modules/hallucination/consistency.js +26 -0
- package/dist/modules/hallucination/detector.js +47 -0
- package/dist/modules/hallucination/factual.js +169 -0
- package/dist/modules/hallucination/index.js +5 -0
- package/dist/modules/hallucination/js-identifiers.js +262 -0
- package/dist/modules/hallucination/llm-judge.js +101 -0
- package/dist/modules/index.js +5 -0
- package/dist/modules/indexer/cache.js +40 -0
- package/dist/modules/indexer/index.js +3 -0
- package/dist/modules/indexer/module.js +246 -0
- package/dist/modules/indexer/project-profile.js +183 -0
- package/dist/modules/indexer/walker.js +101 -0
- package/dist/modules/lsp/check-tool.js +58 -0
- package/dist/modules/lsp/client.js +389 -0
- package/dist/modules/lsp/command.js +60 -0
- package/dist/modules/lsp/config.js +135 -0
- package/dist/modules/lsp/index.js +3 -0
- package/dist/modules/lsp/module.js +260 -0
- package/dist/modules/lsp/probe.js +86 -0
- package/dist/modules/lsp/project-root.js +32 -0
- package/dist/modules/lsp/startup-check.js +144 -0
- package/dist/modules/lsp/types.js +1 -0
- package/dist/modules/mcp/client.js +399 -0
- package/dist/modules/mcp/index.js +3 -0
- package/dist/modules/mcp/module.js +142 -0
- package/dist/modules/mcp/registry.js +15 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +96 -0
- package/dist/modules/memory/search.js +42 -0
- package/dist/modules/memory/store.js +69 -0
- package/dist/modules/pipelines/engine.js +60 -0
- package/dist/modules/pipelines/index.js +3 -0
- package/dist/modules/pipelines/parser.js +56 -0
- package/dist/modules/pipelines/template.js +14 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +334 -0
- package/dist/modules/plugins/builtin/notify.js +9 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +70 -0
- package/dist/modules/plugins/manager.js +261 -0
- package/dist/modules/plugins/types.js +1 -0
- package/dist/modules/pricing/index.js +61 -0
- package/dist/modules/pricing/prices.js +129 -0
- package/dist/modules/processes/detect.js +34 -0
- package/dist/modules/processes/index.js +2 -0
- package/dist/modules/processes/registry.js +327 -0
- package/dist/modules/processes/runner.js +23 -0
- package/dist/modules/providers/create.js +22 -0
- package/dist/modules/providers/fallback.js +79 -0
- package/dist/modules/providers/health.js +46 -0
- package/dist/modules/providers/index.js +5 -0
- package/dist/modules/providers/manager.js +161 -0
- package/dist/modules/providers/presets.js +128 -0
- package/dist/modules/providers/registry.js +22 -0
- package/dist/modules/providers/types.js +1 -0
- package/dist/modules/registry.js +48 -0
- package/dist/modules/security/audit-log.js +136 -0
- package/dist/modules/security/audit-notifier.js +292 -0
- package/dist/modules/security/command-validator.js +219 -0
- package/dist/modules/security/content-scanner.js +53 -0
- package/dist/modules/security/data-sanitizer.js +89 -0
- package/dist/modules/security/encryption.js +242 -0
- package/dist/modules/security/index.js +14 -0
- package/dist/modules/security/network-validator.js +88 -0
- package/dist/modules/security/path-validator.js +203 -0
- package/dist/modules/security/rate-limiter.js +119 -0
- package/dist/modules/security/security-policies.js +531 -0
- package/dist/modules/security/session-encryption.js +210 -0
- package/dist/modules/security/session-isolation.js +95 -0
- package/dist/modules/session/index.js +3 -0
- package/dist/modules/session/manager.js +172 -0
- package/dist/modules/session/module.js +24 -0
- package/dist/modules/session/store.js +222 -0
- package/dist/modules/session/types.js +1 -0
- package/dist/modules/skills/index.js +2 -0
- package/dist/modules/skills/loader.js +72 -0
- package/dist/modules/skills/matcher.js +27 -0
- package/dist/modules/skills/module.js +129 -0
- package/dist/modules/types.js +1 -0
- package/dist/modules/updater/checker.js +96 -0
- package/dist/modules/updater/index.js +2 -0
- package/dist/modules/updater/module.js +116 -0
- package/dist/modules/user-profile/compressor.js +16 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +68 -0
- package/dist/skills/builtin/git.md +36 -36
- package/dist/skills/builtin/typescript.md +35 -35
- package/dist/tools/approve.js +33 -0
- package/dist/tools/attach-image.js +101 -0
- package/dist/tools/bash.js +519 -0
- package/dist/tools/browser.js +115 -0
- package/dist/tools/chunk-query.js +100 -0
- package/dist/tools/create-dir.js +56 -0
- package/dist/tools/delete-file.js +63 -0
- package/dist/tools/download-file.js +117 -0
- package/dist/tools/edit-file.js +80 -0
- package/dist/tools/enable-tools.js +59 -0
- package/dist/tools/executor.js +154 -0
- package/dist/tools/file-info.js +47 -0
- package/dist/tools/filter-tools.js +17 -0
- package/dist/tools/glob-tool.js +27 -0
- package/dist/tools/grep-tool.js +125 -0
- package/dist/tools/hidden-tools-block.js +37 -0
- package/dist/tools/index.js +78 -0
- package/dist/tools/list-dir.js +49 -0
- package/dist/tools/load-skill.js +43 -0
- package/dist/tools/mcp-call.js +69 -0
- package/dist/tools/move-file.js +86 -0
- package/dist/tools/path-utils.js +101 -0
- package/dist/tools/pipeline-run.js +145 -0
- package/dist/tools/preview.js +2 -0
- package/dist/tools/process-kill.js +40 -0
- package/dist/tools/process-list.js +37 -0
- package/dist/tools/process-log.js +54 -0
- package/dist/tools/question.js +141 -0
- package/dist/tools/read-file.js +179 -0
- package/dist/tools/recall.js +118 -0
- package/dist/tools/registry.js +47 -0
- package/dist/tools/remember.js +68 -0
- package/dist/tools/scope-check.js +32 -0
- package/dist/tools/search-history.js +85 -0
- package/dist/tools/subagent.js +196 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/user-input.js +123 -0
- package/dist/tools/web-browse.js +87 -0
- package/dist/tools/web-fetch.js +119 -0
- package/dist/tools/web-search.js +105 -0
- package/dist/tools/write-file.js +82 -0
- package/dist/ui/box.js +77 -0
- package/dist/ui/colors.js +4 -0
- package/dist/ui/diff.js +178 -0
- package/dist/ui/index.js +6 -0
- package/dist/ui/line-editor.js +822 -0
- package/dist/ui/line-math.js +73 -0
- package/dist/ui/md-formatter.js +212 -0
- package/dist/ui/output.js +13 -0
- package/dist/ui/plan-view.js +103 -0
- package/dist/ui/renderer.js +259 -0
- package/dist/ui/spinner.js +70 -0
- package/dist/ui/table.js +144 -0
- package/package.json +50 -50
package/dist/cli/repl.js
ADDED
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import * as readline from "readline";
|
|
2
|
+
import { pc } from "../ui/colors";
|
|
3
|
+
import { LineEditor } from "../ui/line-editor";
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, ProviderArgProvider, ModelArgProvider, } from "./completer";
|
|
8
|
+
import { Renderer } from "../ui/renderer";
|
|
9
|
+
import { divider } from "../ui/box";
|
|
10
|
+
import { t } from "../i18n/index";
|
|
11
|
+
import { registerAllCommands } from "./repl-commands";
|
|
12
|
+
import { probeLspServers } from "../modules/lsp/probe";
|
|
13
|
+
import { FileOnlyLogger } from "../modules/lsp/client";
|
|
14
|
+
import { DEFAULT_LSP_CONFIG } from "../modules/lsp/config";
|
|
15
|
+
import { SessionLogger } from "../core/session-logger";
|
|
16
|
+
import { formatPlanChecklist, stepContextForTool } from "../ui/plan-view";
|
|
17
|
+
import { formatCost } from "../modules/pricing/prices";
|
|
18
|
+
import { probeProviders, targetsFromProviders } from "../modules/providers/health";
|
|
19
|
+
import { writeWarning } from "../ui/output";
|
|
20
|
+
function formatContextBar(used, limit, compactions, quality) {
|
|
21
|
+
const pct = Math.min(100, Math.round((used / limit) * 100));
|
|
22
|
+
const barLen = 20;
|
|
23
|
+
const filled = Math.round((pct / 100) * barLen);
|
|
24
|
+
const bar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
|
|
25
|
+
const pctStr = pct >= 75 ? pc.yellow(`${pct}%`) : pc.dim(`${pct}%`);
|
|
26
|
+
let line = ` ${bar} ${pctStr} ${pc.dim(`(${used} / ${limit} tokens)`)}`;
|
|
27
|
+
if (compactions !== undefined) {
|
|
28
|
+
line += pc.dim(` compactions: ${compactions}`);
|
|
29
|
+
}
|
|
30
|
+
if (quality !== undefined) {
|
|
31
|
+
const qColor = quality >= 70 ? pc.green : quality >= 40 ? pc.yellow : pc.red;
|
|
32
|
+
line += ` ${qColor(`quality: ${quality}%`)}`;
|
|
33
|
+
}
|
|
34
|
+
return line;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the HostBridge exposed to plugins as `ctx.hostBridge`. Web clients
|
|
38
|
+
* (and any other plugin) submit messages through the SAME runAgent path as
|
|
39
|
+
* terminal input — rendering, session logging and single-flight guard are
|
|
40
|
+
* shared, never duplicated.
|
|
41
|
+
*/
|
|
42
|
+
export function createHostBridge(opts) {
|
|
43
|
+
return {
|
|
44
|
+
isBusy: opts.isBusy,
|
|
45
|
+
async submit(text, o) {
|
|
46
|
+
if (opts.isBusy())
|
|
47
|
+
throw new Error("agent is busy");
|
|
48
|
+
for (const url of o?.images ?? [])
|
|
49
|
+
opts.addPendingImage(url);
|
|
50
|
+
return opts.runAgent(text);
|
|
51
|
+
},
|
|
52
|
+
interrupt() {
|
|
53
|
+
if (opts.isBusy())
|
|
54
|
+
opts.interruptRun();
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export class Repl {
|
|
59
|
+
completer = new Completer();
|
|
60
|
+
modelCache = [];
|
|
61
|
+
running = false;
|
|
62
|
+
agentRunning = false;
|
|
63
|
+
inputLocked = false;
|
|
64
|
+
historyPath;
|
|
65
|
+
history = [];
|
|
66
|
+
maxHistory = 1000;
|
|
67
|
+
lastEscTime = 0;
|
|
68
|
+
doubleEscDelay = 500;
|
|
69
|
+
configDir;
|
|
70
|
+
baseDir;
|
|
71
|
+
noAgentsMd;
|
|
72
|
+
pendingClipboardImage = null;
|
|
73
|
+
exitOnClose = false;
|
|
74
|
+
/** Owner of plan state (bootstrap). Null in tests — plan UI degrades. */
|
|
75
|
+
execModule;
|
|
76
|
+
rl;
|
|
77
|
+
agent;
|
|
78
|
+
config;
|
|
79
|
+
sessionManager;
|
|
80
|
+
skillsModule;
|
|
81
|
+
pluginManager;
|
|
82
|
+
logger;
|
|
83
|
+
slog;
|
|
84
|
+
envReport;
|
|
85
|
+
constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger, exitOnClose, envReport, historyPath, execModule) {
|
|
86
|
+
this.agent = agent;
|
|
87
|
+
this.config = config;
|
|
88
|
+
this.exitOnClose = exitOnClose === true;
|
|
89
|
+
this.sessionManager = sessionManager;
|
|
90
|
+
this.skillsModule = skillsModule;
|
|
91
|
+
this.pluginManager = pluginManager;
|
|
92
|
+
this.logger = logger;
|
|
93
|
+
this.envReport = envReport;
|
|
94
|
+
this.execModule = execModule;
|
|
95
|
+
this.slog = new SessionLogger(sessionManager, logger);
|
|
96
|
+
this.configDir = configDir || join(homedir(), ".mma");
|
|
97
|
+
this.baseDir = baseDir || process.cwd();
|
|
98
|
+
this.noAgentsMd = noAgentsMd === true;
|
|
99
|
+
this.historyPath = historyPath ?? join(homedir(), ".mma", "repl-history");
|
|
100
|
+
this.loadHistory();
|
|
101
|
+
this.rl = process.stdin.isTTY
|
|
102
|
+
? new LineEditor({
|
|
103
|
+
input: process.stdin,
|
|
104
|
+
output: process.stdout,
|
|
105
|
+
prompt: pc.cyan(t("repl.you")),
|
|
106
|
+
history: this.history,
|
|
107
|
+
historySize: this.maxHistory,
|
|
108
|
+
completer: (line) => {
|
|
109
|
+
const [matches, partial] = this.completer.complete(line);
|
|
110
|
+
if (matches.length > 0)
|
|
111
|
+
return [matches, partial];
|
|
112
|
+
return [[], line];
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
: readline.createInterface({
|
|
116
|
+
input: process.stdin,
|
|
117
|
+
output: process.stdout,
|
|
118
|
+
prompt: pc.cyan(t("repl.you")),
|
|
119
|
+
history: this.history,
|
|
120
|
+
historySize: this.maxHistory,
|
|
121
|
+
tabSize: 2,
|
|
122
|
+
completer: (line) => {
|
|
123
|
+
const [matches, partial] = this.completer.complete(line);
|
|
124
|
+
if (matches.length > 0)
|
|
125
|
+
return [matches, partial];
|
|
126
|
+
return [[], line];
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
registerAllCommands(this);
|
|
130
|
+
this.setupCompleter();
|
|
131
|
+
this.setupListeners();
|
|
132
|
+
// Expose the agent to plugins (web UI etc.) through a narrow bridge.
|
|
133
|
+
if (pluginManager) {
|
|
134
|
+
pluginManager.setHostBridge(createHostBridge({
|
|
135
|
+
isBusy: () => this.agentRunning,
|
|
136
|
+
addPendingImage: (url) => {
|
|
137
|
+
this.agent.contextManager?.addPendingImage?.({
|
|
138
|
+
type: "image_url",
|
|
139
|
+
image_url: { url },
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
runAgent: (text) => this.runAgent(text),
|
|
143
|
+
interruptRun: () => this.agent.shutdown(),
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
loadHistory() {
|
|
148
|
+
if (existsSync(this.historyPath)) {
|
|
149
|
+
try {
|
|
150
|
+
const raw = readFileSync(this.historyPath, "utf-8");
|
|
151
|
+
this.history = raw.split("\n").filter(Boolean).slice(-this.maxHistory);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
this.history = [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
saveHistory() {
|
|
159
|
+
const allHistory = this.history.slice(-this.maxHistory);
|
|
160
|
+
writeFileSync(this.historyPath, allHistory.join("\n"), "utf-8");
|
|
161
|
+
}
|
|
162
|
+
setupCompleter() {
|
|
163
|
+
const slashCommands = Array.from(this.commands.keys());
|
|
164
|
+
this.completer.registerProvider(new SlashCommandProvider(slashCommands));
|
|
165
|
+
if (this.sessionManager) {
|
|
166
|
+
this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name)));
|
|
167
|
+
}
|
|
168
|
+
if (this.skillsModule) {
|
|
169
|
+
this.completer.registerProvider(new SubcommandProvider("skill", ["list", "loaded", "load", "unload", "search"]));
|
|
170
|
+
this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
|
|
171
|
+
}
|
|
172
|
+
// Subcommand + argument completion for provider/model management.
|
|
173
|
+
this.completer.registerProvider(new SubcommandProvider("provider", ["list", "use"]));
|
|
174
|
+
this.completer.registerProvider(new SubcommandProvider("model", ["list", "use"]));
|
|
175
|
+
const entryLabels = this.config.provider?.entries?.map((e) => e.label || e.type) ?? [];
|
|
176
|
+
if (entryLabels.length > 0) {
|
|
177
|
+
this.completer.registerProvider(new ProviderArgProvider(entryLabels));
|
|
178
|
+
}
|
|
179
|
+
this.completer.registerProvider(new ModelArgProvider(() => this.modelCache));
|
|
180
|
+
// Warm the model cache in the background (free listModels call).
|
|
181
|
+
this.refreshModelCache();
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Background refresh of the cached model list used by `/model use <Tab>`.
|
|
185
|
+
* Best-effort: on failure the cache keeps its previous contents.
|
|
186
|
+
*/
|
|
187
|
+
refreshModelCache() {
|
|
188
|
+
void (async () => {
|
|
189
|
+
try {
|
|
190
|
+
const name = this.config.provider.active ||
|
|
191
|
+
this.config.provider.entries?.[0]?.label ||
|
|
192
|
+
this.config.provider.type;
|
|
193
|
+
const provider = this.agent.getProviderFor(name);
|
|
194
|
+
if (!provider)
|
|
195
|
+
return;
|
|
196
|
+
const models = await provider.listModels();
|
|
197
|
+
if (models.length > 0) {
|
|
198
|
+
this.modelCache = models.sort();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// keep previous cache
|
|
203
|
+
}
|
|
204
|
+
})();
|
|
205
|
+
}
|
|
206
|
+
setupListeners() {
|
|
207
|
+
let multiLineBuffer = "";
|
|
208
|
+
let inMultiLine = false;
|
|
209
|
+
this.rl.on("line", async (line) => {
|
|
210
|
+
const trimmed = line.trim();
|
|
211
|
+
if (this.agentRunning || this.inputLocked) {
|
|
212
|
+
if (trimmed) {
|
|
213
|
+
this.history.push(trimmed);
|
|
214
|
+
if (this.history.length > this.maxHistory) {
|
|
215
|
+
this.history = this.history.slice(-this.maxHistory);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (trimmed) {
|
|
221
|
+
this.history.push(trimmed);
|
|
222
|
+
if (this.history.length > this.maxHistory) {
|
|
223
|
+
this.history = this.history.slice(-this.maxHistory);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (inMultiLine) {
|
|
227
|
+
multiLineBuffer += "\n" + line;
|
|
228
|
+
if (!this.isMultiLineInput(multiLineBuffer)) {
|
|
229
|
+
inMultiLine = false;
|
|
230
|
+
const fullInput = multiLineBuffer.trim();
|
|
231
|
+
multiLineBuffer = "";
|
|
232
|
+
if (fullInput) {
|
|
233
|
+
if (fullInput.startsWith("/")) {
|
|
234
|
+
await this.executeCommand(fullInput);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
await this.runAgent(fullInput);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (this.running) {
|
|
241
|
+
this.rl.setPrompt(pc.cyan(t("repl.you")));
|
|
242
|
+
this.rl.prompt();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
this.rl.setPrompt(pc.cyan(t("repl.you") + "… "));
|
|
247
|
+
this.rl.prompt();
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (this.isMultiLineInput(trimmed)) {
|
|
252
|
+
inMultiLine = true;
|
|
253
|
+
multiLineBuffer = trimmed;
|
|
254
|
+
this.rl.setPrompt(pc.cyan(t("repl.you") + "… "));
|
|
255
|
+
this.rl.prompt();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!trimmed) {
|
|
259
|
+
this.rl.setPrompt(pc.cyan(t("repl.you")));
|
|
260
|
+
this.rl.prompt();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (trimmed.startsWith("/")) {
|
|
264
|
+
await this.executeCommand(trimmed);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
await this.runAgent(trimmed);
|
|
268
|
+
}
|
|
269
|
+
if (this.running) {
|
|
270
|
+
this.rl.setPrompt(pc.cyan(t("repl.you")));
|
|
271
|
+
this.rl.prompt();
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
this.rl.on("close", () => {
|
|
275
|
+
this.running = false;
|
|
276
|
+
this.saveHistory();
|
|
277
|
+
this.agent.shutdown();
|
|
278
|
+
// Close session log when REPL exits
|
|
279
|
+
this.logger?.closeSessionLog();
|
|
280
|
+
if (this.exitOnClose)
|
|
281
|
+
process.exit(0);
|
|
282
|
+
});
|
|
283
|
+
if (this.exitOnClose && !process.stdin.isTTY) {
|
|
284
|
+
process.stdin.on("end", () => {
|
|
285
|
+
this.running = false;
|
|
286
|
+
this.saveHistory();
|
|
287
|
+
this.agent.shutdown();
|
|
288
|
+
// Close session log when REPL exits
|
|
289
|
+
this.logger?.closeSessionLog();
|
|
290
|
+
process.exit(0);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (this.rl instanceof LineEditor) {
|
|
294
|
+
this.rl.onKeyInput = (str, key) => this.handleSpecialKey(str, key);
|
|
295
|
+
}
|
|
296
|
+
let forceExitTimer = null;
|
|
297
|
+
// Idempotent registration: a second Repl in the same process must not add
|
|
298
|
+
// another global SIGINT handler. Leaked handlers from REPL instances
|
|
299
|
+
// created in tests fired process.exit(0) on ANY process.emit("SIGINT")
|
|
300
|
+
// elsewhere and silently terminated the whole bun test runner.
|
|
301
|
+
if (typeof Repl.sigintHandler === "function") {
|
|
302
|
+
process.removeListener("SIGINT", Repl.sigintHandler);
|
|
303
|
+
}
|
|
304
|
+
Repl.sigintHandler = () => {
|
|
305
|
+
if (this.agentRunning) {
|
|
306
|
+
console.log(pc.yellow(t("repl.ctrl_c_interrupt")));
|
|
307
|
+
this.agent.shutdown();
|
|
308
|
+
this.agentRunning = false;
|
|
309
|
+
if (forceExitTimer)
|
|
310
|
+
clearTimeout(forceExitTimer);
|
|
311
|
+
forceExitTimer = setTimeout(() => process.exit(1), 2000).unref();
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
process.exit(0);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
process.on("SIGINT", Repl.sigintHandler);
|
|
318
|
+
}
|
|
319
|
+
handleSpecialKey(str, key) {
|
|
320
|
+
if (key.name === "escape") {
|
|
321
|
+
// Bun's keypress decoder collapses a fast double-Esc into a single
|
|
322
|
+
// keypress whose `sequence` contains two ESC bytes ("\x1b\x1b").
|
|
323
|
+
// Counting bytes (not events) catches both the collapsed case and
|
|
324
|
+
// the case where two separate escape keypresses land in the window.
|
|
325
|
+
const escBytes = key.sequence ? (key.sequence.match(/\x1b/g) || []).length : 1;
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
|
|
328
|
+
this.lastEscTime = now;
|
|
329
|
+
if (escBytes >= 2 || withinWindow) {
|
|
330
|
+
this.lastEscTime = 0;
|
|
331
|
+
if (this.agentRunning) {
|
|
332
|
+
process.stdout.write(pc.yellow(`\n${t("repl.interrupt")}\n`));
|
|
333
|
+
this.agent.shutdown();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
if (key.ctrl && key.name === "v" && !this.agentRunning) {
|
|
339
|
+
this.pasteClipboardImage();
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
async pasteClipboardImage() {
|
|
345
|
+
try {
|
|
346
|
+
const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
|
|
347
|
+
const clipBuf = await readClipboardImage();
|
|
348
|
+
if (clipBuf) {
|
|
349
|
+
const { dataUrl } = await bufferToDataUrl(clipBuf);
|
|
350
|
+
this.pendingClipboardImage = dataUrl;
|
|
351
|
+
const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
|
|
352
|
+
console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
|
|
353
|
+
if (this.rl instanceof LineEditor)
|
|
354
|
+
this.rl.reset();
|
|
355
|
+
this.rl.prompt();
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
console.log(pc.yellow(`\n${t("image.clipboard_empty")}`));
|
|
359
|
+
if (this.rl instanceof LineEditor)
|
|
360
|
+
this.rl.reset();
|
|
361
|
+
this.rl.prompt();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// clipboard read failed — ignore, let terminal paste text normally
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
isMultiLineInput(line) {
|
|
369
|
+
if (line.endsWith("\\"))
|
|
370
|
+
return true;
|
|
371
|
+
const openBraces = (line.match(/\{/g) || []).length;
|
|
372
|
+
const closeBraces = (line.match(/\}/g) || []).length;
|
|
373
|
+
if (openBraces > closeBraces)
|
|
374
|
+
return true;
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
async runAgent(input) {
|
|
378
|
+
if (this.agentRunning)
|
|
379
|
+
return;
|
|
380
|
+
this.agentRunning = true;
|
|
381
|
+
try {
|
|
382
|
+
if (this.pendingClipboardImage) {
|
|
383
|
+
const contextManager = this.agent.contextManager;
|
|
384
|
+
if (contextManager) {
|
|
385
|
+
contextManager.addPendingImage({
|
|
386
|
+
type: "image_url",
|
|
387
|
+
image_url: { url: this.pendingClipboardImage },
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
this.pendingClipboardImage = null;
|
|
391
|
+
}
|
|
392
|
+
this.logger?.logREPL("user", input);
|
|
393
|
+
if (this.rl instanceof LineEditor)
|
|
394
|
+
this.rl.reset();
|
|
395
|
+
// Overwrite the empty prompt that reset() just drew with a divider,
|
|
396
|
+
// then label the agent's reply — separates the user's message from
|
|
397
|
+
// the agent's answer.
|
|
398
|
+
process.stdout.write("\r" + divider() + "\n" + pc.green(t("repl.agent")));
|
|
399
|
+
const renderer = new Renderer({
|
|
400
|
+
spinner: this.config.ui?.spinner ?? true,
|
|
401
|
+
toolStyle: this.config.ui?.toolStyle ?? "inline",
|
|
402
|
+
baseDir: this.baseDir,
|
|
403
|
+
});
|
|
404
|
+
const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
|
|
405
|
+
if (ev.type === "start") {
|
|
406
|
+
renderer.toolStart(ev.tool, ev.args, stepContextForTool(this.execModule?.getActivePlan() ?? null, ev.tool, ev.args), ev.icon);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta, ev.costUsd, ev.provider, ev.model);
|
|
410
|
+
if (ev.tool === "plan" || ev.tool === "todo") {
|
|
411
|
+
this.renderPlan(renderer);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}, (phase) => {
|
|
415
|
+
if (phase === "thinking") {
|
|
416
|
+
renderer.thinkingStart();
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
renderer.thinkingEnd();
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
renderer.flush();
|
|
423
|
+
process.stdout.write("\n");
|
|
424
|
+
this.logger?.logREPL(result.success ? "assistant" : "system", result.text?.slice(0, 400) || result.error || "");
|
|
425
|
+
if (!result.success) {
|
|
426
|
+
console.error(pc.red(`${t("error.prefix")}${result.error}`));
|
|
427
|
+
}
|
|
428
|
+
this.showContextBar(result);
|
|
429
|
+
// Close the agent's turn: a divider before the next prompt keeps
|
|
430
|
+
// consecutive user/agent messages visually separated.
|
|
431
|
+
process.stdout.write("\n" + divider() + "\n");
|
|
432
|
+
}
|
|
433
|
+
finally {
|
|
434
|
+
this.agentRunning = false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/** Print the checklist of the plan currently tracked by ExecutionModule.
|
|
438
|
+
* Called only on plan/todo tool events (a change event) — never polled,
|
|
439
|
+
* so a disk-resident plan from a previous session cannot leak into chat. */
|
|
440
|
+
renderPlan(renderer) {
|
|
441
|
+
const plan = this.execModule?.getActivePlan();
|
|
442
|
+
if (!plan)
|
|
443
|
+
return;
|
|
444
|
+
renderer.planBlock(formatPlanChecklist(plan));
|
|
445
|
+
}
|
|
446
|
+
commands = new Map();
|
|
447
|
+
registerCommand(cmd) {
|
|
448
|
+
this.commands.set(cmd.name, cmd);
|
|
449
|
+
if (cmd.aliases) {
|
|
450
|
+
for (const alias of cmd.aliases) {
|
|
451
|
+
this.commands.set(alias, cmd);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async withExclusiveInput(fn) {
|
|
456
|
+
this.inputLocked = true;
|
|
457
|
+
try {
|
|
458
|
+
await fn();
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
this.inputLocked = false;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
async executeCommand(input) {
|
|
465
|
+
const parts = input.split(/\s+/);
|
|
466
|
+
const name = parts[0].slice(1);
|
|
467
|
+
const args = parts.slice(1);
|
|
468
|
+
const cmd = this.commands.get(name);
|
|
469
|
+
if (!cmd) {
|
|
470
|
+
console.log(pc.red(t("cli.unknown_cmd", { name })), t("cli.help_hint"));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
await cmd.action(args);
|
|
475
|
+
}
|
|
476
|
+
catch (err) {
|
|
477
|
+
console.error(pc.red(t("error.command_error", {
|
|
478
|
+
message: err instanceof Error ? err.message : String(err),
|
|
479
|
+
})));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
showHelp() {
|
|
483
|
+
const { COMMAND_GROUPS } = require("./repl-commands");
|
|
484
|
+
const order = ["general", "agent", "session", "skill"];
|
|
485
|
+
const seen = new Set();
|
|
486
|
+
for (const groupKey of order) {
|
|
487
|
+
const cmds = [];
|
|
488
|
+
for (const [name, cmd] of this.commands) {
|
|
489
|
+
if (cmd.aliases?.includes(name))
|
|
490
|
+
continue;
|
|
491
|
+
if (seen.has(cmd.name))
|
|
492
|
+
continue;
|
|
493
|
+
const group = COMMAND_GROUPS[cmd.name] ?? "general";
|
|
494
|
+
if (group !== groupKey)
|
|
495
|
+
continue;
|
|
496
|
+
seen.add(cmd.name);
|
|
497
|
+
cmds.push(cmd);
|
|
498
|
+
}
|
|
499
|
+
if (cmds.length === 0)
|
|
500
|
+
continue;
|
|
501
|
+
console.log(pc.bold(t(`repl.group.${groupKey}`)));
|
|
502
|
+
for (const cmd of cmds) {
|
|
503
|
+
const aliases = cmd.aliases?.length ? ` (${pc.dim(cmd.aliases.join(", "))})` : "";
|
|
504
|
+
console.log(` ${pc.cyan("/" + cmd.name)}${aliases} ${pc.dim(cmd.description)}`);
|
|
505
|
+
if (cmd.usage) {
|
|
506
|
+
console.log(` ${pc.dim(cmd.usage)}`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
console.log();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
lastCompactionShown = 0;
|
|
513
|
+
showContextBar(result) {
|
|
514
|
+
if (result.contextUsed === undefined ||
|
|
515
|
+
result.contextLimit === undefined ||
|
|
516
|
+
result.contextLimit <= 0) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const ui = this.config.ui;
|
|
520
|
+
if (ui?.showContextStats) {
|
|
521
|
+
console.log();
|
|
522
|
+
const ctxLine = formatContextBar(result.contextUsed, result.contextLimit, result.compactionCount, result.contextQuality);
|
|
523
|
+
console.log(ctxLine);
|
|
524
|
+
if (result.totalTokens !== undefined && result.totalTokens > 0) {
|
|
525
|
+
const apiLine = pc.dim(` API: ${result.promptTokens} prompt + ${result.completionTokens} completion = ${result.totalTokens} total`);
|
|
526
|
+
console.log(apiLine);
|
|
527
|
+
}
|
|
528
|
+
if (result.totalCost !== undefined && result.totalCost > 0) {
|
|
529
|
+
console.log(pc.yellow(` ${t("repl.cost", { cost: formatCost(result.totalCost) })}`));
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
else if (ui?.showCompaction && result.compactionCount !== undefined) {
|
|
533
|
+
if (result.compactionCount > this.lastCompactionShown) {
|
|
534
|
+
this.lastCompactionShown = result.compactionCount;
|
|
535
|
+
console.log(pc.dim(`\n ⟳ Context compacted (${result.compactionCount})`));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async start() {
|
|
540
|
+
this.running = true;
|
|
541
|
+
const info = [];
|
|
542
|
+
const row = (label, value) => {
|
|
543
|
+
info.push(` ${pc.yellow(label)} ${value}`);
|
|
544
|
+
};
|
|
545
|
+
const ctx = this.config.contextWindow;
|
|
546
|
+
const sysBudget = Math.floor(ctx * this.config.contextBudget.systemPrompt);
|
|
547
|
+
const resBudget = Math.floor(ctx * this.config.contextBudget.responseReserve);
|
|
548
|
+
const histBudget = ctx - sysBudget - resBudget;
|
|
549
|
+
row(t("repl.model"), pc.white(this.config.model));
|
|
550
|
+
row(t("repl.provider"), `${this.config.provider.type} → ${pc.dim(this.config.provider.baseUrl)}`);
|
|
551
|
+
row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(sys:${sysBudget} res:${resBudget} hist:${histBudget})`)}`);
|
|
552
|
+
const si = this.agent.getSystemPromptInfo();
|
|
553
|
+
row(t("repl.sysprompt_label"), pc.dim(t("repl.sysprompt_size", { used: si.tokenCount, budget: sysBudget })));
|
|
554
|
+
if (this.skillsModule) {
|
|
555
|
+
const budget = this.skillsModule.getBudget();
|
|
556
|
+
row(t("repl.skills_label"), `${pc.white(String(this.skillsModule.getAvailable().length))} available, ${pc.dim(`budget: ${budget.total} tokens`)}`);
|
|
557
|
+
}
|
|
558
|
+
if (this.pluginManager) {
|
|
559
|
+
const infos = this.pluginManager.getPluginInfos().filter(({ plugin }) => !plugin.isBuiltin);
|
|
560
|
+
if (infos.length > 0) {
|
|
561
|
+
const pluginNames = infos
|
|
562
|
+
.map(({ plugin, source }) => {
|
|
563
|
+
const version = plugin.version ? `${pc.dim(plugin.version)}` : "";
|
|
564
|
+
const origin = source ? pc.dim(` [${source}]`) : "";
|
|
565
|
+
return `${plugin.name}${version}${origin}`;
|
|
566
|
+
})
|
|
567
|
+
.join(", ");
|
|
568
|
+
row(t("repl.plugins_label"), `${pc.white(String(infos.length))} active ${pc.dim(`(${pluginNames})`)}`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const mcpServers = this.config.mcpServers || {};
|
|
572
|
+
const enabledServers = Object.entries(mcpServers).filter(([, s]) => s.enabled !== false);
|
|
573
|
+
if (enabledServers.length > 0) {
|
|
574
|
+
const names = enabledServers.map(([name]) => name).join(", ");
|
|
575
|
+
row(t("repl.mcp_label"), `${pc.white(String(enabledServers.length))} ${pc.dim(`(${names})`)}`);
|
|
576
|
+
}
|
|
577
|
+
const cwd = process.cwd();
|
|
578
|
+
row(t("repl.work_dir"), pc.dim(cwd));
|
|
579
|
+
if (this.noAgentsMd) {
|
|
580
|
+
row(t("repl.agents_label"), pc.red(t("repl.disabled")));
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
const agentsMdCandidates = [
|
|
584
|
+
join(this.baseDir, "AGENTS.md"),
|
|
585
|
+
join(this.baseDir, ".mma", "AGENTS.md"),
|
|
586
|
+
join(this.configDir, "AGENTS.md"),
|
|
587
|
+
];
|
|
588
|
+
const foundAgents = agentsMdCandidates.filter((p) => existsSync(p));
|
|
589
|
+
if (foundAgents.length > 0) {
|
|
590
|
+
for (const p of foundAgents) {
|
|
591
|
+
row(t("repl.agents_label"), pc.dim(p));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
row(t("repl.agents_label"), pc.dim(t("repl.not_found")));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const meta = this.sessionManager?.getActiveMeta();
|
|
599
|
+
if (meta) {
|
|
600
|
+
const sessionPath = join(this.configDir, "sessions", meta.id);
|
|
601
|
+
row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
|
|
602
|
+
}
|
|
603
|
+
// LSP status is probed in the background — it must never block the banner.
|
|
604
|
+
const isTty = process.stdout.isTTY === true;
|
|
605
|
+
const lspEnabled = isTty && (this.config.lsp ?? DEFAULT_LSP_CONFIG).enabled !== false;
|
|
606
|
+
// Plain banner without a surrounding box — line-based, wraps to width.
|
|
607
|
+
for (const line of info) {
|
|
608
|
+
console.log(pc.dim(line).trimEnd());
|
|
609
|
+
}
|
|
610
|
+
console.log();
|
|
611
|
+
// Legacy config hint
|
|
612
|
+
try {
|
|
613
|
+
const { existsSync: exists } = await import("fs");
|
|
614
|
+
const { join: pathJoin } = await import("path");
|
|
615
|
+
const { hasDomainFiles } = await import("../config/domains");
|
|
616
|
+
const legacyPath = pathJoin(this.configDir, "config.json");
|
|
617
|
+
if (exists(legacyPath) && !hasDomainFiles(this.configDir)) {
|
|
618
|
+
console.log(pc.yellow(` ${t("config.legacy_hint")}`));
|
|
619
|
+
console.log();
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
catch { }
|
|
623
|
+
this.rl.prompt();
|
|
624
|
+
if (lspEnabled) {
|
|
625
|
+
this.probeLspBanner()
|
|
626
|
+
.then((lspSummary) => {
|
|
627
|
+
if (lspSummary) {
|
|
628
|
+
const line = `${pc.green(t("repl.agent"))}${pc.yellow(t("repl.lsp_label"))} ${lspSummary}`;
|
|
629
|
+
process.stdout.write(`\x1b[2K\r${divider()}\n${line}\n`);
|
|
630
|
+
this.rl.prompt();
|
|
631
|
+
}
|
|
632
|
+
})
|
|
633
|
+
.catch(() => { });
|
|
634
|
+
}
|
|
635
|
+
if ((this.config.provider.entries?.length ?? 0) > 1) {
|
|
636
|
+
// Phase 4.4: background health probe of all entries (free listModels
|
|
637
|
+
// calls). Failed providers are surfaced as warnings — failover (Phase
|
|
638
|
+
// 4.2) would silently mask a dead primary.
|
|
639
|
+
void probeProviders(targetsFromProviders((this.config.provider.entries ?? [])
|
|
640
|
+
.map((e) => ({
|
|
641
|
+
name: e.label || e.type,
|
|
642
|
+
provider: this.agent.getProviderFor(e.label || e.type),
|
|
643
|
+
}))
|
|
644
|
+
.filter((x) => Boolean(x.provider))), { timeoutMs: 5000 })
|
|
645
|
+
.then((results) => {
|
|
646
|
+
for (const r of results) {
|
|
647
|
+
if (!r.ok && r.name !== this.config.provider.active) {
|
|
648
|
+
writeWarning(t("repl.provider_down", { name: r.name, error: r.error ?? "" }));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
})
|
|
652
|
+
.catch(() => { });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Probe the LSP servers applicable to the current project and format a
|
|
657
|
+
* one-line banner summary, e.g. "✓ typescript (1.2s)" or "✗ css — timeout".
|
|
658
|
+
* Returns null when LSP is disabled or no server applies.
|
|
659
|
+
*/
|
|
660
|
+
async probeLspBanner() {
|
|
661
|
+
const config = this.config.lsp ?? DEFAULT_LSP_CONFIG;
|
|
662
|
+
try {
|
|
663
|
+
const lspOnlyLogger = this.logger ? new FileOnlyLogger(this.logger) : undefined;
|
|
664
|
+
const summary = await probeLspServers(config, this.baseDir, { logger: lspOnlyLogger });
|
|
665
|
+
// Log LSP probe results to session for post-mortem visibility
|
|
666
|
+
this.slog.logSessionStart({
|
|
667
|
+
environment: this.envReport,
|
|
668
|
+
lspProbe: {
|
|
669
|
+
ok: summary.ok.map((r) => ({ language: r.language, durationMs: r.durationMs })),
|
|
670
|
+
failed: summary.failed.map((r) => ({
|
|
671
|
+
language: r.language,
|
|
672
|
+
error: r.error,
|
|
673
|
+
durationMs: r.durationMs,
|
|
674
|
+
})),
|
|
675
|
+
noneApplicable: summary.noneApplicable,
|
|
676
|
+
},
|
|
677
|
+
});
|
|
678
|
+
if (summary.noneApplicable)
|
|
679
|
+
return null;
|
|
680
|
+
const parts = [];
|
|
681
|
+
for (const r of summary.ok) {
|
|
682
|
+
parts.push(pc.green(`✓ ${r.language}${pc.dim(` (${(r.durationMs / 1000).toFixed(1)}s)`)}`));
|
|
683
|
+
}
|
|
684
|
+
for (const r of summary.failed) {
|
|
685
|
+
const reason = r.error?.includes("timeout") ? t("repl.lsp_timeout") : t("repl.lsp_failed");
|
|
686
|
+
parts.push(pc.red(`✗ ${r.language}${pc.dim(` — ${reason}`)}`));
|
|
687
|
+
}
|
|
688
|
+
return parts.join(" ");
|
|
689
|
+
}
|
|
690
|
+
catch {
|
|
691
|
+
return pc.yellow(t("repl.lsp_unknown"));
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
stop() {
|
|
695
|
+
this.running = false;
|
|
696
|
+
this.saveHistory();
|
|
697
|
+
this.agent.shutdown();
|
|
698
|
+
// Close session log when REPL stops
|
|
699
|
+
this.logger?.closeSessionLog();
|
|
700
|
+
this.rl.close();
|
|
701
|
+
}
|
|
702
|
+
}
|