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.
Files changed (218) hide show
  1. package/README.md +312 -312
  2. package/dist/cli/commands.js +323 -0
  3. package/dist/cli/completer.js +167 -0
  4. package/dist/cli/index.js +2 -0
  5. package/dist/cli/main.js +165 -0
  6. package/dist/cli/plugin-commands.js +36 -0
  7. package/dist/cli/repl-commands.js +661 -0
  8. package/dist/cli/repl.js +616 -0
  9. package/dist/cli/run-result.js +22 -0
  10. package/dist/cli/security-commands.js +164 -0
  11. package/dist/cli/setup.js +231 -0
  12. package/dist/config/config.js +249 -0
  13. package/dist/config/defaults.js +124 -0
  14. package/dist/config/experts.js +15 -0
  15. package/dist/config/index.js +3 -0
  16. package/dist/config/security.js +193 -0
  17. package/dist/config/types.js +1 -0
  18. package/dist/core/agent-moe.js +102 -0
  19. package/dist/core/agent.js +886 -0
  20. package/dist/core/bootstrap.js +404 -0
  21. package/dist/core/index.js +2 -0
  22. package/dist/core/prompt-builder.js +76 -0
  23. package/dist/core/session-logger.js +197 -0
  24. package/dist/core/types.js +1 -0
  25. package/dist/core/version.js +24 -0
  26. package/dist/core/workspace.js +76 -0
  27. package/dist/i18n/en.json +598 -0
  28. package/dist/i18n/index.js +46 -0
  29. package/dist/i18n/ru.json +598 -0
  30. package/dist/index.js +22 -0
  31. package/dist/llm/image-utils.js +143 -0
  32. package/dist/llm/index.js +4 -0
  33. package/dist/llm/model-loader.js +78 -0
  34. package/dist/llm/openai-compat.js +359 -0
  35. package/dist/llm/orchestrator.js +198 -0
  36. package/dist/llm/provider.js +10 -0
  37. package/dist/llm/response.js +39 -0
  38. package/dist/llm/token-counter.js +39 -0
  39. package/dist/llm/types.js +1 -0
  40. package/dist/logger/app-logger.js +143 -0
  41. package/dist/logger/file-log.js +151 -0
  42. package/dist/logger/index.js +1 -0
  43. package/dist/main.js +284 -29
  44. package/dist/migration/backup.js +45 -0
  45. package/dist/migration/detect.js +50 -0
  46. package/dist/migration/index.js +2 -0
  47. package/dist/modules/artifacts/store.js +61 -0
  48. package/dist/modules/browser/actions.js +76 -0
  49. package/dist/modules/browser/bridge-client.js +199 -0
  50. package/dist/modules/browser/bridge-path.js +10 -0
  51. package/dist/modules/browser/bridge-server.mjs +202 -202
  52. package/dist/modules/browser/cookie-store.js +24 -0
  53. package/dist/modules/browser/driver.js +136 -0
  54. package/dist/modules/browser/index.js +7 -0
  55. package/dist/modules/browser/module.js +29 -0
  56. package/dist/modules/browser/session.js +338 -0
  57. package/dist/modules/browser/snapshot.js +148 -0
  58. package/dist/modules/browser/types.js +12 -0
  59. package/dist/modules/certification/cli.js +174 -0
  60. package/dist/modules/certification/fact-checker.js +82 -0
  61. package/dist/modules/certification/loader.js +105 -0
  62. package/dist/modules/certification/manifest.js +50 -0
  63. package/dist/modules/certification/runner.js +159 -0
  64. package/dist/modules/certification/scenarios.js +124 -0
  65. package/dist/modules/certification/types.js +1 -0
  66. package/dist/modules/context/chunk-query.js +100 -0
  67. package/dist/modules/context/fact-extractor.js +162 -0
  68. package/dist/modules/context/history.js +15 -0
  69. package/dist/modules/context/index.js +1 -0
  70. package/dist/modules/context/manager.js +423 -0
  71. package/dist/modules/execution/audit-runners.js +152 -0
  72. package/dist/modules/execution/auditor.js +218 -0
  73. package/dist/modules/execution/execution-plugin.js +272 -0
  74. package/dist/modules/execution/index.js +8 -0
  75. package/dist/modules/execution/module.js +436 -0
  76. package/dist/modules/execution/moe-executor.js +291 -0
  77. package/dist/modules/execution/plan-coverage.js +68 -0
  78. package/dist/modules/execution/plan-persister.js +46 -0
  79. package/dist/modules/execution/plan-store.js +157 -0
  80. package/dist/modules/execution/plan-tool.js +508 -0
  81. package/dist/modules/execution/plan-validator.js +153 -0
  82. package/dist/modules/execution/planner.js +90 -0
  83. package/dist/modules/execution/stuck-detector.js +510 -0
  84. package/dist/modules/execution/tracker.js +67 -0
  85. package/dist/modules/execution/types.js +1 -0
  86. package/dist/modules/execution/verifier.js +222 -0
  87. package/dist/modules/execution/windows-commands.js +41 -0
  88. package/dist/modules/hallucination/confidence.js +66 -0
  89. package/dist/modules/hallucination/consistency.js +26 -0
  90. package/dist/modules/hallucination/detector.js +43 -0
  91. package/dist/modules/hallucination/factual.js +129 -0
  92. package/dist/modules/hallucination/index.js +5 -0
  93. package/dist/modules/hallucination/js-identifiers.js +262 -0
  94. package/dist/modules/hallucination/llm-judge.js +101 -0
  95. package/dist/modules/index.js +5 -0
  96. package/dist/modules/indexer/cache.js +40 -0
  97. package/dist/modules/indexer/index.js +3 -0
  98. package/dist/modules/indexer/module.js +245 -0
  99. package/dist/modules/indexer/project-profile.js +183 -0
  100. package/dist/modules/indexer/walker.js +101 -0
  101. package/dist/modules/lsp/check-tool.js +58 -0
  102. package/dist/modules/lsp/client.js +278 -0
  103. package/dist/modules/lsp/command.js +60 -0
  104. package/dist/modules/lsp/config.js +135 -0
  105. package/dist/modules/lsp/index.js +3 -0
  106. package/dist/modules/lsp/module.js +232 -0
  107. package/dist/modules/lsp/probe.js +76 -0
  108. package/dist/modules/lsp/project-root.js +32 -0
  109. package/dist/modules/lsp/startup-check.js +141 -0
  110. package/dist/modules/lsp/types.js +1 -0
  111. package/dist/modules/mcp/client.js +399 -0
  112. package/dist/modules/mcp/index.js +3 -0
  113. package/dist/modules/mcp/module.js +142 -0
  114. package/dist/modules/mcp/registry.js +15 -0
  115. package/dist/modules/memory/index.js +1 -0
  116. package/dist/modules/memory/module.js +96 -0
  117. package/dist/modules/memory/search.js +42 -0
  118. package/dist/modules/memory/store.js +69 -0
  119. package/dist/modules/pipelines/engine.js +60 -0
  120. package/dist/modules/pipelines/index.js +3 -0
  121. package/dist/modules/pipelines/parser.js +56 -0
  122. package/dist/modules/pipelines/template.js +14 -0
  123. package/dist/modules/plugins/builtin/lint-on-write.js +231 -0
  124. package/dist/modules/plugins/builtin/notify.js +9 -0
  125. package/dist/modules/plugins/index.js +1 -0
  126. package/dist/modules/plugins/loader.js +70 -0
  127. package/dist/modules/plugins/manager.js +217 -0
  128. package/dist/modules/plugins/types.js +1 -0
  129. package/dist/modules/processes/detect.js +34 -0
  130. package/dist/modules/processes/index.js +2 -0
  131. package/dist/modules/processes/registry.js +327 -0
  132. package/dist/modules/processes/runner.js +23 -0
  133. package/dist/modules/registry.js +47 -0
  134. package/dist/modules/security/audit-log.js +136 -0
  135. package/dist/modules/security/audit-notifier.js +292 -0
  136. package/dist/modules/security/command-validator.js +205 -0
  137. package/dist/modules/security/content-scanner.js +53 -0
  138. package/dist/modules/security/data-sanitizer.js +89 -0
  139. package/dist/modules/security/encryption.js +242 -0
  140. package/dist/modules/security/index.js +14 -0
  141. package/dist/modules/security/network-validator.js +71 -0
  142. package/dist/modules/security/path-validator.js +207 -0
  143. package/dist/modules/security/rate-limiter.js +119 -0
  144. package/dist/modules/security/security-policies.js +531 -0
  145. package/dist/modules/security/session-encryption.js +210 -0
  146. package/dist/modules/security/session-isolation.js +95 -0
  147. package/dist/modules/session/index.js +3 -0
  148. package/dist/modules/session/manager.js +172 -0
  149. package/dist/modules/session/module.js +24 -0
  150. package/dist/modules/session/store.js +222 -0
  151. package/dist/modules/session/types.js +1 -0
  152. package/dist/modules/skills/index.js +2 -0
  153. package/dist/modules/skills/loader.js +72 -0
  154. package/dist/modules/skills/matcher.js +27 -0
  155. package/dist/modules/skills/module.js +129 -0
  156. package/dist/modules/types.js +1 -0
  157. package/dist/modules/updater/checker.js +96 -0
  158. package/dist/modules/updater/index.js +2 -0
  159. package/dist/modules/updater/module.js +116 -0
  160. package/dist/modules/user-profile/compressor.js +16 -0
  161. package/dist/modules/user-profile/index.js +1 -0
  162. package/dist/modules/user-profile/profile.js +68 -0
  163. package/dist/skills/builtin/git.md +36 -36
  164. package/dist/skills/builtin/typescript.md +35 -35
  165. package/dist/tools/approve.js +32 -0
  166. package/dist/tools/attach-image.js +89 -0
  167. package/dist/tools/bash.js +496 -0
  168. package/dist/tools/browser.js +114 -0
  169. package/dist/tools/chunk-query.js +99 -0
  170. package/dist/tools/create-dir.js +55 -0
  171. package/dist/tools/delete-file.js +62 -0
  172. package/dist/tools/download-file.js +116 -0
  173. package/dist/tools/edit-file.js +79 -0
  174. package/dist/tools/enable-tools.js +58 -0
  175. package/dist/tools/executor.js +144 -0
  176. package/dist/tools/file-info.js +46 -0
  177. package/dist/tools/filter-tools.js +17 -0
  178. package/dist/tools/glob-tool.js +26 -0
  179. package/dist/tools/grep-tool.js +84 -0
  180. package/dist/tools/hidden-tools-block.js +37 -0
  181. package/dist/tools/index.js +78 -0
  182. package/dist/tools/list-dir.js +48 -0
  183. package/dist/tools/load-skill.js +42 -0
  184. package/dist/tools/mcp-call.js +68 -0
  185. package/dist/tools/move-file.js +85 -0
  186. package/dist/tools/path-utils.js +51 -0
  187. package/dist/tools/pipeline-run.js +144 -0
  188. package/dist/tools/preview.js +2 -0
  189. package/dist/tools/process-kill.js +29 -0
  190. package/dist/tools/process-list.js +36 -0
  191. package/dist/tools/process-log.js +45 -0
  192. package/dist/tools/question.js +140 -0
  193. package/dist/tools/read-file.js +91 -0
  194. package/dist/tools/recall.js +117 -0
  195. package/dist/tools/registry.js +47 -0
  196. package/dist/tools/remember.js +67 -0
  197. package/dist/tools/scope-check.js +30 -0
  198. package/dist/tools/search-history.js +84 -0
  199. package/dist/tools/subagent.js +196 -0
  200. package/dist/tools/types.js +1 -0
  201. package/dist/tools/user-input.js +123 -0
  202. package/dist/tools/web-browse.js +86 -0
  203. package/dist/tools/web-fetch.js +98 -0
  204. package/dist/tools/web-search.js +78 -0
  205. package/dist/tools/write-file.js +81 -0
  206. package/dist/ui/box.js +77 -0
  207. package/dist/ui/colors.js +4 -0
  208. package/dist/ui/diff.js +178 -0
  209. package/dist/ui/index.js +6 -0
  210. package/dist/ui/line-editor.js +703 -0
  211. package/dist/ui/line-math.js +69 -0
  212. package/dist/ui/md-formatter.js +212 -0
  213. package/dist/ui/output.js +13 -0
  214. package/dist/ui/plan-view.js +103 -0
  215. package/dist/ui/renderer.js +209 -0
  216. package/dist/ui/spinner.js +70 -0
  217. package/dist/ui/table.js +144 -0
  218. 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
- const totalTimeoutMs = 120000;
5222
- const timeoutId = setTimeout(() => controller.abort(), totalTimeoutMs);
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
- const response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
5236
- method: "POST",
5237
- headers,
5238
- body: JSON.stringify(body),
5239
- signal: abortSignal
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
- throw new Error(t("error.llm_api", {
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 reader.read();
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
- await this.sendRequest("initialize", initParams, timeout);
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
- reject(new Error("LSP server start timeout"));
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
- reject(new Error(`LSP request timeout: ${method}`));
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 {} finally {
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
- constructor(config, client) {
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: e instanceof Error ? e.message : String(e),
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
+ }