micro-models-agent 0.45.0 → 0.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +312 -312
- package/dist/cli/commands.js +323 -0
- package/dist/cli/completer.js +167 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/main.js +165 -0
- package/dist/cli/plugin-commands.js +36 -0
- package/dist/cli/repl-commands.js +661 -0
- package/dist/cli/repl.js +616 -0
- package/dist/cli/run-result.js +22 -0
- package/dist/cli/security-commands.js +164 -0
- package/dist/cli/setup.js +231 -0
- package/dist/config/config.js +249 -0
- package/dist/config/defaults.js +124 -0
- package/dist/config/experts.js +15 -0
- package/dist/config/index.js +3 -0
- package/dist/config/security.js +193 -0
- package/dist/config/types.js +1 -0
- package/dist/core/agent-moe.js +102 -0
- package/dist/core/agent.js +886 -0
- package/dist/core/bootstrap.js +404 -0
- package/dist/core/index.js +2 -0
- package/dist/core/prompt-builder.js +76 -0
- package/dist/core/session-logger.js +197 -0
- package/dist/core/types.js +1 -0
- package/dist/core/version.js +24 -0
- package/dist/core/workspace.js +76 -0
- package/dist/i18n/en.json +598 -0
- package/dist/i18n/index.js +46 -0
- package/dist/i18n/ru.json +598 -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 +359 -0
- package/dist/llm/orchestrator.js +198 -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 +143 -0
- package/dist/logger/file-log.js +151 -0
- package/dist/logger/index.js +1 -0
- package/dist/main.js +284 -29
- 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 +338 -0
- package/dist/modules/browser/snapshot.js +148 -0
- package/dist/modules/browser/types.js +12 -0
- package/dist/modules/certification/cli.js +174 -0
- package/dist/modules/certification/fact-checker.js +82 -0
- package/dist/modules/certification/loader.js +105 -0
- package/dist/modules/certification/manifest.js +50 -0
- package/dist/modules/certification/runner.js +159 -0
- package/dist/modules/certification/scenarios.js +124 -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 +162 -0
- package/dist/modules/context/history.js +15 -0
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +423 -0
- package/dist/modules/execution/audit-runners.js +152 -0
- package/dist/modules/execution/auditor.js +218 -0
- package/dist/modules/execution/execution-plugin.js +272 -0
- package/dist/modules/execution/index.js +8 -0
- package/dist/modules/execution/module.js +436 -0
- package/dist/modules/execution/moe-executor.js +291 -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 +157 -0
- package/dist/modules/execution/plan-tool.js +508 -0
- package/dist/modules/execution/plan-validator.js +153 -0
- package/dist/modules/execution/planner.js +90 -0
- package/dist/modules/execution/stuck-detector.js +510 -0
- package/dist/modules/execution/tracker.js +67 -0
- package/dist/modules/execution/types.js +1 -0
- package/dist/modules/execution/verifier.js +222 -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 +43 -0
- package/dist/modules/hallucination/factual.js +129 -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 +245 -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 +278 -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 +232 -0
- package/dist/modules/lsp/probe.js +76 -0
- package/dist/modules/lsp/project-root.js +32 -0
- package/dist/modules/lsp/startup-check.js +141 -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 +231 -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 +217 -0
- package/dist/modules/plugins/types.js +1 -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/registry.js +47 -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 +205 -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 +71 -0
- package/dist/modules/security/path-validator.js +207 -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 +32 -0
- package/dist/tools/attach-image.js +89 -0
- package/dist/tools/bash.js +496 -0
- package/dist/tools/browser.js +114 -0
- package/dist/tools/chunk-query.js +99 -0
- package/dist/tools/create-dir.js +55 -0
- package/dist/tools/delete-file.js +62 -0
- package/dist/tools/download-file.js +116 -0
- package/dist/tools/edit-file.js +79 -0
- package/dist/tools/enable-tools.js +58 -0
- package/dist/tools/executor.js +144 -0
- package/dist/tools/file-info.js +46 -0
- package/dist/tools/filter-tools.js +17 -0
- package/dist/tools/glob-tool.js +26 -0
- package/dist/tools/grep-tool.js +84 -0
- package/dist/tools/hidden-tools-block.js +37 -0
- package/dist/tools/index.js +78 -0
- package/dist/tools/list-dir.js +48 -0
- package/dist/tools/load-skill.js +42 -0
- package/dist/tools/mcp-call.js +68 -0
- package/dist/tools/move-file.js +85 -0
- package/dist/tools/path-utils.js +51 -0
- package/dist/tools/pipeline-run.js +144 -0
- package/dist/tools/preview.js +2 -0
- package/dist/tools/process-kill.js +29 -0
- package/dist/tools/process-list.js +36 -0
- package/dist/tools/process-log.js +45 -0
- package/dist/tools/question.js +140 -0
- package/dist/tools/read-file.js +91 -0
- package/dist/tools/recall.js +117 -0
- package/dist/tools/registry.js +47 -0
- package/dist/tools/remember.js +67 -0
- package/dist/tools/scope-check.js +30 -0
- package/dist/tools/search-history.js +84 -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 +86 -0
- package/dist/tools/web-fetch.js +98 -0
- package/dist/tools/web-search.js +78 -0
- package/dist/tools/write-file.js +81 -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 +703 -0
- package/dist/ui/line-math.js +69 -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 +209 -0
- package/dist/ui/spinner.js +70 -0
- package/dist/ui/table.js +144 -0
- package/package.json +48 -48
package/dist/main.js
CHANGED
|
@@ -2263,7 +2263,9 @@ var init_defaults = __esm(() => {
|
|
|
2263
2263
|
retry: {
|
|
2264
2264
|
maxRetries: 3,
|
|
2265
2265
|
baseDelay: 1000,
|
|
2266
|
-
maxDelay: 30000
|
|
2266
|
+
maxDelay: 30000,
|
|
2267
|
+
maxStreamRetries: 2,
|
|
2268
|
+
noDataTimeoutMs: 60000
|
|
2267
2269
|
},
|
|
2268
2270
|
maxToolIterations: 1000,
|
|
2269
2271
|
stuckThreshold: 6,
|
|
@@ -2396,6 +2398,8 @@ Use read_file on {path} to see the current content before editing — the target
|
|
|
2396
2398
|
"error.llm_retries": "LLM request failed after retries",
|
|
2397
2399
|
"error.llm_429": "Rate limit exceeded (HTTP 429) for {model} on {baseUrl}. The provider is throttling requests — free models are especially strict. Get an API key or switch to a paid/faster model: {baseUrl}",
|
|
2398
2400
|
"error.no_response_body": "No response body stream",
|
|
2401
|
+
"error.llm_stream_idle": "LLM stream stalled — no data for {timeout}ms",
|
|
2402
|
+
"error.llm_timeout": "LLM request timed out ({timeout}ms)",
|
|
2399
2403
|
"session.started": "Session started: {id}",
|
|
2400
2404
|
"session.ended": "Session ended: {id}",
|
|
2401
2405
|
"session.not_found": "Session not found: {id}",
|
|
@@ -2779,6 +2783,19 @@ Available commands:`,
|
|
|
2779
2783
|
"repl.lsp_timeout": "timeout",
|
|
2780
2784
|
"repl.lsp_failed": "did not start",
|
|
2781
2785
|
"repl.lsp_unknown": "unknown",
|
|
2786
|
+
"repl.lsp": "LSP server management (status, restart, check)",
|
|
2787
|
+
"repl.lsp_usage": "Usage: /lsp [status|restart|check <path>]",
|
|
2788
|
+
"repl.lsp_not_available": "LSP module not available",
|
|
2789
|
+
"repl.lsp_status_header": "LSP Status:",
|
|
2790
|
+
"repl.lsp_enabled": "Enabled:",
|
|
2791
|
+
"repl.lsp_disabled_servers": "Disabled servers:",
|
|
2792
|
+
"repl.lsp_failure_counts": "Failure counts:",
|
|
2793
|
+
"repl.lsp_all_ok": "All servers operational",
|
|
2794
|
+
"repl.lsp_restarting": "Resetting LSP server state...",
|
|
2795
|
+
"repl.lsp_restarted": "LSP servers reset. Failed servers will be retried on next use.",
|
|
2796
|
+
"repl.lsp_check_usage": "Usage: /lsp check <file-or-directory>",
|
|
2797
|
+
"repl.lsp_checking": "Running LSP check on {path}...",
|
|
2798
|
+
"repl.lsp_check_error": "LSP check failed: {error}",
|
|
2782
2799
|
"repl.work_dir": "Dir:",
|
|
2783
2800
|
"repl.agents_label": "Instructions:",
|
|
2784
2801
|
"repl.not_found": "not found",
|
|
@@ -3059,6 +3076,8 @@ var init_ru = __esm(() => {
|
|
|
3059
3076
|
"error.llm_retries": "Запрос LLM не удался после повторов",
|
|
3060
3077
|
"error.llm_429": "Превышен лимит запросов (HTTP 429) для {model} на {baseUrl}. Провайдер ограничивает трафик — особенно строгие free-модели. Получите API-ключ или переключитесь на платную/быструю модель: {baseUrl}",
|
|
3061
3078
|
"error.no_response_body": "Нет потока тела ответа",
|
|
3079
|
+
"error.llm_stream_idle": "Поток LLM завис — нет данных {timeout}мс",
|
|
3080
|
+
"error.llm_timeout": "Время запроса LLM истекло ({timeout}мс)",
|
|
3062
3081
|
"session.started": "Сессия начата: {id}",
|
|
3063
3082
|
"session.ended": "Сессия завершена: {id}",
|
|
3064
3083
|
"session.not_found": "Сессия не найдена: {id}",
|
|
@@ -3437,6 +3456,19 @@ var init_ru = __esm(() => {
|
|
|
3437
3456
|
"repl.lsp_timeout": "таймаут",
|
|
3438
3457
|
"repl.lsp_failed": "не стартовал",
|
|
3439
3458
|
"repl.lsp_unknown": "неизвестно",
|
|
3459
|
+
"repl.lsp": "Управление LSP-серверами (status, restart, check)",
|
|
3460
|
+
"repl.lsp_usage": "Использование: /lsp [status|restart|check <путь>]",
|
|
3461
|
+
"repl.lsp_not_available": "Модуль LSP недоступен",
|
|
3462
|
+
"repl.lsp_status_header": "Статус LSP:",
|
|
3463
|
+
"repl.lsp_enabled": "Включён:",
|
|
3464
|
+
"repl.lsp_disabled_servers": "Отключённые серверы:",
|
|
3465
|
+
"repl.lsp_failure_counts": "Количество ошибок:",
|
|
3466
|
+
"repl.lsp_all_ok": "Все серверы работают",
|
|
3467
|
+
"repl.lsp_restarting": "Сброс состояния LSP-серверов...",
|
|
3468
|
+
"repl.lsp_restarted": "LSP-серверы сброшены. Упавшие серверы будут повторно запущены при следующем использовании.",
|
|
3469
|
+
"repl.lsp_check_usage": "Использование: /lsp check <файл-или-каталог>",
|
|
3470
|
+
"repl.lsp_checking": "Запуск LSP-проверки {path}...",
|
|
3471
|
+
"repl.lsp_check_error": "LSP-проверка не удалась: {error}",
|
|
3440
3472
|
"repl.work_dir": "Директория:",
|
|
3441
3473
|
"repl.agents_label": "Инструкции:",
|
|
3442
3474
|
"repl.not_found": "не найден",
|
|
@@ -5172,7 +5204,9 @@ class OpenAICompatProvider {
|
|
|
5172
5204
|
this.retryConfig = config.retry ?? {
|
|
5173
5205
|
maxRetries: 3,
|
|
5174
5206
|
baseDelay: 1000,
|
|
5175
|
-
maxDelay: 30000
|
|
5207
|
+
maxDelay: 30000,
|
|
5208
|
+
maxStreamRetries: 2,
|
|
5209
|
+
noDataTimeoutMs: 60000
|
|
5176
5210
|
};
|
|
5177
5211
|
this.rateLimiter = createRateLimiter(config.rateLimits);
|
|
5178
5212
|
}
|
|
@@ -5203,6 +5237,32 @@ class OpenAICompatProvider {
|
|
|
5203
5237
|
}
|
|
5204
5238
|
}
|
|
5205
5239
|
async* doStream(messages, tools, signal, options) {
|
|
5240
|
+
const { baseDelay, maxDelay, maxStreamRetries, noDataTimeoutMs } = this.retryConfig;
|
|
5241
|
+
const streamRetries = maxStreamRetries ?? 2;
|
|
5242
|
+
const idleTimeoutMs = noDataTimeoutMs ?? 60000;
|
|
5243
|
+
for (let attempt = 0;; attempt++) {
|
|
5244
|
+
let emitted = false;
|
|
5245
|
+
const onEmit = () => {
|
|
5246
|
+
emitted = true;
|
|
5247
|
+
};
|
|
5248
|
+
try {
|
|
5249
|
+
const sawDone = yield* this.streamOnce(messages, tools, signal, options, onEmit, idleTimeoutMs);
|
|
5250
|
+
if (sawDone || emitted)
|
|
5251
|
+
return;
|
|
5252
|
+
if (attempt >= streamRetries)
|
|
5253
|
+
return;
|
|
5254
|
+
} catch (err) {
|
|
5255
|
+
if (err?.name === "AbortError" || err?.llmTerminal || signal?.aborted)
|
|
5256
|
+
throw err;
|
|
5257
|
+
if (emitted || attempt >= streamRetries)
|
|
5258
|
+
throw err;
|
|
5259
|
+
}
|
|
5260
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
5261
|
+
const jitter = Math.random() * baseDelay * 0.1;
|
|
5262
|
+
await this.sleep(delay + jitter, signal);
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
5265
|
+
async* streamOnce(messages, tools, signal, options, onEmit, idleTimeoutMs) {
|
|
5206
5266
|
const body = buildRequestBody({
|
|
5207
5267
|
model: this.model,
|
|
5208
5268
|
messages,
|
|
@@ -5218,8 +5278,11 @@ class OpenAICompatProvider {
|
|
|
5218
5278
|
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
5219
5279
|
}
|
|
5220
5280
|
const controller = new AbortController;
|
|
5221
|
-
|
|
5222
|
-
const timeoutId = setTimeout(() =>
|
|
5281
|
+
let timedOut = false;
|
|
5282
|
+
const timeoutId = setTimeout(() => {
|
|
5283
|
+
timedOut = true;
|
|
5284
|
+
controller.abort();
|
|
5285
|
+
}, REQUEST_TIMEOUT_MS);
|
|
5223
5286
|
const abortSignal = (() => {
|
|
5224
5287
|
if (!signal)
|
|
5225
5288
|
return controller.signal;
|
|
@@ -5232,20 +5295,31 @@ class OpenAICompatProvider {
|
|
|
5232
5295
|
return controller.signal;
|
|
5233
5296
|
}
|
|
5234
5297
|
})();
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5298
|
+
let response;
|
|
5299
|
+
try {
|
|
5300
|
+
response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
|
|
5301
|
+
method: "POST",
|
|
5302
|
+
headers,
|
|
5303
|
+
body: JSON.stringify(body),
|
|
5304
|
+
signal: abortSignal
|
|
5305
|
+
});
|
|
5306
|
+
} catch (err) {
|
|
5307
|
+
if (err?.name === "AbortError")
|
|
5308
|
+
throw err;
|
|
5309
|
+
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
5310
|
+
wrapped.llmTerminal = true;
|
|
5311
|
+
throw wrapped;
|
|
5312
|
+
}
|
|
5241
5313
|
if (!response.ok) {
|
|
5242
5314
|
clearTimeout(timeoutId);
|
|
5243
5315
|
const errorText = await response.text();
|
|
5244
|
-
|
|
5316
|
+
const err = new Error(t("error.llm_api", {
|
|
5245
5317
|
status: response.status,
|
|
5246
5318
|
statusText: response.statusText,
|
|
5247
5319
|
errorText
|
|
5248
5320
|
}));
|
|
5321
|
+
err.llmTerminal = true;
|
|
5322
|
+
throw err;
|
|
5249
5323
|
}
|
|
5250
5324
|
const reader = response.body?.getReader();
|
|
5251
5325
|
if (!reader) {
|
|
@@ -5256,9 +5330,29 @@ class OpenAICompatProvider {
|
|
|
5256
5330
|
let buffer = "";
|
|
5257
5331
|
const toolCallAccs = new Map;
|
|
5258
5332
|
let usage;
|
|
5333
|
+
let sawDone = false;
|
|
5334
|
+
const readIdle = () => new Promise((resolve, reject) => {
|
|
5335
|
+
const idleTimer = setTimeout(() => {
|
|
5336
|
+
timedOut = true;
|
|
5337
|
+
controller.abort();
|
|
5338
|
+
}, idleTimeoutMs);
|
|
5339
|
+
reader.read().then((result) => {
|
|
5340
|
+
clearTimeout(idleTimer);
|
|
5341
|
+
if (timedOut)
|
|
5342
|
+
reject(new Error(t("error.llm_stream_idle", { timeout: idleTimeoutMs })));
|
|
5343
|
+
else
|
|
5344
|
+
resolve(result);
|
|
5345
|
+
}, (err) => {
|
|
5346
|
+
clearTimeout(idleTimer);
|
|
5347
|
+
if (timedOut)
|
|
5348
|
+
reject(new Error(t("error.llm_stream_idle", { timeout: idleTimeoutMs })));
|
|
5349
|
+
else
|
|
5350
|
+
reject(err);
|
|
5351
|
+
});
|
|
5352
|
+
});
|
|
5259
5353
|
try {
|
|
5260
5354
|
while (true) {
|
|
5261
|
-
const { done, value } = await
|
|
5355
|
+
const { done, value } = await readIdle();
|
|
5262
5356
|
if (done)
|
|
5263
5357
|
break;
|
|
5264
5358
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -5270,8 +5364,10 @@ class OpenAICompatProvider {
|
|
|
5270
5364
|
if (!trimmed || !trimmed.startsWith("data: "))
|
|
5271
5365
|
continue;
|
|
5272
5366
|
const data = trimmed.slice(6);
|
|
5273
|
-
if (data === "[DONE]")
|
|
5367
|
+
if (data === "[DONE]") {
|
|
5368
|
+
sawDone = true;
|
|
5274
5369
|
continue;
|
|
5370
|
+
}
|
|
5275
5371
|
try {
|
|
5276
5372
|
const parsed = JSON.parse(data);
|
|
5277
5373
|
const choice = parsed.choices?.[0];
|
|
@@ -5288,6 +5384,7 @@ class OpenAICompatProvider {
|
|
|
5288
5384
|
const delta = choice.delta || {};
|
|
5289
5385
|
const finishReason = choice.finish_reason;
|
|
5290
5386
|
if (delta.reasoning_content) {
|
|
5387
|
+
onEmit();
|
|
5291
5388
|
yield { type: "reasoning", content: delta.reasoning_content };
|
|
5292
5389
|
}
|
|
5293
5390
|
if (delta.tool_calls) {
|
|
@@ -5307,11 +5404,13 @@ class OpenAICompatProvider {
|
|
|
5307
5404
|
}
|
|
5308
5405
|
}
|
|
5309
5406
|
if (delta.content) {
|
|
5407
|
+
onEmit();
|
|
5310
5408
|
yield { type: "text", content: delta.content };
|
|
5311
5409
|
}
|
|
5312
5410
|
if (finishReason === "tool_calls" && toolCallAccs.size > 0) {
|
|
5313
5411
|
for (const [, acc] of toolCallAccs) {
|
|
5314
5412
|
if (acc.name) {
|
|
5413
|
+
onEmit();
|
|
5315
5414
|
yield {
|
|
5316
5415
|
type: "tool_call",
|
|
5317
5416
|
toolCall: {
|
|
@@ -5328,12 +5427,14 @@ class OpenAICompatProvider {
|
|
|
5328
5427
|
}
|
|
5329
5428
|
}
|
|
5330
5429
|
if (usage) {
|
|
5430
|
+
onEmit();
|
|
5331
5431
|
yield { type: "done", usage };
|
|
5332
5432
|
}
|
|
5333
5433
|
} finally {
|
|
5334
5434
|
clearTimeout(timeoutId);
|
|
5335
5435
|
reader.releaseLock();
|
|
5336
5436
|
}
|
|
5437
|
+
return sawDone;
|
|
5337
5438
|
}
|
|
5338
5439
|
async doNonStreaming(messages, tools, signal, options) {
|
|
5339
5440
|
const body = buildRequestBody({
|
|
@@ -5350,12 +5451,30 @@ class OpenAICompatProvider {
|
|
|
5350
5451
|
if (this.config.apiKey && this.config.apiKey !== "not-needed") {
|
|
5351
5452
|
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
5352
5453
|
}
|
|
5454
|
+
const controller = new AbortController;
|
|
5455
|
+
let timedOut = false;
|
|
5456
|
+
const timeoutId = setTimeout(() => {
|
|
5457
|
+
timedOut = true;
|
|
5458
|
+
controller.abort();
|
|
5459
|
+
}, REQUEST_TIMEOUT_MS);
|
|
5460
|
+
const abortSignal = (() => {
|
|
5461
|
+
if (!signal)
|
|
5462
|
+
return controller.signal;
|
|
5463
|
+
try {
|
|
5464
|
+
return AbortSignal.any([controller.signal, signal]);
|
|
5465
|
+
} catch {
|
|
5466
|
+
signal.addEventListener("abort", () => controller.abort(), {
|
|
5467
|
+
once: true
|
|
5468
|
+
});
|
|
5469
|
+
return controller.signal;
|
|
5470
|
+
}
|
|
5471
|
+
})();
|
|
5353
5472
|
try {
|
|
5354
5473
|
const response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
|
|
5355
5474
|
method: "POST",
|
|
5356
5475
|
headers,
|
|
5357
5476
|
body: JSON.stringify(body),
|
|
5358
|
-
signal
|
|
5477
|
+
signal: abortSignal
|
|
5359
5478
|
});
|
|
5360
5479
|
if (!response.ok) {
|
|
5361
5480
|
const errorText = await response.text();
|
|
@@ -5402,7 +5521,12 @@ class OpenAICompatProvider {
|
|
|
5402
5521
|
}
|
|
5403
5522
|
return chunks;
|
|
5404
5523
|
} catch (err) {
|
|
5524
|
+
if (timedOut && err?.name === "AbortError") {
|
|
5525
|
+
throw new Error(t("error.llm_timeout", { timeout: REQUEST_TIMEOUT_MS }));
|
|
5526
|
+
}
|
|
5405
5527
|
throw err instanceof Error ? err : new Error(String(err));
|
|
5528
|
+
} finally {
|
|
5529
|
+
clearTimeout(timeoutId);
|
|
5406
5530
|
}
|
|
5407
5531
|
}
|
|
5408
5532
|
countTokens(text) {
|
|
@@ -5486,6 +5610,7 @@ class OpenAICompatProvider {
|
|
|
5486
5610
|
});
|
|
5487
5611
|
}
|
|
5488
5612
|
}
|
|
5613
|
+
var REQUEST_TIMEOUT_MS = 120000;
|
|
5489
5614
|
var init_openai_compat = __esm(() => {
|
|
5490
5615
|
init_token_counter();
|
|
5491
5616
|
init_i18n();
|
|
@@ -11587,6 +11712,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
11587
11712
|
onPhase?.(phase);
|
|
11588
11713
|
}
|
|
11589
11714
|
async run(input, onChunk, onMeta, onTool, onPhase) {
|
|
11715
|
+
this.shutdownRequested = false;
|
|
11590
11716
|
this.setScope();
|
|
11591
11717
|
const {
|
|
11592
11718
|
config,
|
|
@@ -12259,7 +12385,6 @@ ${warnLine}
|
|
|
12259
12385
|
if (killed > 0) {
|
|
12260
12386
|
logger.info(`Killed ${killed} background process(es) on shutdown`);
|
|
12261
12387
|
}
|
|
12262
|
-
logger.closeSessionLog();
|
|
12263
12388
|
pluginManager.runOnSessionEnd({
|
|
12264
12389
|
logger,
|
|
12265
12390
|
sessionManager: sessionManager?.getActiveMeta(),
|
|
@@ -19915,6 +20040,10 @@ import { resolve as resolve20 } from "path";
|
|
|
19915
20040
|
import { platform as platform8 } from "os";
|
|
19916
20041
|
|
|
19917
20042
|
class LspClient {
|
|
20043
|
+
logger = null;
|
|
20044
|
+
setLogger(logger) {
|
|
20045
|
+
this.logger = logger;
|
|
20046
|
+
}
|
|
19918
20047
|
process = null;
|
|
19919
20048
|
requestId = 0;
|
|
19920
20049
|
pending = new Map;
|
|
@@ -19974,11 +20103,25 @@ class LspClient {
|
|
|
19974
20103
|
try {
|
|
19975
20104
|
await this.sendRequest("initialize", initParams, timeout);
|
|
19976
20105
|
} catch (e) {
|
|
20106
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
20107
|
+
this.logger?.warn(`LSP initialize failed (attempt 1): ${msg}`, {
|
|
20108
|
+
command: config.command,
|
|
20109
|
+
projectRoot
|
|
20110
|
+
});
|
|
19977
20111
|
if (!(e instanceof Error) || !e.message.includes("initialize"))
|
|
19978
20112
|
throw e;
|
|
19979
20113
|
await this.shutdown();
|
|
19980
20114
|
await this.startServer(config, projectRoot);
|
|
19981
|
-
|
|
20115
|
+
try {
|
|
20116
|
+
await this.sendRequest("initialize", initParams, timeout);
|
|
20117
|
+
} catch (retryErr) {
|
|
20118
|
+
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
20119
|
+
this.logger?.error(`LSP initialize failed (attempt 2, giving up): ${retryMsg}`, {
|
|
20120
|
+
command: config.command,
|
|
20121
|
+
projectRoot
|
|
20122
|
+
});
|
|
20123
|
+
throw retryErr;
|
|
20124
|
+
}
|
|
19982
20125
|
}
|
|
19983
20126
|
this.initialized = true;
|
|
19984
20127
|
}
|
|
@@ -20016,8 +20159,11 @@ class LspClient {
|
|
|
20016
20159
|
});
|
|
20017
20160
|
this.process = proc;
|
|
20018
20161
|
setTimeout(() => {
|
|
20019
|
-
if (!this.initialized && this.process)
|
|
20020
|
-
|
|
20162
|
+
if (!this.initialized && this.process) {
|
|
20163
|
+
const msg = "LSP server start timeout";
|
|
20164
|
+
this.logger?.error(msg, { command: config.command, projectRoot, timeout: config.timeout ?? 1e4 });
|
|
20165
|
+
reject(new Error(msg));
|
|
20166
|
+
}
|
|
20021
20167
|
}, config.timeout ?? 1e4);
|
|
20022
20168
|
});
|
|
20023
20169
|
}
|
|
@@ -20056,7 +20202,9 @@ class LspClient {
|
|
|
20056
20202
|
try {
|
|
20057
20203
|
const msg = JSON.parse(body);
|
|
20058
20204
|
this.handleMessage(msg);
|
|
20059
|
-
} catch {
|
|
20205
|
+
} catch (e) {
|
|
20206
|
+
this.logger?.debug(`LSP JSON parse error: ${e instanceof Error ? e.message : String(e)}`);
|
|
20207
|
+
}
|
|
20060
20208
|
}
|
|
20061
20209
|
}
|
|
20062
20210
|
handleMessage(msg) {
|
|
@@ -20094,7 +20242,9 @@ class LspClient {
|
|
|
20094
20242
|
setTimeout(() => {
|
|
20095
20243
|
if (this.pending.has(id)) {
|
|
20096
20244
|
this.pending.delete(id);
|
|
20097
|
-
|
|
20245
|
+
const msg = `LSP request timeout: ${method}`;
|
|
20246
|
+
this.logger?.warn(msg, { method, timeout });
|
|
20247
|
+
reject(new Error(msg));
|
|
20098
20248
|
}
|
|
20099
20249
|
}, timeout);
|
|
20100
20250
|
});
|
|
@@ -20104,27 +20254,37 @@ class LspClient {
|
|
|
20104
20254
|
this.write(message);
|
|
20105
20255
|
}
|
|
20106
20256
|
write(message) {
|
|
20107
|
-
if (!this.process?.stdin?.writable)
|
|
20257
|
+
if (!this.process?.stdin?.writable) {
|
|
20258
|
+
this.logger?.debug("LSP write failed: process stdin not writable");
|
|
20108
20259
|
return;
|
|
20260
|
+
}
|
|
20109
20261
|
const header = `Content-Length: ${Buffer.byteLength(message)}\r
|
|
20110
20262
|
\r
|
|
20111
20263
|
`;
|
|
20112
20264
|
try {
|
|
20113
20265
|
this.process.stdin.write(header + message);
|
|
20114
|
-
} catch {
|
|
20266
|
+
} catch (e) {
|
|
20267
|
+
this.logger?.debug(`LSP write error: ${e instanceof Error ? e.message : String(e)}`);
|
|
20268
|
+
}
|
|
20115
20269
|
}
|
|
20116
20270
|
async shutdown() {
|
|
20117
20271
|
try {
|
|
20118
20272
|
if (this.process && this.initialized && this.process.stdin?.writable) {
|
|
20119
|
-
this.sendRequest("shutdown", null, 3000).catch(() => {
|
|
20273
|
+
this.sendRequest("shutdown", null, 3000).catch((e) => {
|
|
20274
|
+
this.logger?.debug(`LSP shutdown request failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
20275
|
+
});
|
|
20120
20276
|
this.sendNotification("exit", null);
|
|
20121
20277
|
await new Promise((r) => setTimeout(r, 200));
|
|
20122
20278
|
}
|
|
20123
|
-
} catch
|
|
20279
|
+
} catch (e) {
|
|
20280
|
+
this.logger?.debug(`LSP shutdown error: ${e instanceof Error ? e.message : String(e)}`);
|
|
20281
|
+
} finally {
|
|
20124
20282
|
if (this.process) {
|
|
20125
20283
|
try {
|
|
20126
20284
|
killTree(this.process);
|
|
20127
|
-
} catch {
|
|
20285
|
+
} catch (e) {
|
|
20286
|
+
this.logger?.debug(`LSP killTree error: ${e instanceof Error ? e.message : String(e)}`);
|
|
20287
|
+
}
|
|
20128
20288
|
this.process = null;
|
|
20129
20289
|
}
|
|
20130
20290
|
this.initialized = false;
|
|
@@ -20232,9 +20392,26 @@ class LspModule {
|
|
|
20232
20392
|
client;
|
|
20233
20393
|
failuresByServer = new Map;
|
|
20234
20394
|
disabledServers = new Set;
|
|
20235
|
-
|
|
20395
|
+
logger = null;
|
|
20396
|
+
constructor(config, client, logger) {
|
|
20236
20397
|
this.config = { ...DEFAULT_LSP_CONFIG, ...config };
|
|
20237
20398
|
this.client = client ?? new LspClient;
|
|
20399
|
+
this.logger = logger ?? null;
|
|
20400
|
+
this.client.setLogger(this.logger);
|
|
20401
|
+
}
|
|
20402
|
+
setLogger(logger) {
|
|
20403
|
+
this.logger = logger;
|
|
20404
|
+
this.client.setLogger(logger);
|
|
20405
|
+
}
|
|
20406
|
+
resetDisabledServers() {
|
|
20407
|
+
this.failuresByServer.clear();
|
|
20408
|
+
this.disabledServers.clear();
|
|
20409
|
+
}
|
|
20410
|
+
getDisabledServers() {
|
|
20411
|
+
return Array.from(this.disabledServers);
|
|
20412
|
+
}
|
|
20413
|
+
getFailureCounts() {
|
|
20414
|
+
return new Map(this.failuresByServer);
|
|
20238
20415
|
}
|
|
20239
20416
|
isLspDisabled() {
|
|
20240
20417
|
return this.disabledServers.size > 0;
|
|
@@ -20505,7 +20682,9 @@ async function runCheck(config, baseDir, deps) {
|
|
|
20505
20682
|
lines.push(formatStartupError(file, baseDir, d));
|
|
20506
20683
|
}
|
|
20507
20684
|
}
|
|
20508
|
-
} catch {
|
|
20685
|
+
} catch (e) {
|
|
20686
|
+
deps.logger?.debug(`Startup LSP check failed for ${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
20687
|
+
}
|
|
20509
20688
|
}
|
|
20510
20689
|
if (checked === 0 || lines.length === 0)
|
|
20511
20690
|
return null;
|
|
@@ -21596,7 +21775,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
21596
21775
|
pluginManager.register(browserPlugin);
|
|
21597
21776
|
}
|
|
21598
21777
|
}
|
|
21599
|
-
const lspModule = new LspModule(config.lsp);
|
|
21778
|
+
const lspModule = new LspModule(config.lsp, undefined, logger);
|
|
21600
21779
|
moduleRegistry.register(lspModule);
|
|
21601
21780
|
const lspPlugin = lspModule.getPlugin();
|
|
21602
21781
|
if (lspPlugin) {
|
|
@@ -30620,6 +30799,70 @@ Excluded blocks: ${info.excluded.length}`));
|
|
|
30620
30799
|
}
|
|
30621
30800
|
}
|
|
30622
30801
|
});
|
|
30802
|
+
ctx.registerCommand({
|
|
30803
|
+
name: "lsp",
|
|
30804
|
+
description: t("repl.lsp"),
|
|
30805
|
+
usage: t("repl.lsp_usage"),
|
|
30806
|
+
action: async (args) => {
|
|
30807
|
+
const subcommand = args[0] || "status";
|
|
30808
|
+
const lspModule = ctx.agent.deps?.moduleRegistry?.getModules?.()?.find((m) => m.name === "lsp");
|
|
30809
|
+
if (!lspModule) {
|
|
30810
|
+
console.log(pc2.yellow(t("repl.lsp_not_available")));
|
|
30811
|
+
return;
|
|
30812
|
+
}
|
|
30813
|
+
switch (subcommand) {
|
|
30814
|
+
case "status": {
|
|
30815
|
+
const disabled = lspModule.getDisabledServers();
|
|
30816
|
+
const failures = lspModule.getFailureCounts();
|
|
30817
|
+
const config = ctx.config.lsp;
|
|
30818
|
+
console.log(pc2.bold(t("repl.lsp_status_header")));
|
|
30819
|
+
console.log(`${t("repl.lsp_enabled")} ${config?.enabled ? pc2.green("yes") : pc2.red("no")}`);
|
|
30820
|
+
if (disabled.length > 0) {
|
|
30821
|
+
console.log(pc2.yellow(`${t("repl.lsp_disabled_servers")} ${disabled.join(", ")}`));
|
|
30822
|
+
}
|
|
30823
|
+
if (failures.size > 0) {
|
|
30824
|
+
console.log(pc2.dim(t("repl.lsp_failure_counts")));
|
|
30825
|
+
for (const [server, count] of failures) {
|
|
30826
|
+
console.log(pc2.dim(` ${server}: ${count}`));
|
|
30827
|
+
}
|
|
30828
|
+
}
|
|
30829
|
+
if (disabled.length === 0 && failures.size === 0) {
|
|
30830
|
+
console.log(pc2.green(t("repl.lsp_all_ok")));
|
|
30831
|
+
}
|
|
30832
|
+
break;
|
|
30833
|
+
}
|
|
30834
|
+
case "restart": {
|
|
30835
|
+
console.log(pc2.yellow(t("repl.lsp_restarting")));
|
|
30836
|
+
lspModule.resetDisabledServers();
|
|
30837
|
+
console.log(pc2.green(t("repl.lsp_restarted")));
|
|
30838
|
+
break;
|
|
30839
|
+
}
|
|
30840
|
+
case "check": {
|
|
30841
|
+
const path = args[1];
|
|
30842
|
+
if (!path) {
|
|
30843
|
+
console.log(pc2.yellow(t("repl.lsp_check_usage")));
|
|
30844
|
+
return;
|
|
30845
|
+
}
|
|
30846
|
+
console.log(pc2.dim(t("repl.lsp_checking", { path })));
|
|
30847
|
+
try {
|
|
30848
|
+
const result = await ctx.agent.deps?.toolExecutor?.execute("lsp_check", { path }, ctx.agent.deps?.toolCtx);
|
|
30849
|
+
if (result?.output) {
|
|
30850
|
+
console.log(result.output);
|
|
30851
|
+
}
|
|
30852
|
+
} catch (e) {
|
|
30853
|
+
console.log(pc2.red(t("repl.lsp_check_error", {
|
|
30854
|
+
error: e instanceof Error ? e.message : String(e)
|
|
30855
|
+
})));
|
|
30856
|
+
}
|
|
30857
|
+
break;
|
|
30858
|
+
}
|
|
30859
|
+
default: {
|
|
30860
|
+
console.log(pc2.dim(t("repl.lsp_usage")));
|
|
30861
|
+
break;
|
|
30862
|
+
}
|
|
30863
|
+
}
|
|
30864
|
+
}
|
|
30865
|
+
});
|
|
30623
30866
|
}
|
|
30624
30867
|
function registerSessionCommands(ctx) {
|
|
30625
30868
|
if (!ctx.sessionManager)
|
|
@@ -30874,6 +31117,7 @@ var init_repl_commands = __esm(() => {
|
|
|
30874
31117
|
reload: "agent",
|
|
30875
31118
|
wizard: "agent",
|
|
30876
31119
|
sysprompt: "agent",
|
|
31120
|
+
lsp: "agent",
|
|
30877
31121
|
sessions: "session",
|
|
30878
31122
|
new: "session",
|
|
30879
31123
|
resume: "session",
|
|
@@ -33113,16 +33357,24 @@ async function probeLspServers(config, baseDir, deps = {}) {
|
|
|
33113
33357
|
await probeClient.probe(server, projectRoot, serverTimeout);
|
|
33114
33358
|
ok.push({ language, ok: true, durationMs: Date.now() - started });
|
|
33115
33359
|
} catch (e) {
|
|
33360
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
33361
|
+
deps.logger?.warn(`LSP probe failed for ${language}: ${errorMsg}`, {
|
|
33362
|
+
command: server.command,
|
|
33363
|
+
timeout: serverTimeout,
|
|
33364
|
+
durationMs: Date.now() - started
|
|
33365
|
+
});
|
|
33116
33366
|
failed.push({
|
|
33117
33367
|
language,
|
|
33118
33368
|
ok: false,
|
|
33119
|
-
error:
|
|
33369
|
+
error: errorMsg,
|
|
33120
33370
|
durationMs: Date.now() - started
|
|
33121
33371
|
});
|
|
33122
33372
|
}
|
|
33123
33373
|
};
|
|
33124
33374
|
for (let i = 0;i < servers.length; i += maxParallel) {
|
|
33125
33375
|
if (Date.now() >= deadline) {
|
|
33376
|
+
const remaining = servers.slice(i).map((s) => s.language);
|
|
33377
|
+
deps.logger?.warn(`LSP probe deadline reached, skipping: ${remaining.join(", ")}`);
|
|
33126
33378
|
for (const { language } of servers.slice(i)) {
|
|
33127
33379
|
failed.push({ language, ok: false, error: "timeout", durationMs: 0 });
|
|
33128
33380
|
}
|
|
@@ -33421,6 +33673,7 @@ class Repl {
|
|
|
33421
33673
|
this.running = false;
|
|
33422
33674
|
this.saveHistory();
|
|
33423
33675
|
this.agent.shutdown();
|
|
33676
|
+
this.logger?.closeSessionLog();
|
|
33424
33677
|
if (this.exitOnClose)
|
|
33425
33678
|
process.exit(0);
|
|
33426
33679
|
});
|
|
@@ -33429,6 +33682,7 @@ class Repl {
|
|
|
33429
33682
|
this.running = false;
|
|
33430
33683
|
this.saveHistory();
|
|
33431
33684
|
this.agent.shutdown();
|
|
33685
|
+
this.logger?.closeSessionLog();
|
|
33432
33686
|
process.exit(0);
|
|
33433
33687
|
});
|
|
33434
33688
|
}
|
|
@@ -33775,6 +34029,7 @@ ${t("image.clipboard_empty")}`));
|
|
|
33775
34029
|
this.running = false;
|
|
33776
34030
|
this.saveHistory();
|
|
33777
34031
|
this.agent.shutdown();
|
|
34032
|
+
this.logger?.closeSessionLog();
|
|
33778
34033
|
this.rl.close();
|
|
33779
34034
|
}
|
|
33780
34035
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { t } from "../i18n/index";
|
|
4
|
+
export class BackupManager {
|
|
5
|
+
configDir;
|
|
6
|
+
constructor(configDir) {
|
|
7
|
+
this.configDir = configDir;
|
|
8
|
+
}
|
|
9
|
+
backupConfig() {
|
|
10
|
+
const configPath = join(this.configDir, "config.json");
|
|
11
|
+
if (!existsSync(configPath))
|
|
12
|
+
return;
|
|
13
|
+
const bakPath = join(this.configDir, "config.json.bak");
|
|
14
|
+
copyFileSync(configPath, bakPath);
|
|
15
|
+
}
|
|
16
|
+
backupAll() {
|
|
17
|
+
const bakDir = this.configDir + ".bak";
|
|
18
|
+
if (existsSync(bakDir))
|
|
19
|
+
return;
|
|
20
|
+
if (existsSync(this.configDir)) {
|
|
21
|
+
cpSync(this.configDir, bakDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
writeFreshConfig(config) {
|
|
25
|
+
if (!existsSync(this.configDir)) {
|
|
26
|
+
mkdirSync(this.configDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
writeFileSync(join(this.configDir, "config.json"), JSON.stringify(config, null, 2), "utf-8");
|
|
29
|
+
}
|
|
30
|
+
getBackupSummary() {
|
|
31
|
+
const lines = [];
|
|
32
|
+
const bakConfig = join(this.configDir, "config.json.bak");
|
|
33
|
+
const bakDir = this.configDir + ".bak";
|
|
34
|
+
if (existsSync(bakConfig)) {
|
|
35
|
+
lines.push(t("migration.config_bak"));
|
|
36
|
+
}
|
|
37
|
+
if (existsSync(bakDir)) {
|
|
38
|
+
const files = readdirSync(bakDir);
|
|
39
|
+
lines.push(t("migration.dir_bak", { count: files.length }));
|
|
40
|
+
}
|
|
41
|
+
return lines.length > 0
|
|
42
|
+
? `${t("migration.migrated")}\n${lines.join("\n")}`
|
|
43
|
+
: t("migration.not_needed");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
const V1_ONLY_FIELDS = ["apiBase", "apiKey", "chatEndpoint", "temperature", "maxTokens"];
|
|
4
|
+
export class MigrationDetector {
|
|
5
|
+
configDir;
|
|
6
|
+
constructor(configDir) {
|
|
7
|
+
this.configDir = configDir;
|
|
8
|
+
}
|
|
9
|
+
needsMigration() {
|
|
10
|
+
const configPath = join(this.configDir, "config.json");
|
|
11
|
+
if (!existsSync(configPath))
|
|
12
|
+
return false;
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(configPath, "utf-8");
|
|
15
|
+
const config = JSON.parse(content);
|
|
16
|
+
if (config.version && config.version.startsWith("2."))
|
|
17
|
+
return false;
|
|
18
|
+
for (const field of V1_ONLY_FIELDS) {
|
|
19
|
+
if (field in config)
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const v2Required = ["provider", "contextWindow", "contextBudget"];
|
|
23
|
+
const hasAllV2Fields = v2Required.every((f) => f in config);
|
|
24
|
+
if (!hasAllV2Fields)
|
|
25
|
+
return true;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
hasOldSessions() {
|
|
33
|
+
const sessionsDir = join(this.configDir, "sessions");
|
|
34
|
+
if (!existsSync(sessionsDir))
|
|
35
|
+
return false;
|
|
36
|
+
return readdirSync(sessionsDir).length > 0;
|
|
37
|
+
}
|
|
38
|
+
hasOldProfile() {
|
|
39
|
+
const profilePath = join(this.configDir, "profile.json");
|
|
40
|
+
if (!existsSync(profilePath))
|
|
41
|
+
return false;
|
|
42
|
+
try {
|
|
43
|
+
const profile = JSON.parse(readFileSync(profilePath, "utf-8"));
|
|
44
|
+
return !profile.platform && !profile.os;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|