micro-models-agent 0.49.1 → 0.50.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 (2) hide show
  1. package/dist/main.js +916 -116
  2. package/package.json +2 -2
package/dist/main.js CHANGED
@@ -1991,7 +1991,6 @@ var init_security = __esm(() => {
1991
1991
  "python",
1992
1992
  "python3",
1993
1993
  "node",
1994
- "bun",
1995
1994
  "perl",
1996
1995
  "ruby",
1997
1996
  "del",
@@ -2013,8 +2012,6 @@ var init_security = __esm(() => {
2013
2012
  "--delete",
2014
2013
  "--remove",
2015
2014
  "--dangerous",
2016
- "--yes",
2017
- "-y",
2018
2015
  "--no-confirm"
2019
2016
  ],
2020
2017
  dangerousOperators: [">", ">>", "2>", "2>>", ";", "&&", "||", "|", "`", "$("]
@@ -2539,6 +2536,8 @@ Command: {command}`,
2539
2536
  {lines}`,
2540
2537
  "proc.none": "No background processes running.",
2541
2538
  "proc.not_found": "Process not found: {id}",
2539
+ "proc.missing_id": "Provide the process id: process_kill id=proc_xxx (the id is returned by bash when a command goes to the background, or by process_list).",
2540
+ "proc.missing_id_list": "No background processes are currently running.",
2542
2541
  "proc.killed": "Process {id} (PID {pid}) killed.",
2543
2542
  "proc.kill_failed": "Failed to kill process {id}",
2544
2543
  "proc.list_header": "Background processes",
@@ -2595,6 +2594,8 @@ Fix the error and re-edit the file (a clean write clears the failure), or mark t
2595
2594
  "plan.deleted": "Plan {id} deleted permanently.",
2596
2595
  "plan.purged": "Deleted {count} plan(s). Plan storage is empty.",
2597
2596
  "plan.no_deliverables": "note: step {step} names no files — its completion cannot be auto-verified; run an explicit check (e.g. build/tests) before your final answer.",
2597
+ "plan.bulk_update_rejected": 'Bulk steps[] rewrite via action=update is not supported: it would discard all step statuses. Use "re-plan" (keeps completed steps, replaces the rest) or mark steps individually: plan update step=N status=done|failed|skipped.',
2598
+ "plan.readonly": 'Plan {id} is {status} (read-only) — it cannot be updated. Use "plan switch" to activate it, or create a new plan for new work.',
2598
2599
  "plan.list_legend": "[*] active · [ ] draft · [-] archived",
2599
2600
  "exec.plan_nudge": "You made {count} file-changing tool call(s) without a plan. For tasks that create or modify files, or span multiple steps, create a plan first (plan create) with concrete steps (exact filenames, commands, deliverables), then continue.",
2600
2601
  "todo.added": "Added {count} todo(s): {items}",
@@ -2676,6 +2677,8 @@ Fix the error and re-edit the file (a clean write clears the failure), or mark t
2676
2677
  "cli.cert_fixture_missing": "Fixture missing for {id}: {path}",
2677
2678
  "cli.cert_marks_hint": "Markers: ✔ certified on this provider, ○ certified on an older MMA version, · not certified",
2678
2679
  "cli.manage_providers": "Manage providers",
2680
+ "cli.provider_check": "Probe configured providers (free listModels call)",
2681
+ "repl.provider_down": '[WARN] Provider "{name}" is unreachable: {error}',
2679
2682
  "cli.list_providers": "List providers",
2680
2683
  "cli.current_provider": "Current provider:",
2681
2684
  "cli.base_url": "Base URL:",
@@ -2829,14 +2832,14 @@ Available commands:`,
2829
2832
  "repl.wizard": "Run setup wizard",
2830
2833
  "repl.wizard_usage": "Usage: /wizard",
2831
2834
  "repl.wizard_running": "Running setup wizard... (current config will be overwritten)",
2832
- "repl.provider_list": "Show/set provider",
2835
+ "repl.provider_list": "List configured providers or hot-swap the active one",
2833
2836
  "repl.provider_current": "Current provider",
2834
2837
  "repl.provider_set": "Provider set to: {name}",
2835
- "repl.provider_usage": "Usage: /provider list | /provider use <type>",
2836
- "repl.model_list": "Show/set model",
2838
+ "repl.provider_usage": "/provider list - show entries (* = active, prio = fallback order) | /provider use <name> - switch without restart",
2839
+ "repl.model_list": "List models of the active provider or switch the model",
2837
2840
  "repl.model_current": "Current model",
2838
2841
  "repl.model_set": "Model set to: {name}",
2839
- "repl.model_usage": "Usage: /model list | /model use <name>",
2842
+ "repl.model_usage": "/model list - fetch models from the active provider | /model use <name> - switch model",
2840
2843
  "repl.group.general": "General",
2841
2844
  "repl.group.agent": "Agent",
2842
2845
  "repl.group.session": "Sessions",
@@ -2939,6 +2942,7 @@ Apply a matching solution from these results. If none is relevant — do NOT rep
2939
2942
  "exec.error_search_failed": 'Web search returned nothing for "{query}".',
2940
2943
  "exec.error_search_no_query": "Error output is not meaningful — skipping the web search.",
2941
2944
  "exec.npm_exec_hint": '"could not determine executable to run" — no "bin" for that package/script. Use "npm run <script>" (script must exist in package.json) or "bunx <pkg>" for a package that declares a bin.',
2945
+ "exec.hidden_tool_hint": '"{tool}" is not a shell command — it is an MMA tool that is currently hidden. Call the enable_tools tool with tags ["shell"] to unlock it; it becomes available on the next iteration.',
2942
2946
  "exec.task_reminder": "Task: {task}. Continue making progress — do not repeat failed actions.",
2943
2947
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2944
2948
  "hall.short_response": "Response too short or empty",
@@ -3047,7 +3051,12 @@ Apply a matching solution from these results. If none is relevant — do NOT rep
3047
3051
  "tools.hidden_header": "Additional tools (enable on demand via enable_tools or route to subagent tool_tags):",
3048
3052
  "tool.friendly.enable_tools": "Enable tools",
3049
3053
  "cli.provider_base_hint": "Base URL set to: {baseUrl}",
3054
+ "cli.add_provider": "Add a provider to the config",
3055
+ "cli.provider_no_url": 'No base URL for provider "{name}". Pass --url.',
3056
+ "cli.provider_added": "Provider added: {name}",
3057
+ "cli.provider_switch_hint": "Switch to it with: mma provider use <name>",
3050
3058
  "repl.cost": "Total cost: {cost}",
3059
+ "repl.cost_breakdown": "By provider: {breakdown}",
3051
3060
  "repl.tokens": "Tokens used: {tokens}",
3052
3061
  "repl.ctrl_c_interrupt": `
3053
3062
  [Ctrl+C] Stopping agent... (press again to force)`,
@@ -3241,6 +3250,8 @@ var init_ru = __esm(() => {
3241
3250
  {lines}`,
3242
3251
  "proc.none": "Фоновых процессов нет.",
3243
3252
  "proc.not_found": "Процесс не найден: {id}",
3253
+ "proc.missing_id": "Укажите id процесса: process_kill id=proc_xxx (id возвращается bash при уходе команды в фон или выводится через process_list).",
3254
+ "proc.missing_id_list": "Сейчас фоновых процессов нет.",
3244
3255
  "proc.killed": "Процесс {id} (PID {pid}) остановлен.",
3245
3256
  "proc.kill_failed": "Не удалось остановить процесс {id}",
3246
3257
  "proc.list_header": "Фоновые процессы",
@@ -3297,6 +3308,8 @@ var init_ru = __esm(() => {
3297
3308
  "plan.deleted": "План {id} удалён окончательно.",
3298
3309
  "plan.purged": "Удалено планов: {count}. Хранилище планов пусто.",
3299
3310
  "plan.no_deliverables": "примечание: шаг {step} не называет файлов — его выполнение нельзя проверить автоматически; перед финальным ответом явно проверь результат (например, запусти сборку/тесты).",
3311
+ "plan.bulk_update_rejected": 'Массовая перезапись steps[] через action=update не поддерживается: она бы стёрла статусы всех шагов. Используй "re-plan" (сохраняет завершённые шаги, заменяет остальные) или отмечай шаги по одному: plan update step=N status=done|failed|skipped.',
3312
+ "plan.readonly": 'План {id} имеет статус {status} (только чтение) — обновить его нельзя. Используй "plan switch", чтобы активировать его, или создай новый план для новой задачи.',
3300
3313
  "plan.list_legend": "[*] активный · [ ] черновик · [-] архив",
3301
3314
  "exec.plan_nudge": "Ты сделал(а) {count} вызов(ов), изменяющих файлы, без плана. Для задач, создающих/изменяющих файлы или состоящих из нескольких шагов, сначала создай план (plan create) с конкретными шагами (точные имена файлов, команды, результаты), а потом продолжай.",
3302
3315
  "todo.added": "Добавлено {count} задач: {items}",
@@ -3374,6 +3387,8 @@ var init_ru = __esm(() => {
3374
3387
  "cli.cert_fixture_missing": "Фикстура отсутствует для {id}: {path}",
3375
3388
  "cli.cert_marks_hint": "Маркеры: ✔ сертифицировано на этом провайдере, ○ сертифицировано на старой версии MMA, · не сертифицировано",
3376
3389
  "cli.manage_providers": "Управление провайдерами",
3390
+ "cli.provider_check": "Проверить доступность провайдеров (бесплатный вызов listModels)",
3391
+ "repl.provider_down": "[ВНИМАНИЕ] Провайдер «{name}» недоступен: {error}",
3377
3392
  "cli.list_providers": "Список провайдеров",
3378
3393
  "cli.current_provider": "Текущий провайдер:",
3379
3394
  "cli.base_url": "Базовый URL:",
@@ -3527,14 +3542,14 @@ var init_ru = __esm(() => {
3527
3542
  "repl.wizard": "Запустить мастер настройки",
3528
3543
  "repl.wizard_usage": "Использование: /wizard",
3529
3544
  "repl.wizard_running": "Запуск мастера настройки... (текущая конфигурация будет перезаписана)",
3530
- "repl.provider_list": "Показать/сменить провайдер",
3545
+ "repl.provider_list": "Список настроенных провайдеров или горячая смена активного",
3531
3546
  "repl.provider_current": "Текущий провайдер",
3532
3547
  "repl.provider_set": "Провайдер установлен: {name}",
3533
- "repl.provider_usage": "Использование: /provider list | /provider use <тип>",
3534
- "repl.model_list": "Показать/сменить модель",
3548
+ "repl.provider_usage": "/provider list — записи (* = активный, prio = порядок fallback) | /provider use <имя> — смена без перезапуска",
3549
+ "repl.model_list": "Список моделей активного провайдера или смена модели",
3535
3550
  "repl.model_current": "Текущая модель",
3536
3551
  "repl.model_set": "Модель установлена: {name}",
3537
- "repl.model_usage": "Использование: /model list | /model use <имя>",
3552
+ "repl.model_usage": "/model list — получить модели активного провайдера | /model use <имя> — сменить модель",
3538
3553
  "repl.reload": "Перезагрузить агента с текущим конфигом",
3539
3554
  "repl.reload_usage": "Использование: /reload",
3540
3555
  "repl.reloading": "Перезагрузка агента...",
@@ -3641,6 +3656,7 @@ var init_ru = __esm(() => {
3641
3656
  "exec.error_search_failed": 'Поиск в интернете для "{query}" ничего не дал.',
3642
3657
  "exec.error_search_no_query": "Текст ошибки незначимый — поиск в интернете пропущен.",
3643
3658
  "exec.npm_exec_hint": '"could not determine executable to run" — у пакета/скрипта нет "bin". Используй "npm run <script>" (скрипт должен быть в package.json) или "bunx <pkg>" для пакета с объявленным bin.',
3659
+ "exec.hidden_tool_hint": '"{tool}" — не команда оболочки, это инструмент MMA, который сейчас скрыт. Вызови тул enable_tools с tags ["shell"], чтобы включить его; он станет доступен на следующей итерации.',
3644
3660
  "exec.task_reminder": "Задача: {task}. Продолжай работу — не повторяй неудачные действия.",
3645
3661
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3646
3662
  "hall.short_response": "Слишком короткий или пустой ответ",
@@ -3752,7 +3768,12 @@ var init_ru = __esm(() => {
3752
3768
  "tools.hidden_header": "Дополнительные тулы (включите по требованию через enable_tools или маршрутизируйте через subagent tool_tags):",
3753
3769
  "tool.friendly.enable_tools": "Включить тулы",
3754
3770
  "cli.provider_base_hint": "Базовый URL установлен: {baseUrl}",
3771
+ "cli.add_provider": "Добавить провайдера в конфиг",
3772
+ "cli.provider_no_url": 'Нет базового URL для провайдера "{name}". Укажите --url.',
3773
+ "cli.provider_added": "Провайдер добавлен: {name}",
3774
+ "cli.provider_switch_hint": "Переключитесь на него: mma provider use <имя>",
3755
3775
  "repl.cost": "Итого потрачено: {cost}",
3776
+ "repl.cost_breakdown": "По провайдерам: {breakdown}",
3756
3777
  "repl.tokens": "Потрачено токенов: {tokens}",
3757
3778
  "repl.ctrl_c_interrupt": `
3758
3779
  [Ctrl+C] Остановка агента... (ещё раз — принудительно)`,
@@ -4186,22 +4207,6 @@ function normalizeLspServerArgs(config) {
4186
4207
  }
4187
4208
  }
4188
4209
  }
4189
- function normalizeStaleSecurityConfig(config) {
4190
- const sec = config.security;
4191
- if (!sec || sec.enabled !== false)
4192
- return;
4193
- const bash = sec.bash;
4194
- if (!bash || bash.enabled !== false || bash.blockDangerousFlags !== false)
4195
- return;
4196
- const LEGACY_OPERATORS = [">", ">>", "2>", "2>>", "`"];
4197
- if (JSON.stringify(bash.dangerousOperators ?? []) !== JSON.stringify(LEGACY_OPERATORS))
4198
- return;
4199
- const defaultCmds = new Set(DEFAULT_SECURITY_CONFIG.bash.blacklist);
4200
- const blacklist = Array.isArray(bash.blacklist) ? bash.blacklist : [];
4201
- if (blacklist.some((cmd) => !defaultCmds.has(cmd)))
4202
- return;
4203
- config.security = JSON.parse(JSON.stringify(DEFAULT_SECURITY_CONFIG));
4204
- }
4205
4210
  function loadJSON(path) {
4206
4211
  try {
4207
4212
  if (existsSync4(path)) {
@@ -4275,7 +4280,6 @@ function loadConfig(options) {
4275
4280
  config.security.contentScan.dangerousPatterns = restoreDangerousPatterns(config.security.contentScan.dangerousPatterns, DEFAULT_SECURITY_CONFIG.contentScan.dangerousPatterns);
4276
4281
  }
4277
4282
  normalizeLspServerArgs(config);
4278
- normalizeStaleSecurityConfig(config);
4279
4283
  config = applyEnvVars(config);
4280
4284
  try {
4281
4285
  const encryptor = new ConfigEncryptor;
@@ -4757,6 +4761,26 @@ var init_app_logger = __esm(() => {
4757
4761
  };
4758
4762
  });
4759
4763
 
4764
+ // src/modules/providers/registry.ts
4765
+ class ProviderRegistry {
4766
+ specs = new Map;
4767
+ register(spec) {
4768
+ this.specs.set(spec.type, spec);
4769
+ }
4770
+ get(type) {
4771
+ return this.specs.get(type);
4772
+ }
4773
+ has(type) {
4774
+ return this.specs.has(type);
4775
+ }
4776
+ list() {
4777
+ return [...this.specs.values()];
4778
+ }
4779
+ types() {
4780
+ return [...this.specs.keys()];
4781
+ }
4782
+ }
4783
+
4760
4784
  // node_modules/base64-js/index.js
4761
4785
  var require_base64_js = __commonJS((exports) => {
4762
4786
  exports.byteLength = byteLength;
@@ -5646,13 +5670,13 @@ class OpenAICompatProvider {
5646
5670
  headers
5647
5671
  });
5648
5672
  if (!response.ok) {
5649
- return [];
5673
+ throw new Error(`HTTP ${response.status} from ${url}`);
5650
5674
  }
5651
5675
  const data = await response.json();
5652
5676
  const models = (data.data || data || []).map((m) => m.id || m.name || m.model || "").filter(Boolean);
5653
5677
  return models;
5654
- } catch {
5655
- return [];
5678
+ } catch (err) {
5679
+ throw err instanceof Error ? err : new Error(String(err));
5656
5680
  }
5657
5681
  }
5658
5682
  async fetchWithRetry(url, init) {
@@ -5681,12 +5705,16 @@ class OpenAICompatProvider {
5681
5705
  }
5682
5706
  }
5683
5707
  if (lastStatus === 429) {
5684
- throw new Error(t("error.llm_429", {
5708
+ const e429 = new Error(t("error.llm_429", {
5685
5709
  model: this.model,
5686
5710
  baseUrl: this.config.baseUrl
5687
5711
  }));
5712
+ e429.llmStatus = lastStatus;
5713
+ throw e429;
5688
5714
  }
5689
- throw new Error(t("error.llm_retries"));
5715
+ const eRetry = new Error(t("error.llm_retries"));
5716
+ eRetry.llmStatus = lastStatus;
5717
+ throw eRetry;
5690
5718
  }
5691
5719
  isRetryable(status) {
5692
5720
  return status === 429 || status >= 500;
@@ -5717,6 +5745,399 @@ var init_openai_compat = __esm(() => {
5717
5745
  init_rate_limiter();
5718
5746
  });
5719
5747
 
5748
+ // src/modules/providers/presets.ts
5749
+ function openaiCompat(opts) {
5750
+ const retry = opts.retry ?? {
5751
+ maxRetries: 3,
5752
+ baseDelay: 1000,
5753
+ maxDelay: 30000
5754
+ };
5755
+ const rateLimits = opts.rateLimits;
5756
+ return new OpenAICompatProvider({
5757
+ model: opts.model,
5758
+ baseUrl: opts.baseUrl,
5759
+ apiKey: opts.apiKey,
5760
+ contextWindow: opts.contextWindow,
5761
+ retry,
5762
+ rateLimits
5763
+ });
5764
+ }
5765
+ var OPENAI_COMPAT, OPENROUTER, OPENAI, ANTHROPIC, OPENCODE_ZEN, OPENCODE_GO, BUILTIN_PROVIDERS, HOSTED_BASE_URLS;
5766
+ var init_presets = __esm(() => {
5767
+ init_openai_compat();
5768
+ OPENAI_COMPAT = {
5769
+ type: "openai-compat",
5770
+ label: "OpenAI-compatible (LM Studio, Ollama, vLLM, etc.)",
5771
+ baseUrl: "",
5772
+ capabilities: {
5773
+ streaming: true,
5774
+ tools: true,
5775
+ vision: false,
5776
+ reasoningEffort: false,
5777
+ listModels: true,
5778
+ requiresKey: false,
5779
+ auth: "bearer"
5780
+ },
5781
+ create: openaiCompat
5782
+ };
5783
+ OPENROUTER = {
5784
+ type: "openrouter",
5785
+ label: "OpenRouter (openrouter.ai)",
5786
+ baseUrl: "https://openrouter.ai/api/v1",
5787
+ capabilities: {
5788
+ streaming: true,
5789
+ tools: true,
5790
+ vision: true,
5791
+ reasoningEffort: false,
5792
+ listModels: true,
5793
+ requiresKey: true,
5794
+ auth: "bearer"
5795
+ },
5796
+ create: openaiCompat
5797
+ };
5798
+ OPENAI = {
5799
+ type: "openai",
5800
+ label: "OpenAI API",
5801
+ baseUrl: "https://api.openai.com/v1",
5802
+ capabilities: {
5803
+ streaming: true,
5804
+ tools: true,
5805
+ vision: true,
5806
+ reasoningEffort: true,
5807
+ listModels: true,
5808
+ requiresKey: true,
5809
+ auth: "bearer"
5810
+ },
5811
+ create: openaiCompat
5812
+ };
5813
+ ANTHROPIC = {
5814
+ type: "anthropic",
5815
+ label: "Anthropic Claude",
5816
+ baseUrl: "https://api.anthropic.com",
5817
+ capabilities: {
5818
+ streaming: false,
5819
+ tools: false,
5820
+ vision: true,
5821
+ reasoningEffort: false,
5822
+ listModels: false,
5823
+ requiresKey: true,
5824
+ auth: "header"
5825
+ },
5826
+ create: openaiCompat
5827
+ };
5828
+ OPENCODE_ZEN = {
5829
+ type: "opencode-zen",
5830
+ label: "OpenCode Zen (opencode.ai/zen)",
5831
+ baseUrl: "https://opencode.ai/zen/v1",
5832
+ capabilities: {
5833
+ streaming: true,
5834
+ tools: true,
5835
+ vision: false,
5836
+ reasoningEffort: false,
5837
+ listModels: true,
5838
+ requiresKey: false,
5839
+ auth: "bearer"
5840
+ },
5841
+ create: openaiCompat
5842
+ };
5843
+ OPENCODE_GO = {
5844
+ type: "opencode-go",
5845
+ label: "OpenCode Go (opencode.ai/go)",
5846
+ baseUrl: "https://opencode.ai/zen/go/v1",
5847
+ capabilities: {
5848
+ streaming: true,
5849
+ tools: true,
5850
+ vision: false,
5851
+ reasoningEffort: false,
5852
+ listModels: true,
5853
+ requiresKey: false,
5854
+ auth: "bearer"
5855
+ },
5856
+ create: openaiCompat
5857
+ };
5858
+ BUILTIN_PROVIDERS = [
5859
+ OPENAI_COMPAT,
5860
+ OPENROUTER,
5861
+ OPENAI,
5862
+ ANTHROPIC,
5863
+ OPENCODE_ZEN,
5864
+ OPENCODE_GO
5865
+ ];
5866
+ HOSTED_BASE_URLS = {};
5867
+ for (const spec of BUILTIN_PROVIDERS) {
5868
+ if (spec.baseUrl)
5869
+ HOSTED_BASE_URLS[spec.type] = spec.baseUrl;
5870
+ }
5871
+ });
5872
+
5873
+ // src/modules/providers/create.ts
5874
+ var exports_create = {};
5875
+ __export(exports_create, {
5876
+ defaultRegistry: () => defaultRegistry,
5877
+ createProvider: () => createProvider
5878
+ });
5879
+ function createProvider(type, opts, registry = defaultRegistry()) {
5880
+ const spec = registry.get(type);
5881
+ if (!spec) {
5882
+ return BUILTIN_PROVIDERS[0].create(opts);
5883
+ }
5884
+ return spec.create(opts);
5885
+ }
5886
+ function defaultRegistry() {
5887
+ const registry = new ProviderRegistry;
5888
+ for (const spec of BUILTIN_PROVIDERS) {
5889
+ registry.register(spec);
5890
+ }
5891
+ return registry;
5892
+ }
5893
+ var init_create = __esm(() => {
5894
+ init_presets();
5895
+ });
5896
+
5897
+ // src/modules/providers/manager.ts
5898
+ class ProviderManager {
5899
+ entries;
5900
+ activeProviderName;
5901
+ model;
5902
+ opts;
5903
+ registry;
5904
+ cache = new Map;
5905
+ constructor(config, opts) {
5906
+ this.opts = opts;
5907
+ this.registry = opts.registry ?? defaultRegistry();
5908
+ this.entries = this.normalizeEntries(config);
5909
+ this.activeProviderName = this.resolveActive(config);
5910
+ this.model = "";
5911
+ }
5912
+ get activeModel() {
5913
+ return this.model;
5914
+ }
5915
+ get activeName() {
5916
+ return this.activeProviderName;
5917
+ }
5918
+ get active() {
5919
+ return this.build(this.activeProviderName);
5920
+ }
5921
+ get activeEntry() {
5922
+ const entry = this.entryByName(this.activeProviderName);
5923
+ if (!entry)
5924
+ throw new Error(`Unknown active provider "${this.activeProviderName}"`);
5925
+ return entry;
5926
+ }
5927
+ list() {
5928
+ return this.entries.map((e) => {
5929
+ const preset = this.registry.get(e.type);
5930
+ return { ...e, label: e.label || preset?.label || e.type };
5931
+ }).map((e, i) => ({ e, i, p: e.priority ?? Number.MAX_SAFE_INTEGER })).sort((a, b) => a.p - b.p || a.i - b.i).map(({ e }) => e);
5932
+ }
5933
+ listNames() {
5934
+ return this.list().map((e) => e.label);
5935
+ }
5936
+ pickFor(needs) {
5937
+ const globalWindow = this.opts.contextWindow;
5938
+ if (!needs.vision && !needs.tools && needs.minContext === undefined) {
5939
+ return this.activeProviderName;
5940
+ }
5941
+ for (const e of this.list()) {
5942
+ const caps = this.registry.get(e.type)?.capabilities;
5943
+ if (needs.vision && !caps?.vision)
5944
+ continue;
5945
+ if (needs.tools && !caps?.tools)
5946
+ continue;
5947
+ if (needs.minContext !== undefined && (e.contextWindow ?? globalWindow) < needs.minContext) {
5948
+ continue;
5949
+ }
5950
+ return e.label;
5951
+ }
5952
+ return;
5953
+ }
5954
+ switch(name, model) {
5955
+ if (!this.entryByName(name)) {
5956
+ throw new Error(`Unknown provider "${name}"`);
5957
+ }
5958
+ this.activeProviderName = name;
5959
+ if (model !== undefined)
5960
+ this.model = model;
5961
+ return this.active;
5962
+ }
5963
+ setModel(model) {
5964
+ this.model = model;
5965
+ return this.active;
5966
+ }
5967
+ toConfig() {
5968
+ const active = this.activeEntry;
5969
+ return {
5970
+ active: this.activeProviderName,
5971
+ entries: this.entries,
5972
+ fallback: false,
5973
+ type: active.type,
5974
+ baseUrl: active.baseUrl,
5975
+ apiKey: active.apiKey
5976
+ };
5977
+ }
5978
+ normalizeEntries(config) {
5979
+ if (Array.isArray(config.entries) && config.entries.length > 0) {
5980
+ return config.entries.map((e) => ({ ...e, label: e.label || e.type }));
5981
+ }
5982
+ return [
5983
+ {
5984
+ type: config.type ?? "openai-compat",
5985
+ label: config.type ?? "openai-compat",
5986
+ baseUrl: config.baseUrl ?? "",
5987
+ apiKey: config.apiKey
5988
+ }
5989
+ ];
5990
+ }
5991
+ resolveActive(config) {
5992
+ if (config.active && this.entryByName(config.active))
5993
+ return config.active;
5994
+ const first = this.entries[0];
5995
+ return first ? first.label ?? "openai-compat" : "openai-compat";
5996
+ }
5997
+ entryByName(name) {
5998
+ return this.entries.find((e) => e.label === name || e.type === name);
5999
+ }
6000
+ build(name) {
6001
+ const entry = this.entryByName(name);
6002
+ if (!entry)
6003
+ throw new Error(`Unknown provider "${name}"`);
6004
+ const key = `${name}|${this.model}`;
6005
+ const cached = this.cache.get(key);
6006
+ if (cached)
6007
+ return cached;
6008
+ const provider = createProvider(entry.type, {
6009
+ model: this.model,
6010
+ baseUrl: entry.baseUrl,
6011
+ apiKey: entry.apiKey,
6012
+ contextWindow: entry.contextWindow ?? this.opts.contextWindow,
6013
+ retry: entry.retry ?? this.opts.retry,
6014
+ rateLimits: entry.rateLimits ?? this.opts.rateLimits
6015
+ }, this.registry);
6016
+ this.cache.set(key, provider);
6017
+ return provider;
6018
+ }
6019
+ resetCache() {
6020
+ this.cache.clear();
6021
+ }
6022
+ }
6023
+ var init_manager = __esm(() => {
6024
+ init_create();
6025
+ init_presets();
6026
+ });
6027
+
6028
+ // src/modules/providers/health.ts
6029
+ async function probeProviders(targets, opts) {
6030
+ const timeoutMs = opts?.timeoutMs ?? 5000;
6031
+ const results = await Promise.all(targets.map((t2) => probeOne(t2, timeoutMs)));
6032
+ return results;
6033
+ }
6034
+ async function probeOne(target, timeoutMs) {
6035
+ const start = Date.now();
6036
+ try {
6037
+ const models = await withTimeout(target.listModels(), timeoutMs);
6038
+ return { name: target.name, ok: true, ms: Date.now() - start, models: models.length };
6039
+ } catch (err) {
6040
+ const message = err instanceof Error ? err.message : String(err);
6041
+ return {
6042
+ name: target.name,
6043
+ ok: false,
6044
+ ms: Date.now() - start,
6045
+ models: 0,
6046
+ error: message
6047
+ };
6048
+ }
6049
+ }
6050
+ function withTimeout(promise, ms) {
6051
+ return new Promise((resolve, reject) => {
6052
+ const timer = setTimeout(() => reject(new Error(`timeout after ${ms}ms`)), ms);
6053
+ promise.then((v) => {
6054
+ clearTimeout(timer);
6055
+ resolve(v);
6056
+ }, (e) => {
6057
+ clearTimeout(timer);
6058
+ reject(e);
6059
+ });
6060
+ });
6061
+ }
6062
+ function targetsFromProviders(entries) {
6063
+ return entries.map(({ name, provider }) => ({
6064
+ name,
6065
+ listModels: () => provider.listModels()
6066
+ }));
6067
+ }
6068
+
6069
+ // src/modules/providers/index.ts
6070
+ var init_providers = __esm(() => {
6071
+ init_create();
6072
+ init_manager();
6073
+ init_presets();
6074
+ });
6075
+
6076
+ // src/modules/providers/fallback.ts
6077
+ function isFailoverError(err) {
6078
+ const e = err;
6079
+ if (e?.name === "AbortError")
6080
+ return false;
6081
+ if (typeof e?.llmStatus === "number")
6082
+ return e.llmStatus === 429 || e.llmStatus >= 500;
6083
+ const normCode = (e?.code ?? "").replace(/[-_]/g, "").toLowerCase();
6084
+ if (/^abort/.test(normCode))
6085
+ return false;
6086
+ if (/conn(refused|reset|timedout)|enotfound|eai_again|namenotresolved/.test(normCode)) {
6087
+ return true;
6088
+ }
6089
+ return /fetch failed|unable to connect|network|econnrefused|enotfound|etimedout|socket hang up|connection refused|connection reset/i.test(e?.message ?? "");
6090
+ }
6091
+
6092
+ class FallbackProvider {
6093
+ source;
6094
+ onFailover;
6095
+ constructor(source, onFailover) {
6096
+ this.source = source;
6097
+ this.onFailover = onFailover;
6098
+ }
6099
+ get model() {
6100
+ return this.source.active.model;
6101
+ }
6102
+ get contextWindow() {
6103
+ return this.source.active.contextWindow;
6104
+ }
6105
+ async* chat(messages, tools, signal, options) {
6106
+ const names = this.source.listNames();
6107
+ if (names.length <= 1) {
6108
+ yield* this.source.active.chat(messages, tools, signal, options);
6109
+ return;
6110
+ }
6111
+ let emitted = false;
6112
+ let idx = Math.max(0, names.indexOf(this.source.activeName));
6113
+ let lastError;
6114
+ for (let attempt = 0;attempt < names.length; attempt++) {
6115
+ const name = names[idx];
6116
+ const provider = name === this.source.activeName ? this.source.active : this.source.switch(name);
6117
+ try {
6118
+ for await (const chunk of provider.chat(messages, tools, signal, options)) {
6119
+ emitted = true;
6120
+ yield chunk;
6121
+ }
6122
+ return;
6123
+ } catch (err) {
6124
+ if (emitted || !isFailoverError(err))
6125
+ throw err;
6126
+ lastError = err;
6127
+ idx = (idx + 1) % names.length;
6128
+ this.onFailover?.(name, names[idx], err);
6129
+ }
6130
+ }
6131
+ throw lastError;
6132
+ }
6133
+ countTokens(text) {
6134
+ return this.source.active.countTokens(text);
6135
+ }
6136
+ async listModels() {
6137
+ return this.source.active.listModels();
6138
+ }
6139
+ }
6140
+
5720
6141
  // src/llm/model-loader.ts
5721
6142
  class ModelLoader {
5722
6143
  baseUrl;
@@ -6018,6 +6439,16 @@ function safeResolvePath(baseDir, userPath) {
6018
6439
  return asIs;
6019
6440
  }
6020
6441
  const norm = normalize(userPath);
6442
+ if (isAbsolute(norm)) {
6443
+ if (existsSync7(norm) || existsSync7(dirname2(norm)))
6444
+ return norm;
6445
+ const stripped2 = norm.replace(/^[/\\]/, "");
6446
+ const relativeCandidate = resolve(baseDir, stripped2);
6447
+ if (existsSync7(relativeCandidate) || existsSync7(dirname2(relativeCandidate))) {
6448
+ return relativeCandidate;
6449
+ }
6450
+ return norm;
6451
+ }
6021
6452
  const stripped = norm.replace(/^[/\\]/, "");
6022
6453
  const resolved = resolve(baseDir, stripped);
6023
6454
  if (existsSync7(resolved) || existsSync7(dirname2(resolved)))
@@ -8187,7 +8618,7 @@ Hint: ${t("exec.npm_exec_hint")}`;
8187
8618
  }
8188
8619
  return output;
8189
8620
  }
8190
- var BASH_GRACE_MS = 5000, SPAWN_SETTLE_MS = 100, BG_OUTPUT_PREVIEW_LINES = 15, bashGraceMs, FAILING_FIRST_WORDS, HARD_BLOCK_THRESHOLD = 3, UNIX_TO_WIN_HINTS, UNIX_TO_WIN_TRANSLATE, NEVER_TOOL_CALLS, CLI_FILE_RUN_RE, NPM_EXEC_RE, bashTool;
8621
+ var BASH_GRACE_MS = 5000, SPAWN_SETTLE_MS = 100, BG_OUTPUT_PREVIEW_LINES = 15, bashGraceMs, FAILING_FIRST_WORDS, HARD_BLOCK_THRESHOLD = 3, UNIX_TO_WIN_HINTS, UNIX_TO_WIN_TRANSLATE, NEVER_TOOL_CALLS, CLI_FILE_RUN_RE, NPM_EXEC_RE, HIDDEN_PROCESS_TOOLS, bashTool;
8191
8622
  var init_bash = __esm(() => {
8192
8623
  init_command_validator();
8193
8624
  init_audit_log();
@@ -8253,6 +8684,7 @@ var init_bash = __esm(() => {
8253
8684
  ]);
8254
8685
  CLI_FILE_RUN_RE = /\b(bun|node|deno|python|python3|tsx|ts-node|php|ruby|go\s+run)\S*\s+(run\s+)?["']?[\w./\\-]+\.(ts|js|tsx|jsx|mjs|cjs|py)\b/;
8255
8686
  NPM_EXEC_RE = /could not determine executable to run/i;
8687
+ HIDDEN_PROCESS_TOOLS = ["process_list", "process_log", "process_kill"];
8256
8688
  bashTool = {
8257
8689
  name: "bash",
8258
8690
  icon: "\uD83D\uDCBB",
@@ -8287,6 +8719,13 @@ ${redirected.output}`
8287
8719
  };
8288
8720
  }
8289
8721
  const command = adaptCommandForWindows(originalCommand);
8722
+ const baseCmd = originalCommand.trim().split(/\s+/)[0];
8723
+ if (HIDDEN_PROCESS_TOOLS.includes(baseCmd) && ctx.toolExecutor?.hasTool?.(baseCmd)) {
8724
+ return {
8725
+ success: false,
8726
+ output: t("exec.hidden_tool_hint", { tool: baseCmd })
8727
+ };
8728
+ }
8290
8729
  if (platform2() === "win32") {
8291
8730
  const echoWrite = extractEchoFileWrite(originalCommand);
8292
8731
  if (echoWrite) {
@@ -8489,6 +8928,14 @@ var init_process_log = __esm(() => {
8489
8928
  required: ["id"]
8490
8929
  },
8491
8930
  handler: async (ctx, args) => {
8931
+ if (args.id === undefined || String(args.id).trim() === "") {
8932
+ const known = processRegistry.list();
8933
+ const ids = known.map((p) => p.id);
8934
+ const listPart = ids.length ? `
8935
+ ${t("proc.list_header")}: ${ids.join(", ")}` : `
8936
+ ${t("proc.missing_id_list")}`;
8937
+ return { success: false, output: `${t("proc.missing_id")}${listPart}` };
8938
+ }
8492
8939
  const id = String(args.id);
8493
8940
  const entry = processRegistry.get(id);
8494
8941
  if (!entry) {
@@ -8530,6 +8977,14 @@ var init_process_kill = __esm(() => {
8530
8977
  required: ["id"]
8531
8978
  },
8532
8979
  handler: async (ctx, args) => {
8980
+ if (args.id === undefined || String(args.id).trim() === "") {
8981
+ const known = processRegistry.list();
8982
+ const ids = known.map((p) => p.id);
8983
+ const listPart = ids.length ? `
8984
+ ${t("proc.list_header")}: ${ids.join(", ")}` : `
8985
+ ${t("proc.missing_id_list")}`;
8986
+ return { success: false, output: `${t("proc.missing_id")}${listPart}` };
8987
+ }
8533
8988
  const id = String(args.id);
8534
8989
  const entry = processRegistry.get(id);
8535
8990
  if (!entry) {
@@ -8636,9 +9091,11 @@ import { readFileSync as readFileSync9, existsSync as existsSync18 } from "fs";
8636
9091
 
8637
9092
  class SessionLogger {
8638
9093
  session;
9094
+ getCaller;
8639
9095
  logger;
8640
- constructor(session, logger) {
9096
+ constructor(session, logger, getCaller) {
8641
9097
  this.session = session;
9098
+ this.getCaller = getCaller;
8642
9099
  this.logger = logger;
8643
9100
  this.bindSessionDir();
8644
9101
  }
@@ -8677,12 +9134,15 @@ class SessionLogger {
8677
9134
  });
8678
9135
  }
8679
9136
  logAssistant(content, reasoning, toolCalls, iteration) {
9137
+ const caller = this.getCaller?.();
8680
9138
  if (reasoning) {
8681
9139
  this.session?.appendLog({
8682
9140
  ts: new Date().toISOString(),
8683
9141
  type: "reasoning",
8684
9142
  content: reasoning,
8685
- iteration
9143
+ iteration,
9144
+ provider: caller?.provider,
9145
+ model: caller?.model
8686
9146
  });
8687
9147
  }
8688
9148
  this.session?.appendLog({
@@ -8696,14 +9156,19 @@ class SessionLogger {
8696
9156
  arguments: tc.arguments
8697
9157
  }))
8698
9158
  } : {},
8699
- iteration
9159
+ iteration,
9160
+ provider: caller?.provider,
9161
+ model: caller?.model
8700
9162
  });
8701
9163
  }
8702
9164
  saveAssistantMessage(content) {
9165
+ const caller = this.getCaller?.();
8703
9166
  this.session?.appendMessage({
8704
9167
  role: "assistant",
8705
9168
  content: content.slice(0, 500),
8706
- timestamp: new Date().toISOString()
9169
+ timestamp: new Date().toISOString(),
9170
+ provider: caller?.provider,
9171
+ model: caller?.model
8707
9172
  });
8708
9173
  }
8709
9174
  logToolDefs(toolCount, toolNames, iteration) {
@@ -8726,11 +9191,14 @@ class SessionLogger {
8726
9191
  });
8727
9192
  }
8728
9193
  logToolResult(call, result, duration, iteration) {
9194
+ const caller = this.getCaller?.();
8729
9195
  this.session?.appendMessage({
8730
9196
  role: "tool",
8731
9197
  content: sanitizeLogMessage(result.output.slice(0, 500)),
8732
9198
  name: call.name,
8733
- timestamp: new Date().toISOString()
9199
+ timestamp: new Date().toISOString(),
9200
+ provider: caller?.provider,
9201
+ model: caller?.model
8734
9202
  });
8735
9203
  this.session?.appendLog({
8736
9204
  ts: new Date().toISOString(),
@@ -8741,7 +9209,9 @@ class SessionLogger {
8741
9209
  content: sanitizeLogMessage(result.output.slice(0, 1000)),
8742
9210
  diff: result.diff,
8743
9211
  duration,
8744
- iteration
9212
+ iteration,
9213
+ provider: caller?.provider,
9214
+ model: caller?.model
8745
9215
  });
8746
9216
  }
8747
9217
  logCompaction(info) {
@@ -8768,6 +9238,8 @@ class SessionLogger {
8768
9238
  type: "context",
8769
9239
  content: info.kind === "start" ? "context snapshot at session start" : `context at iteration ${info.iteration}`,
8770
9240
  iteration: info.iteration,
9241
+ provider: info.provider,
9242
+ model: info.model,
8771
9243
  window: info.window,
8772
9244
  systemBudget: info.systemBudget,
8773
9245
  reserveBudget: info.reserveBudget,
@@ -8787,6 +9259,8 @@ class SessionLogger {
8787
9259
  ts: new Date().toISOString(),
8788
9260
  type: "llm_usage",
8789
9261
  iteration,
9262
+ provider: usage.provider,
9263
+ model: usage.model,
8790
9264
  promptTokens: usage.promptTokens,
8791
9265
  completionTokens: usage.completionTokens,
8792
9266
  totalTokens: usage.totalTokens,
@@ -9622,18 +10096,20 @@ class OrchestratorClient {
9622
10096
  this.config = config;
9623
10097
  if (config.model) {
9624
10098
  if (config.provider) {
9625
- this.provider = new OpenAICompatProvider({
10099
+ this.provider = createProvider(config.provider.type ?? "openai-compat", {
9626
10100
  model: config.model,
9627
10101
  baseUrl: config.provider.baseUrl || "http://localhost:1234/v1",
9628
10102
  apiKey: config.provider.apiKey,
10103
+ contextWindow: 32768,
9629
10104
  retry: config.retry
9630
10105
  });
9631
10106
  } else if (defaultProvider) {
9632
10107
  this.provider = defaultProvider;
9633
10108
  } else {
9634
- this.provider = new OpenAICompatProvider({
10109
+ this.provider = createProvider("openai-compat", {
9635
10110
  model: config.model,
9636
10111
  baseUrl: "http://localhost:1234/v1",
10112
+ contextWindow: 32768,
9637
10113
  retry: config.retry
9638
10114
  });
9639
10115
  }
@@ -9800,7 +10276,7 @@ Respond with JSON only:
9800
10276
 
9801
10277
  Max 3 re-plan cycles. After 3 cycles, return partial result.`;
9802
10278
  var init_orchestrator = __esm(() => {
9803
- init_openai_compat();
10279
+ init_create();
9804
10280
  init_esm();
9805
10281
  });
9806
10282
 
@@ -10118,6 +10594,12 @@ class StuckDetector {
10118
10594
  this.consecutiveFailures = 0;
10119
10595
  this.lastFailedTool = "";
10120
10596
  this.escalationCount = 0;
10597
+ for (const [tool, count] of this.toolErrors) {
10598
+ if (count <= 1)
10599
+ this.toolErrors.delete(tool);
10600
+ else
10601
+ this.toolErrors.set(tool, count - 1);
10602
+ }
10121
10603
  if (this.iterationsOnCurrentStep > 0) {
10122
10604
  this.iterationsOnCurrentStep = Math.max(0, this.iterationsOnCurrentStep - 1);
10123
10605
  }
@@ -10308,6 +10790,20 @@ class StuckDetector {
10308
10790
  count: this.repetitionThreshold
10309
10791
  });
10310
10792
  }
10793
+ getWarningKey() {
10794
+ if (this.isStuck())
10795
+ return "stuck";
10796
+ const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
10797
+ if (errorTool)
10798
+ return `tool-errors:${errorTool[0]}`;
10799
+ if (this.hasConsecutiveFailures())
10800
+ return "consecutive";
10801
+ if (this.hasRepetitiveToolCalls())
10802
+ return "repetitive";
10803
+ if (this.hasReadOnlyLoop())
10804
+ return "read-only";
10805
+ return "";
10806
+ }
10311
10807
  getStuckReason() {
10312
10808
  if (this.isStuck()) {
10313
10809
  return t("exec.stuck", {
@@ -11849,6 +12345,7 @@ class CostTracker {
11849
12345
  model;
11850
12346
  _total = 0;
11851
12347
  _known = false;
12348
+ byProvider = new Map;
11852
12349
  constructor(model, config) {
11853
12350
  this.model = model;
11854
12351
  this.config = config;
@@ -11856,16 +12353,36 @@ class CostTracker {
11856
12353
  setModel(model) {
11857
12354
  this.model = model;
11858
12355
  }
11859
- record(promptTokens, completionTokens) {
12356
+ record(promptTokens, completionTokens, provider) {
12357
+ const key = provider || "unknown";
11860
12358
  const cost = calculateCost(this.model, promptTokens, completionTokens, this.config);
11861
12359
  if (cost === undefined)
11862
12360
  return;
11863
12361
  this._known = true;
11864
12362
  this._total += cost;
12363
+ let entry = this.byProvider.get(key);
12364
+ if (!entry) {
12365
+ entry = { total: 0, known: false };
12366
+ this.byProvider.set(key, entry);
12367
+ }
12368
+ entry.known = true;
12369
+ entry.total += cost;
11865
12370
  }
11866
12371
  get total() {
11867
12372
  return this._known ? this._total : undefined;
11868
12373
  }
12374
+ totalFor(provider) {
12375
+ const entry = this.byProvider.get(provider);
12376
+ return entry && entry.known ? entry.total : undefined;
12377
+ }
12378
+ breakdown() {
12379
+ const entries = [];
12380
+ for (const [provider, entry] of this.byProvider) {
12381
+ if (entry.known)
12382
+ entries.push({ provider, cost: entry.total });
12383
+ }
12384
+ return entries.sort((a, b) => b.cost - a.cost);
12385
+ }
11869
12386
  get formatted() {
11870
12387
  return this._known ? formatCost(this._total) : undefined;
11871
12388
  }
@@ -11908,6 +12425,12 @@ class Agent {
11908
12425
  get contextManager() {
11909
12426
  return this.deps.contextManager;
11910
12427
  }
12428
+ callerProvenance() {
12429
+ return {
12430
+ provider: this.deps.config.provider?.type || "unknown",
12431
+ model: this.deps.config.model
12432
+ };
12433
+ }
11911
12434
  getModule(name) {
11912
12435
  return this.deps.moduleRegistry?.get(name);
11913
12436
  }
@@ -12022,7 +12545,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12022
12545
  sessionManager,
12023
12546
  baseDir
12024
12547
  } = this.deps;
12025
- const slog = new SessionLogger(sessionManager, logger);
12548
+ const slog = new SessionLogger(sessionManager, logger, () => this.callerProvenance());
12026
12549
  if (sessionManager && !sessionManager.getActive()) {
12027
12550
  sessionManager.create();
12028
12551
  logger.debug(`Session started: ${sessionManager.getActive()}`);
@@ -12076,7 +12599,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12076
12599
  sessionManager,
12077
12600
  baseDir
12078
12601
  } = this.deps;
12079
- const slog = new SessionLogger(sessionManager, logger);
12602
+ const slog = new SessionLogger(sessionManager, logger, () => this.callerProvenance());
12080
12603
  this.abortController = new AbortController;
12081
12604
  let iteration = 0;
12082
12605
  let lastText = "";
@@ -12255,7 +12778,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
12255
12778
  source,
12256
12779
  durationMs: Date.now() - llmStart
12257
12780
  });
12258
- this.costTracker.record(prompt, completion);
12781
+ this.costTracker.record(prompt, completion, this.callerProvenance().provider);
12259
12782
  }
12260
12783
  if (this.shutdownRequested) {
12261
12784
  break;
@@ -12608,6 +13131,7 @@ ${warnLine}
12608
13131
  completionTokens: usageTokens.completion,
12609
13132
  totalTokens: usageTokens.total,
12610
13133
  totalCost: this.costTracker.total,
13134
+ costBreakdown: this.costTracker.breakdown(),
12611
13135
  compactionCount: contextManager.getCompactionCount(),
12612
13136
  contextQuality: contextManager.getQuality()
12613
13137
  };
@@ -12623,6 +13147,7 @@ ${warnLine}
12623
13147
  completionTokens: usageTokens.completion,
12624
13148
  totalTokens: usageTokens.total,
12625
13149
  totalCost: this.costTracker.total,
13150
+ costBreakdown: this.costTracker.breakdown(),
12626
13151
  compactionCount: contextManager.getCompactionCount(),
12627
13152
  contextQuality: contextManager.getQuality()
12628
13153
  };
@@ -12649,6 +13174,45 @@ ${warnLine}
12649
13174
  this.deps.config = config;
12650
13175
  this.costTracker.setModel(config.model);
12651
13176
  }
13177
+ setProvider(name, model) {
13178
+ const manager = new ProviderManager(this.deps.config.provider, {
13179
+ contextWindow: this.deps.config.contextWindow,
13180
+ retry: this.deps.config.retry,
13181
+ rateLimits: this.deps.config.security?.rateLimits
13182
+ });
13183
+ manager.switch(name, model);
13184
+ const providerCfg = manager.toConfig();
13185
+ this.deps.config.provider = providerCfg;
13186
+ this.deps.llmProvider = manager.active;
13187
+ this.deps.toolExecutor.updateProvider(manager.active);
13188
+ this.deps.toolExecutor.ctx.llmProvider = manager.active;
13189
+ }
13190
+ listProviders() {
13191
+ const manager = new ProviderManager(this.deps.config.provider, {
13192
+ contextWindow: this.deps.config.contextWindow,
13193
+ retry: this.deps.config.retry,
13194
+ rateLimits: this.deps.config.security?.rateLimits
13195
+ });
13196
+ const activeName = this.deps.config.provider.active;
13197
+ return manager.list().map((e) => ({
13198
+ ...e,
13199
+ active: e.label === activeName || e.type === activeName
13200
+ }));
13201
+ }
13202
+ getProviderFor(name) {
13203
+ try {
13204
+ const manager = new ProviderManager(this.deps.config.provider, {
13205
+ contextWindow: this.deps.config.contextWindow,
13206
+ retry: this.deps.config.retry,
13207
+ rateLimits: this.deps.config.security?.rateLimits
13208
+ });
13209
+ manager.setModel(this.deps.config.model);
13210
+ manager.switch(name);
13211
+ return manager.active;
13212
+ } catch {
13213
+ return;
13214
+ }
13215
+ }
12652
13216
  setContext(messages) {
12653
13217
  const { contextManager } = this.deps;
12654
13218
  contextManager.clear();
@@ -12683,6 +13247,7 @@ ${warnLine}
12683
13247
  }
12684
13248
  var TOOL_RESULT_MAX_TOKENS_RATIO = 0.3, TOOL_RESULT_ABSOLUTE_MAX_CHARS = 15000, QUALITY_TRIGGER_THRESHOLD = 40, FORCED_COMPACTION_COOLDOWN = 3;
12685
13249
  var init_agent = __esm(() => {
13250
+ init_manager();
12686
13251
  init_i18n();
12687
13252
  init_colors();
12688
13253
  init_prompt_builder();
@@ -13158,7 +13723,7 @@ ${lines.join(`
13158
13723
  }
13159
13724
  }
13160
13725
  var COMPACTION_INTERVAL = 15, KEEP_LAST_N = 6;
13161
- var init_manager = __esm(() => {
13726
+ var init_manager2 = __esm(() => {
13162
13727
  init_fact_extractor();
13163
13728
  });
13164
13729
 
@@ -13828,7 +14393,7 @@ class PluginManager {
13828
14393
  return chunk;
13829
14394
  }
13830
14395
  }
13831
- var init_manager2 = __esm(() => {
14396
+ var init_manager3 = __esm(() => {
13832
14397
  init_checker();
13833
14398
  });
13834
14399
 
@@ -13848,9 +14413,9 @@ Workflow rules:
13848
14413
  var init_subagent = __esm(() => {
13849
14414
  init_i18n();
13850
14415
  init_agent();
13851
- init_manager();
13852
- init_detector();
13853
14416
  init_manager2();
14417
+ init_detector();
14418
+ init_manager3();
13854
14419
  init_audit_log();
13855
14420
  init_session_isolation();
13856
14421
  init_store();
@@ -14958,8 +15523,8 @@ var init_pipeline_run = __esm(() => {
14958
15523
  init_engine();
14959
15524
  init_parser();
14960
15525
  init_agent();
14961
- init_manager();
14962
15526
  init_manager2();
15527
+ init_manager3();
14963
15528
  init_detector();
14964
15529
  init_audit_log();
14965
15530
  init_session_isolation();
@@ -17503,6 +18068,16 @@ import { spawn as spawn6, execSync } from "child_process";
17503
18068
  import { existsSync as existsSync32, readFileSync as readFileSync17 } from "fs";
17504
18069
  import { resolve as resolve18, extname as extname4, join as join26 } from "path";
17505
18070
  import { platform as platform5 } from "os";
18071
+ function lintCacheKey(baseDir, lintScript) {
18072
+ return `${baseDir}::${lintScript}`;
18073
+ }
18074
+ function isMissingBinaryError(err, combinedOutput) {
18075
+ if (err.status === 127)
18076
+ return true;
18077
+ if (err.code === "ENOENT")
18078
+ return true;
18079
+ return MISSING_BINARY_RE.test(combinedOutput);
18080
+ }
17506
18081
  function contentHash(content) {
17507
18082
  let h = 5381;
17508
18083
  for (let i = 0;i < content.length; i++) {
@@ -17612,23 +18187,35 @@ class LintOnWritePlugin {
17612
18187
  return null;
17613
18188
  }
17614
18189
  async runProjectLint(ctx, result, signal) {
18190
+ let lintScript;
17615
18191
  try {
17616
18192
  const packageJsonPath = join26(ctx.baseDir, "package.json");
17617
18193
  if (!existsSync32(packageJsonPath)) {
17618
18194
  return;
17619
18195
  }
17620
18196
  const packageJson = JSON.parse(readFileSync17(packageJsonPath, "utf-8"));
17621
- const lintScript = packageJson.scripts?.lint;
18197
+ lintScript = packageJson.scripts?.lint;
17622
18198
  if (!lintScript) {
17623
18199
  return;
17624
18200
  }
18201
+ if (missingLintBinaries.has(lintCacheKey(ctx.baseDir, lintScript))) {
18202
+ ctx.logger.debug(`Lint skipped (linter for "${lintScript}" not installed)`);
18203
+ return;
18204
+ }
17625
18205
  ctx.logger.debug(`Running lint: ${lintScript}`);
17626
18206
  await runAsync(lintScript, ctx.baseDir, 30000, signal);
17627
18207
  ctx.logger.debug("Lint passed");
17628
18208
  } catch (err) {
17629
18209
  const stderr = (err.stderr?.toString?.() || "").trim();
17630
18210
  const stdout = (err.stdout?.toString?.() || "").trim();
17631
- const detail = (stderr || stdout || err.message).split(`
18211
+ const combined = `${stderr}
18212
+ ${stdout}`;
18213
+ if (lintScript && isMissingBinaryError(err, combined)) {
18214
+ missingLintBinaries.add(lintCacheKey(ctx.baseDir, lintScript));
18215
+ ctx.logger.debug(`Lint binary missing — skipping project lint for this session`);
18216
+ return;
18217
+ }
18218
+ const detail = combined.trim().split(`
17632
18219
  `).filter(Boolean).slice(-5).join(`
17633
18220
  `);
17634
18221
  ctx.logger.warn(`Lint failed: ${err.message}`);
@@ -17733,9 +18320,11 @@ function runAsync(command, cwd, timeoutMs, signal) {
17733
18320
  });
17734
18321
  });
17735
18322
  }
17736
- var TYPE_CHECK_DEBOUNCE_MS = 2000, syntaxCache, _winDecoder, plugin;
18323
+ var TYPE_CHECK_DEBOUNCE_MS = 2000, missingLintBinaries, MISSING_BINARY_RE, syntaxCache, _winDecoder, plugin;
17737
18324
  var init_lint_on_write = __esm(() => {
17738
18325
  init_project_root();
18326
+ missingLintBinaries = new Set;
18327
+ MISSING_BINARY_RE = /command not found|not recognized|не является внутренней|не распознан|не є внутрішньою|".+?" не является/i;
17739
18328
  syntaxCache = new Map;
17740
18329
  plugin = new LintOnWritePlugin;
17741
18330
  });
@@ -17763,6 +18352,13 @@ function generatePlanId() {
17763
18352
  }
17764
18353
  return `plan_${id}`;
17765
18354
  }
18355
+ function getPlanProgress(plan) {
18356
+ const done = plan.steps.filter((s) => s.status === "done").length;
18357
+ const skipped = plan.steps.filter((s) => s.status === "skipped").length;
18358
+ const total = plan.steps.length;
18359
+ const settled = done + skipped;
18360
+ return { done, skipped, total, settled, terminal: total > 0 && settled === total };
18361
+ }
17766
18362
 
17767
18363
  class PlanCreator {
17768
18364
  static createPlan(title, stepDescriptions, baseDir, kinds) {
@@ -17803,9 +18399,9 @@ class PlanCreator {
17803
18399
  }
17804
18400
  static toPromptBlock(plan, currentStepIndex) {
17805
18401
  const date = plan.createdAt.slice(0, 10);
17806
- const doneCount = plan.steps.filter((s) => s.status === "done").length;
17807
- const terminal = plan.steps.every((s) => s.status === "done" || s.status === "skipped");
17808
- const progress = terminal ? `${doneCount}/${plan.steps.length} done, complete` : `${doneCount}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`;
18402
+ const p = getPlanProgress(plan);
18403
+ const skippedPart = p.skipped > 0 ? `, ${p.skipped} skipped` : "";
18404
+ const progress = p.terminal ? `${p.settled}/${p.total} done${skippedPart}, complete` : `${p.settled}/${p.total} done${skippedPart}, current: step ${currentStepIndex + 1}`;
17809
18405
  const lines = [
17810
18406
  `[${plan.id}] ${plan.title}`,
17811
18407
  `Dir: ${plan.baseDir}`,
@@ -17871,13 +18467,15 @@ class PlanTracker {
17871
18467
  return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
17872
18468
  }
17873
18469
  getProgressString() {
17874
- const done = this.plan.steps.filter((s) => s.status === "done").length;
17875
- const total = this.plan.steps.length;
17876
- const pct = total > 0 ? Math.round(done / total * 100) : 0;
18470
+ const p = getPlanProgress(this.plan);
18471
+ const total = p.total;
18472
+ const settled = p.settled;
18473
+ const pct = total > 0 ? Math.round(settled / total * 100) : 0;
17877
18474
  const barWidth = 10;
17878
- const filled = Math.round(done / total * barWidth);
18475
+ const filled = Math.round(settled / total * barWidth);
17879
18476
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
17880
- return `[${this.plan.id}] ${this.plan.title} ${done}/${total} ${bar} ${pct}%`;
18477
+ const skippedPart = p.skipped > 0 ? ` (${p.skipped} skipped)` : "";
18478
+ return `[${this.plan.id}] ${this.plan.title} ${settled}/${total}${skippedPart} ${bar} ${pct}%`;
17881
18479
  }
17882
18480
  toPromptBlock() {
17883
18481
  return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
@@ -18255,13 +18853,20 @@ function createExecutionPlugin(deps) {
18255
18853
  }
18256
18854
  const stuckReason = deps.stuckDetector.getStuckReason();
18257
18855
  if (stuckReason) {
18258
- const logIt = deps.stuckDetector.isStuck() ? !deps.state.stuckNotified : true;
18856
+ const isStuck = deps.stuckDetector.isStuck();
18857
+ const warnKey = deps.stuckDetector.getWarningKey();
18858
+ const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
18859
+ const logIt = isStuck ? !deps.state.stuckNotified : warnKey !== deps.state.lastStuckWarnKey || iter - deps.state.lastStuckWarnIter >= STUCK_WARN_REPEAT_EVERY;
18259
18860
  if (logIt) {
18260
18861
  ctx.logger?.warn(stuckReason);
18261
18862
  ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
18262
- if (deps.stuckDetector.isStuck())
18863
+ deps.state.lastStuckWarnKey = warnKey;
18864
+ deps.state.lastStuckWarnIter = iter;
18865
+ if (isStuck)
18263
18866
  deps.state.stuckNotified = true;
18264
18867
  }
18868
+ } else {
18869
+ deps.state.lastStuckWarnKey = "";
18265
18870
  }
18266
18871
  if (deps.stuckDetector.isStuck() || deps.stuckDetector.hasRepetitiveToolCalls() || deps.stuckDetector.hasReadOnlyLoop()) {
18267
18872
  const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
@@ -18442,7 +19047,7 @@ Last compile error: ${first[1]}`;
18442
19047
  }
18443
19048
  };
18444
19049
  }
18445
- var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10, PLAN_NUDGE_THRESHOLD = 2;
19050
+ var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10, STUCK_WARN_REPEAT_EVERY = 5, PLAN_NUDGE_THRESHOLD = 2;
18446
19051
  var init_execution_plugin = __esm(() => {
18447
19052
  init_i18n();
18448
19053
  init_bash();
@@ -18697,12 +19302,12 @@ ${display}`,
18697
19302
  deps.store.archivePlan(plan);
18698
19303
  deps.recordCompleted(plan);
18699
19304
  deps.trackerRef.current = null;
18700
- const done = plan.steps.filter((s) => s.status === "done").length;
19305
+ const p = getPlanProgress(plan);
18701
19306
  return {
18702
19307
  success: true,
18703
19308
  output: t("plan.completed_archived", {
18704
19309
  id: plan.id,
18705
- done: String(done),
19310
+ done: String(p.settled),
18706
19311
  total: String(plan.steps.length)
18707
19312
  })
18708
19313
  };
@@ -18796,13 +19401,13 @@ ${t("plan.no_deliverables", { step: String(stepId) })}` : "";
18796
19401
  deps.store.archivePlan(plan);
18797
19402
  deps.recordCompleted(plan);
18798
19403
  deps.trackerRef.current = null;
18799
- const done = plan.steps.filter((s) => s.status === "done").length;
19404
+ const p = getPlanProgress(plan);
18800
19405
  return {
18801
19406
  success: true,
18802
19407
  output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
18803
19408
  ${t("plan.completed_archived", {
18804
19409
  id: plan.id,
18805
- done: String(done),
19410
+ done: String(p.settled),
18806
19411
  total: String(plan.steps.length)
18807
19412
  })}${vacuousNote}`
18808
19413
  };
@@ -18817,8 +19422,15 @@ ${progress}${vacuousNote}`,
18817
19422
  display
18818
19423
  };
18819
19424
  }
18820
- if (action === "update" && Array.isArray(args.steps) && args.steps.length > 0 && deps.trackerRef.current) {
19425
+ if (action === "update" && Array.isArray(args.steps) && args.steps.length > 0 && !args.step) {
18821
19426
  const tracker = deps.trackerRef.current;
19427
+ if (!tracker) {
19428
+ return { success: false, output: t("plan.no_active") };
19429
+ }
19430
+ const progressed = tracker.getPlan().steps.some((s) => s.status !== "pending");
19431
+ if (progressed) {
19432
+ return { success: false, output: t("plan.bulk_update_rejected") };
19433
+ }
18822
19434
  const title = String(args.title || tracker.getPlan().title);
18823
19435
  const steps = args.steps.map(String);
18824
19436
  const parsed = deps.parseKinds(args, steps);
@@ -18905,6 +19517,15 @@ ${progress}${vacuousNote}`,
18905
19517
  };
18906
19518
  }
18907
19519
  if (!deps.trackerRef.current) {
19520
+ if (args.id) {
19521
+ const found = deps.store.find(String(args.id));
19522
+ if (found && found.status !== "active") {
19523
+ return {
19524
+ success: false,
19525
+ output: t("plan.readonly", { id: found.plan.id, status: found.status })
19526
+ };
19527
+ }
19528
+ }
18908
19529
  return { success: false, output: t("plan.no_active") };
18909
19530
  }
18910
19531
  return {
@@ -19079,6 +19700,8 @@ class ExecutionModule {
19079
19700
  consecutivePlanWarnings: 0,
19080
19701
  lastStepId: -1,
19081
19702
  stuckNotified: false,
19703
+ lastStuckWarnKey: "",
19704
+ lastStuckWarnIter: -STUCK_WARN_REPEAT_EVERY,
19082
19705
  depsGateHints: new Map,
19083
19706
  mutationsWithoutPlan: 0,
19084
19707
  planNudgeSent: false,
@@ -20016,7 +20639,7 @@ class SessionManager {
20016
20639
  }
20017
20640
  }
20018
20641
  }
20019
- var init_manager3 = __esm(() => {
20642
+ var init_manager4 = __esm(() => {
20020
20643
  init_i18n();
20021
20644
  init_session_isolation();
20022
20645
  });
@@ -20052,7 +20675,7 @@ var init_module2 = __esm(() => {
20052
20675
  // src/modules/session/index.ts
20053
20676
  var init_session2 = __esm(() => {
20054
20677
  init_store3();
20055
- init_manager3();
20678
+ init_manager4();
20056
20679
  init_module2();
20057
20680
  });
20058
20681
 
@@ -22274,14 +22897,18 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
22274
22897
  const profile = new UserProfile(join39(dir));
22275
22898
  profile.load() || profile.collect();
22276
22899
  profile.save();
22277
- const llmProvider = new OpenAICompatProvider({
22278
- model: config.model,
22279
- baseUrl: config.provider.baseUrl,
22280
- apiKey: config.provider.apiKey,
22900
+ const providerManager = new ProviderManager(config.provider, {
22281
22901
  contextWindow: config.contextWindow,
22282
22902
  retry: config.retry,
22283
22903
  rateLimits: config.security?.rateLimits
22284
22904
  });
22905
+ providerManager.setModel(config.model);
22906
+ let llmProvider = providerManager.active;
22907
+ if (config.provider.fallback && providerManager.listNames().length > 1) {
22908
+ llmProvider = new FallbackProvider(providerManager, (from, to, error) => {
22909
+ logger.warn(`provider failover: ${from} -> ${to} (${error.message.slice(0, 120)})`);
22910
+ });
22911
+ }
22285
22912
  const baseDir = projectDir ? resolve23(projectDir) : process.cwd();
22286
22913
  const envReport = collectEnvironment({
22287
22914
  configDir: dir,
@@ -22572,15 +23199,15 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
22572
23199
  var init_bootstrap = __esm(() => {
22573
23200
  init_config2();
22574
23201
  init_app_logger();
22575
- init_openai_compat();
23202
+ init_providers();
22576
23203
  init_tools();
22577
23204
  init_hidden_tools_block();
22578
23205
  init_executor();
22579
- init_manager2();
23206
+ init_manager3();
22580
23207
  init_loader();
22581
23208
  init_lint_on_write();
22582
23209
  init_notify();
22583
- init_manager();
23210
+ init_manager2();
22584
23211
  init_token_counter();
22585
23212
  init_detector();
22586
23213
  init_module();
@@ -23324,35 +23951,24 @@ async function runSetup(externalRl) {
23324
23951
  console.log(l);
23325
23952
  return answers;
23326
23953
  }
23327
- var PROVIDER_TYPES, KNOWN_PORTS, HOSTED_BASE_URLS;
23954
+ var PROVIDER_TYPES, KNOWN_PORTS;
23328
23955
  var init_setup = __esm(() => {
23329
23956
  init_colors();
23330
23957
  init_i18n();
23331
23958
  init_spinner();
23959
+ init_presets();
23332
23960
  init_box();
23333
23961
  init_table();
23334
- PROVIDER_TYPES = [
23335
- {
23336
- value: "openai-compat",
23337
- label: "OpenAI-compatible (LM Studio, Ollama, vLLM, etc.)"
23338
- },
23339
- { value: "openai", label: "OpenAI API" },
23340
- { value: "anthropic", label: "Anthropic Claude" },
23341
- { value: "opencode-zen", label: "OpenCode Zen (opencode.ai/zen)" },
23342
- { value: "opencode-go", label: "OpenCode Go (opencode.ai/go)" }
23343
- ];
23962
+ PROVIDER_TYPES = BUILTIN_PROVIDERS.map((spec) => ({
23963
+ value: spec.type,
23964
+ label: spec.label
23965
+ }));
23344
23966
  KNOWN_PORTS = [
23345
23967
  { port: 1234, label: "LM Studio" },
23346
23968
  { port: 11434, label: "Ollama" },
23347
23969
  { port: 8080, label: "vLLM / custom" },
23348
23970
  { port: 4891, label: "GPT4All" }
23349
23971
  ];
23350
- HOSTED_BASE_URLS = {
23351
- openai: "https://api.openai.com/v1",
23352
- anthropic: "https://api.anthropic.com",
23353
- "opencode-zen": "https://opencode.ai/zen/v1",
23354
- "opencode-go": "https://opencode.ai/zen/go/v1"
23355
- };
23356
23972
  });
23357
23973
 
23358
23974
  // src/modules/certification/manifest.ts
@@ -31176,7 +31792,13 @@ function registerMmaCommands(ctx) {
31176
31792
  const sys = Math.floor(ctxW * cfg.contextBudget.systemPrompt);
31177
31793
  const res = Math.floor(ctxW * cfg.contextBudget.responseReserve);
31178
31794
  console.log(`${t("repl.model")} ${cfg.model}`);
31179
- console.log(`${t("repl.provider")} ${cfg.provider.type} → ${cfg.provider.baseUrl}`);
31795
+ const providers = ctx.agent.listProviders();
31796
+ const active = providers.find((p) => p.active);
31797
+ if (active) {
31798
+ console.log(`${t("repl.provider")} ${active.label} (${pc2.dim(active.type)}) → ${pc2.dim(active.baseUrl)}`);
31799
+ } else {
31800
+ console.log(`${t("repl.provider")} ${cfg.provider.type} → ${cfg.provider.baseUrl}`);
31801
+ }
31180
31802
  console.log(`${t("repl.context")} ${ctxW} (sys:${sys} res:${res} hist:${ctxW - sys - res})`);
31181
31803
  console.log(`${t("repl.max_iters")} ${cfg.maxToolIterations}`);
31182
31804
  console.log(`${t("repl.stuck_thresh")} ${cfg.stuckThreshold}`);
@@ -31204,8 +31826,14 @@ function registerMmaCommands(ctx) {
31204
31826
  description: t("repl.status"),
31205
31827
  usage: t("repl.status_usage"),
31206
31828
  action: () => {
31829
+ const providers = ctx.agent.listProviders();
31830
+ const active = providers.find((p) => p.active);
31207
31831
  console.log(`${t("repl.model")} ${ctx.config.model}`);
31208
- console.log(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
31832
+ if (active) {
31833
+ console.log(`${t("repl.provider")} ${active.label} (${pc2.dim(active.type)}) @ ${pc2.dim(active.baseUrl)}`);
31834
+ } else {
31835
+ console.log(`${t("repl.provider")} ${ctx.config.provider.type} @ ${ctx.config.provider.baseUrl}`);
31836
+ }
31209
31837
  const meta = ctx.sessionManager?.getActiveMeta();
31210
31838
  if (meta) {
31211
31839
  console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} messages`);
@@ -31264,8 +31892,17 @@ Excluded blocks: ${info.excluded.length}`));
31264
31892
  action: async (args) => {
31265
31893
  const subcmd = args[0];
31266
31894
  if (!subcmd || subcmd === "list") {
31267
- console.log(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
31268
- console.log(` ${ctx.config.provider.baseUrl}`);
31895
+ const providers = ctx.agent.listProviders();
31896
+ if (providers.length === 0) {
31897
+ console.log(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
31898
+ console.log(` ${ctx.config.provider.baseUrl}`);
31899
+ } else {
31900
+ console.log(t("repl.provider_current"));
31901
+ for (const p of providers) {
31902
+ const marker = p.active ? pc2.green("* ") : " ";
31903
+ console.log(` ${marker}${p.label} (${pc2.dim(p.type)}) ${pc2.dim(p.baseUrl)}`);
31904
+ }
31905
+ }
31269
31906
  return;
31270
31907
  }
31271
31908
  if (subcmd === "use") {
@@ -31274,11 +31911,13 @@ Excluded blocks: ${info.excluded.length}`));
31274
31911
  console.log(t("repl.provider_usage"));
31275
31912
  return;
31276
31913
  }
31277
- ctx.config.provider.type = name;
31278
- const configPath = join47(homedir17(), ".mma", "config.json");
31279
- saveConfig(ctx.config, configPath);
31280
- await ctx.agent.reconfigure(ctx.config);
31281
- console.log(pc2.green(t("repl.provider_set", { name })));
31914
+ try {
31915
+ await ctx.agent.setProvider(name);
31916
+ ctx.refreshModelCache();
31917
+ console.log(pc2.green(t("repl.provider_set", { name })));
31918
+ } catch (e) {
31919
+ console.log(pc2.red(e.message));
31920
+ }
31282
31921
  return;
31283
31922
  }
31284
31923
  console.log(t("repl.provider_usage"));
@@ -31306,6 +31945,7 @@ Excluded blocks: ${info.excluded.length}`));
31306
31945
  const models = await provider.listModels();
31307
31946
  s.stop();
31308
31947
  if (models.length > 0) {
31948
+ ctx.refreshModelCache();
31309
31949
  console.log(t("cli.available_models"));
31310
31950
  const { getCertMark: getCertMark2 } = await Promise.resolve().then(() => (init_manifest(), exports_manifest));
31311
31951
  for (const m of models) {
@@ -31777,6 +32417,7 @@ init_bootstrap();
31777
32417
  init_config2();
31778
32418
  init_setup();
31779
32419
  init_i18n();
32420
+ init_colors();
31780
32421
  import { join as join46 } from "path";
31781
32422
  import { homedir as homedir16 } from "os";
31782
32423
 
@@ -32413,7 +33054,7 @@ function createPluginCommand(program2) {
32413
33054
  }
32414
33055
 
32415
33056
  // src/cli/commands.ts
32416
- init_setup();
33057
+ init_presets();
32417
33058
  init_version();
32418
33059
  var version = readMmaVersion();
32419
33060
  function createProgram() {
@@ -32496,8 +33137,8 @@ function createProgram() {
32496
33137
  model.command("list").description(t("cli.list_models")).action(async () => {
32497
33138
  const { config } = await bootstrap();
32498
33139
  console.log(t("cli.current_model"), config.model);
32499
- const { OpenAICompatProvider: OpenAICompatProvider2 } = await Promise.resolve().then(() => (init_openai_compat(), exports_openai_compat));
32500
- const provider2 = new OpenAICompatProvider2({
33140
+ const { createProvider: createProvider2 } = await Promise.resolve().then(() => (init_create(), exports_create));
33141
+ const provider2 = createProvider2(config.provider.type, {
32501
33142
  model: config.model,
32502
33143
  baseUrl: config.provider.baseUrl,
32503
33144
  apiKey: config.provider.apiKey,
@@ -32576,14 +33217,51 @@ function createProgram() {
32576
33217
  console.log(t("cli.context_set", { size: contextWindow }));
32577
33218
  });
32578
33219
  const provider = program2.command("provider").description(t("cli.manage_providers"));
33220
+ provider.command("check").description(t("cli.provider_check")).option("--timeout <ms>", "Per-provider probe timeout in ms").action(async (opts) => {
33221
+ const { config, agent } = await bootstrap();
33222
+ const providers = agent.listProviders();
33223
+ const targets = targetsFromProviders(providers.length > 0 ? providers.map((p) => ({ name: p.label, provider: agent.getProviderFor(p.label) })).filter((x) => Boolean(x.provider)) : [
33224
+ {
33225
+ name: config.provider.type,
33226
+ provider: agent.getProviderFor(config.provider.active || config.provider.type)
33227
+ }
33228
+ ].filter((x) => Boolean(x.provider)));
33229
+ const results = await probeProviders(targets, {
33230
+ timeoutMs: opts.timeout ? Number(opts.timeout) : 5000
33231
+ });
33232
+ for (const r of results) {
33233
+ const mark = r.ok ? pc2.green("✓") : pc2.red("✗");
33234
+ const detail = r.ok ? pc2.dim(`${r.ms}ms, ${r.models} models`) : pc2.red(r.error ?? "failed");
33235
+ console.log(` ${mark} ${r.name.padEnd(16)} ${detail}`);
33236
+ }
33237
+ });
32579
33238
  provider.command("list").description(t("cli.list_providers")).action(async () => {
32580
- const { config } = await bootstrap();
33239
+ const { config, agent } = await bootstrap();
33240
+ const providers = agent.listProviders();
33241
+ if (providers.length > 0) {
33242
+ console.log(t("cli.current_provider"));
33243
+ for (const p of providers) {
33244
+ const marker = p.active ? pc2.green("* ") : " ";
33245
+ const prio = p.priority !== undefined ? ` ${pc2.dim(`prio=${p.priority}`)}` : "";
33246
+ console.log(` ${marker}${p.label} (${pc2.dim(p.type)}) ${pc2.dim(p.baseUrl)}${prio}`);
33247
+ }
33248
+ return;
33249
+ }
32581
33250
  console.log(t("cli.current_provider"), config.provider.type);
32582
33251
  console.log(t("cli.base_url"), config.provider.baseUrl);
32583
33252
  });
32584
33253
  provider.command("use").argument("<name>", "Provider name").description(t("cli.set_provider")).action(async (name) => {
32585
33254
  const configPath = join46(homedir16(), ".mma", "config.json");
32586
- const { config } = await bootstrap();
33255
+ const { config, agent } = await bootstrap();
33256
+ if (config.provider.entries && config.provider.entries.length > 0) {
33257
+ try {
33258
+ await agent.setProvider(name);
33259
+ console.log(t("cli.provider_set", { name }));
33260
+ } catch (e) {
33261
+ console.log(pc2.red(e.message));
33262
+ }
33263
+ return;
33264
+ }
32587
33265
  config.provider.type = name;
32588
33266
  const baseUrl = HOSTED_BASE_URLS[name];
32589
33267
  if (baseUrl) {
@@ -32595,6 +33273,43 @@ function createProgram() {
32595
33273
  console.log(t("cli.provider_base_hint", { baseUrl }));
32596
33274
  }
32597
33275
  });
33276
+ provider.command("add").argument("<name>", "Provider type or label").option("--url <url>", "Base URL").option("--key <key>", "API key").option("--priority <n>", "Fallback priority (lower = tried first)").option("--context-window <n>", "Context window override for this entry").option("--rpm <n>", "Max requests per minute for this entry").option("--parallel <n>", "Max parallel tasks for this entry").description(t("cli.add_provider")).action(async (name, opts) => {
33277
+ const configPath = join46(homedir16(), ".mma", "config.json");
33278
+ const { config } = await bootstrap();
33279
+ const entries = Array.isArray(config.provider.entries) ? config.provider.entries : [];
33280
+ if (entries.length === 0) {
33281
+ entries.push({
33282
+ type: config.provider.type,
33283
+ label: config.provider.type,
33284
+ baseUrl: config.provider.baseUrl,
33285
+ apiKey: config.provider.apiKey
33286
+ });
33287
+ }
33288
+ const baseUrl = opts.url || HOSTED_BASE_URLS[name] || HOSTED_BASE_URLS[`opencode-${name}`] || "";
33289
+ if (!baseUrl) {
33290
+ console.log(pc2.red(t("cli.provider_no_url", { name })));
33291
+ return;
33292
+ }
33293
+ entries.push({
33294
+ type: name,
33295
+ label: name,
33296
+ baseUrl,
33297
+ apiKey: opts.key,
33298
+ ...opts.priority !== undefined ? { priority: Number(opts.priority) } : {},
33299
+ ...opts.contextWindow !== undefined ? { contextWindow: Number(opts.contextWindow) } : {},
33300
+ ...opts.rpm !== undefined || opts.parallel !== undefined ? {
33301
+ rateLimits: {
33302
+ maxRequestsPerMinute: Number(opts.rpm ?? 0) || 60,
33303
+ maxParallelTasks: Number(opts.parallel ?? 0) || 1
33304
+ }
33305
+ } : {}
33306
+ });
33307
+ config.provider.entries = entries;
33308
+ config.provider.active = config.provider.active || config.provider.type;
33309
+ saveConfig(config, configPath);
33310
+ console.log(pc2.green(t("cli.provider_added", { name })));
33311
+ console.log(t("cli.provider_switch_hint"));
33312
+ });
32598
33313
  const session2 = program2.command("session").description(t("cli.manage_sessions"));
32599
33314
  session2.command("list").description(t("cli.list_sessions")).action(async () => {
32600
33315
  const { sessionManager } = await bootstrap();
@@ -33431,6 +34146,34 @@ class SubcommandProvider {
33431
34146
  return this.subcommands.filter((s) => s.startsWith(ctx.partial));
33432
34147
  }
33433
34148
  }
34149
+
34150
+ class ModelArgProvider {
34151
+ name = "model-args";
34152
+ getModels;
34153
+ constructor(getModels) {
34154
+ this.getModels = getModels;
34155
+ }
34156
+ match(ctx) {
34157
+ return ctx.tokenIndex === 2 && ctx.tokens[0] === "/model" && ctx.tokens[1] === "use";
34158
+ }
34159
+ complete(ctx) {
34160
+ return this.getModels().filter((m) => m.startsWith(ctx.partial));
34161
+ }
34162
+ }
34163
+ class ProviderArgProvider {
34164
+ name = "provider-args";
34165
+ providers;
34166
+ constructor(providers) {
34167
+ this.providers = providers;
34168
+ }
34169
+ match(ctx) {
34170
+ return ctx.tokenIndex === 2 && ctx.tokens[0] === "/provider" && ctx.tokens[1] === "use";
34171
+ }
34172
+ complete(ctx) {
34173
+ return this.providers.filter((p) => p.startsWith(ctx.partial));
34174
+ }
34175
+ }
34176
+
33434
34177
  class SessionNameProvider {
33435
34178
  name = "session-names";
33436
34179
  getNames;
@@ -33697,6 +34440,9 @@ class FormattingStream {
33697
34440
  return result;
33698
34441
  }
33699
34442
  }
34443
+ function formatWarning(text) {
34444
+ return pc2.yellow(pc2.bold(t("ui.warning_prefix")) + text);
34445
+ }
33700
34446
 
33701
34447
  // src/ui/renderer.ts
33702
34448
  init_spinner();
@@ -33843,8 +34589,9 @@ ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}
33843
34589
  `);
33844
34590
  }
33845
34591
  }
33846
- toolEnd(_tool, duration, error, ctxDelta, costUsd) {
34592
+ toolEnd(_tool, duration, error, ctxDelta, costUsd, provider, model) {
33847
34593
  this.spinner.stop();
34594
+ const prov = provider && model ? `${pc2.dim(`${provider}·${model}`)}` : undefined;
33848
34595
  if (!this.rich) {
33849
34596
  const parts = [];
33850
34597
  if (ctxDelta !== undefined && ctxDelta !== 0) {
@@ -33854,6 +34601,8 @@ ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}
33854
34601
  if (costUsd !== undefined && costUsd > 0) {
33855
34602
  parts.push(`${pc2.dim("cost")} ${pc2.yellow(formatUsd(costUsd))}`);
33856
34603
  }
34604
+ if (prov)
34605
+ parts.push(prov);
33857
34606
  if (parts.length > 0)
33858
34607
  this.out.write(`${parts.join(" ")}
33859
34608
  `);
@@ -33871,6 +34620,9 @@ ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}
33871
34620
  if (costUsd !== undefined && costUsd > 0) {
33872
34621
  footer += ` ${pc2.dim("cost")} ${pc2.yellow(formatUsd(costUsd))}`;
33873
34622
  }
34623
+ if (prov) {
34624
+ footer += ` ${pc2.dim("via")} ${prov}`;
34625
+ }
33874
34626
  if (this.toolStyle === "inline") {
33875
34627
  this.out.write(`${GUTTER}${footer}
33876
34628
  `);
@@ -33961,7 +34713,7 @@ async function probeLspServers(config, baseDir, deps = {}) {
33961
34713
  const ok = [];
33962
34714
  const failed = [];
33963
34715
  const deadline = Date.now() + deadlineMs;
33964
- const probeOne = async (language, server) => {
34716
+ const probeOne2 = async (language, server) => {
33965
34717
  const projectRoot = findProjectRoot(baseDir, baseDir, server.workspaceMarkers ?? []);
33966
34718
  const serverTimeout = Math.min(server.timeout ?? timeoutCap, timeoutCap);
33967
34719
  const started = Date.now();
@@ -33996,7 +34748,7 @@ async function probeLspServers(config, baseDir, deps = {}) {
33996
34748
  break;
33997
34749
  }
33998
34750
  const wave = servers.slice(i, i + maxParallel);
33999
- await Promise.all(wave.map(({ language, config: server }) => probeOne(language, server)));
34751
+ await Promise.all(wave.map(({ language, config: server }) => probeOne2(language, server)));
34000
34752
  }
34001
34753
  return { ok, failed, noneApplicable: false };
34002
34754
  }
@@ -34086,6 +34838,14 @@ function stepContextForTool(plan, tool, args) {
34086
34838
 
34087
34839
  // src/cli/repl.ts
34088
34840
  init_prices();
34841
+
34842
+ // src/ui/output.ts
34843
+ function writeWarning(text) {
34844
+ process.stdout.write(formatWarning(text) + `
34845
+ `);
34846
+ }
34847
+
34848
+ // src/cli/repl.ts
34089
34849
  function formatContextBar(used, limit, compactions, quality) {
34090
34850
  const pct = Math.min(100, Math.round(used / limit * 100));
34091
34851
  const barLen = 20;
@@ -34105,6 +34865,7 @@ function formatContextBar(used, limit, compactions, quality) {
34105
34865
 
34106
34866
  class Repl {
34107
34867
  completer = new Completer;
34868
+ modelCache = [];
34108
34869
  running = false;
34109
34870
  agentRunning = false;
34110
34871
  inputLocked = false;
@@ -34200,6 +34961,28 @@ class Repl {
34200
34961
  this.completer.registerProvider(new SubcommandProvider("skill", ["list", "loaded", "load", "unload", "search"]));
34201
34962
  this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
34202
34963
  }
34964
+ this.completer.registerProvider(new SubcommandProvider("provider", ["list", "use"]));
34965
+ this.completer.registerProvider(new SubcommandProvider("model", ["list", "use"]));
34966
+ const entryLabels = this.config.provider?.entries?.map((e) => e.label || e.type) ?? [];
34967
+ if (entryLabels.length > 0) {
34968
+ this.completer.registerProvider(new ProviderArgProvider(entryLabels));
34969
+ }
34970
+ this.completer.registerProvider(new ModelArgProvider(() => this.modelCache));
34971
+ this.refreshModelCache();
34972
+ }
34973
+ refreshModelCache() {
34974
+ (async () => {
34975
+ try {
34976
+ const name = this.config.provider.active || this.config.provider.entries?.[0]?.label || this.config.provider.type;
34977
+ const provider = this.agent.getProviderFor(name);
34978
+ if (!provider)
34979
+ return;
34980
+ const models = await provider.listModels();
34981
+ if (models.length > 0) {
34982
+ this.modelCache = models.sort();
34983
+ }
34984
+ } catch {}
34985
+ })();
34203
34986
  }
34204
34987
  setupListeners() {
34205
34988
  let multiLineBuffer = "";
@@ -34383,7 +35166,7 @@ ${t("image.clipboard_empty")}`));
34383
35166
  if (ev.type === "start") {
34384
35167
  renderer.toolStart(ev.tool, ev.args, stepContextForTool(this.execModule?.getActivePlan() ?? null, ev.tool, ev.args), ev.icon);
34385
35168
  } else {
34386
- renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta, ev.costUsd);
35169
+ renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta, ev.costUsd, ev.provider, ev.model);
34387
35170
  if (ev.tool === "plan" || ev.tool === "todo") {
34388
35171
  this.renderPlan(renderer);
34389
35172
  }
@@ -34582,6 +35365,18 @@ ${t("image.clipboard_empty")}`));
34582
35365
  this.updateLspRow(headerWidth, lspSummary);
34583
35366
  }).catch(() => {});
34584
35367
  }
35368
+ if ((this.config.provider.entries?.length ?? 0) > 1) {
35369
+ probeProviders(targetsFromProviders((this.config.provider.entries ?? []).map((e) => ({
35370
+ name: e.label || e.type,
35371
+ provider: this.agent.getProviderFor(e.label || e.type)
35372
+ })).filter((x) => Boolean(x.provider))), { timeoutMs: 5000 }).then((results) => {
35373
+ for (const r of results) {
35374
+ if (!r.ok && r.name !== this.config.provider.active) {
35375
+ writeWarning(t("repl.provider_down", { name: r.name, error: r.error ?? "" }));
35376
+ }
35377
+ }
35378
+ }).catch(() => {});
35379
+ }
34585
35380
  }
34586
35381
  updateLspRow(headerWidth, summary) {
34587
35382
  if (this.agentRunning || !process.stdout.isTTY)
@@ -34644,6 +35439,10 @@ function printRunResult(result, flush) {
34644
35439
  if (result.success) {
34645
35440
  if (result.totalCost !== undefined && result.totalCost > 0) {
34646
35441
  console.log(pc2.dim(`${t("repl.cost", { cost: formatCost(result.totalCost) })}`));
35442
+ if (result.costBreakdown && result.costBreakdown.length > 1) {
35443
+ const parts = result.costBreakdown.map((e) => `${e.provider} ${formatCost(e.cost)}`);
35444
+ console.log(pc2.dim(` ${t("repl.cost_breakdown", { breakdown: parts.join(", ") })}`));
35445
+ }
34647
35446
  } else if (result.totalTokens !== undefined && result.totalTokens > 0) {
34648
35447
  console.log(pc2.dim(`${t("repl.tokens", { tokens: result.totalTokens })}`));
34649
35448
  }
@@ -34858,7 +35657,8 @@ async function main() {
34858
35657
  promptTokens: result2.promptTokens ?? null,
34859
35658
  completionTokens: result2.completionTokens ?? null,
34860
35659
  totalTokens: result2.totalTokens ?? null,
34861
- totalCost: result2.totalCost ?? null
35660
+ totalCost: result2.totalCost ?? null,
35661
+ costBreakdown: result2.costBreakdown ?? []
34862
35662
  }, null, 2));
34863
35663
  process.stdout.write(`
34864
35664
  `);