micro-models-agent 0.62.0 → 0.63.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/i18n/en.json +4 -1
- package/dist/i18n/ru.json +4 -1
- package/dist/main.js +400 -211
- package/dist/modules/browser/bridge-server.mjs +37 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@ All notable changes to Micro Models Agent (MMA) will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.63.3] - 2026-09-17
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Plan-alignment false positives hard-blocked legitimate writes**: `checkPlanAlignment` (`src/modules/execution/module.ts`) could accumulate 3 "outside the current step" warnings and block a tool call the agent needed for the current step (observed in `ses_mu5wed4r`: the current step's own `src/main.ts` was blocked). Causes fixed:
|
|
11
|
+
- **Path separators were not normalized** (root cause): model tool calls pass absolute Windows paths (`C:\…\src\main.ts`) while plan steps name files relatively (`src/main.ts`); the raw substring comparison never intersected backslash vs forward slash, so *every* write was treated as off-path. Both sides now go through `planPathToken` (forward slashes + lowercase) before matching.
|
|
12
|
+
- The completed-step rewrite exemption compared paths one-directionally (`finishedPaths.some((s) => s.includes(p))`), so a done step naming `main.ts` did not exempt a rewrite of `src/main.ts` — the overlap check is now bidirectional, mirroring the current-step comparison.
|
|
13
|
+
- Read-only diagnostic/structural tools were not exempt: `lsp_check` (mandated by the system prompt after every write) and `project_map` now join the allow-list (`PLAN_ALIGNMENT_EXEMPT_TOOLS`), alongside the other filesystem-reads, web/memory/session reads, process introspection and interactive prompts. Only file-mutating tools are pinned to the current step's file tokens.
|
|
14
|
+
- **Plugin-blocked calls were treated as real tool failures**: a guard refusal (`onBeforeTool` → `false`/string) is now flagged `blocked` on `ToolResult` and propagated to the context message (`src/tools/executor.ts`, `src/core/agent/tool-batch.ts`, `src/modules/context/manager.ts`). Blocked calls no longer feed the compaction "Already tried & failed — do NOT repeat" memory, the per-tool repeated-failure rule, or the consecutive-failure recovery counter — so a transient guard (or a guard bug) cannot convince the agent to never retry a correct action.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Browser launch overrides**: `MMA_BROWSER_EXECUTABLE` (absolute path) and `MMA_BROWSER_CHANNEL` (`chrome`/`msedge`/`chromium`) let the browser tool use an installed system browser instead of Playwright's pinned Chromium. When the bundled revision is missing (offline / blocked CDN) it now falls back to `chrome`, then `msedge`, automatically (`src/modules/browser/driver.ts`, `bridge-server.mjs`).
|
|
18
|
+
|
|
19
|
+
## [0.63.2] - 2026-09-17
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Long answers were silently cut by the completion cap**: `max_tokens` (default 4096) is sent on every request; when the model hit it while already streaming text, `finish_reason: "length"` was ignored and the truncated text was accepted as the final answer. The provider now emits a non-fatal `warning` chunk (`src/llm/openai-compat.ts`, streaming and non-streaming paths) that the agent renders in the chat — the answer still arrives, but the user sees it was cut off. Tool-call/empty-response truncation keeps the existing recoverable-error path.
|
|
23
|
+
- **Resumed sessions lost their history**: `saveAssistantMessage` and `logToolResult` (`src/core/session-logger.ts`) persisted only the first 500 chars of each assistant/tool message, so `/resume` seeded the model with truncated context. Full content is stored now; the context budget still trims what actually goes to the LLM.
|
|
24
|
+
|
|
25
|
+
## [0.63.1] - 2026-09-17
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **`ui.verbose`**: opt-in chatty mode for internal orchestration status. MoE planning / re-plan / execution / scope one-liners (`src/core/agent-moe.ts`) are now behind this flag (default `false`) instead of always being written to the chat.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- **Failed tool output is no longer echoed verbatim**: the chat used to print the full model-facing `result.output` of any tool without a `display` — including `Hint:` / recovery directives / "Do NOT …" instructions written for the agent. Only the first non-empty line (the gist of the error) is shown now, in red (`compactToolError`, `src/core/agent/tool-output.ts`).
|
|
32
|
+
- **Live busy spinners for tool runs**: the renderer animates while `read_file` / `write_file` / `edit_file` / `bash` / … execute and restarts the spinner on every LLM "thinking" phase. Previously the spinner stopped at the first tool call and the rest of the run looked frozen. Interactive tools (`question`, `approve`) never spin; dead `BUSY_TOOLS` set removed.
|
|
33
|
+
|
|
34
|
+
## [0.63.0] - 2026-09-17
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- **REPL `/provider add`**: add a provider without leaving the agent. Mirrors `mma provider add` — `/provider add <name> [--url <url>] [--key <key>] [--priority <n>] [--context-window <n>] [--rpm <n>] [--parallel <n>]`. Seeds the legacy provider into `provider.entries` on first use, saves the config and hot-reloads the agent; switch with `/provider use <name>`. `/provider` autocompletion now includes `add`; i18n `repl.provider_add_missing_value`, `repl.provider_switch_hint` (en+ru).
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- **Subcommand dispatch via handler maps** (new architecture rule #17): `/provider`, `/model` and `/skill` (`src/cli/repl-commands.ts`) and `SkillNameProvider.complete` (`src/cli/completer.ts`) now resolve subcommands through a `Record<string, handler>` map instead of `if (subcmd === …)` chains.
|
|
41
|
+
|
|
7
42
|
## [0.62.0] - 2026-09-17
|
|
8
43
|
|
|
9
44
|
### Added
|
package/dist/i18n/en.json
CHANGED
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"error.llm_stream_idle_toolcall": "LLM stream stalled after a tool_call started — no data for {timeout}ms. The provider most likely buffers SSE instead of streaming tool-call argument deltas (seen with LM Studio). Raise \"retry.noDataTimeoutMs\" in ~/.mma/config.json (restart required)",
|
|
45
45
|
"error.llm_truncated": "Response hit the completion token limit ({tokens}) and was cut off before any content arrived. Split the task into smaller outputs or raise \"maxCompletionTokens\" in the config",
|
|
46
46
|
"error.llm_truncated_toolcall": "Response hit the completion token limit ({tokens}) in the middle of a tool_call — its arguments were cut off. Write the file in smaller chunks (several write_file/edit_file calls) or raise \"maxCompletionTokens\" in the config",
|
|
47
|
+
"error.llm_output_truncated": "The response hit the completion token limit ({tokens}) and was cut off — this answer is incomplete. Raise \"maxCompletionTokens\" in the config to allow longer replies",
|
|
47
48
|
"error.llm_provider_stream_error": "Provider returned an error mid-stream: {error}",
|
|
48
49
|
"error.llm_timeout": "LLM request timed out ({timeout}ms)",
|
|
49
50
|
"env.runtime_node": "Running under Node (v{version}) — clipboard image paste, subagent performance and LSP spawn on Windows degrade. Install Bun (https://bun.sh) for full features.",
|
|
@@ -494,7 +495,9 @@
|
|
|
494
495
|
"repl.provider_list": "List configured providers or hot-swap the active one",
|
|
495
496
|
"repl.provider_current": "Current provider",
|
|
496
497
|
"repl.provider_set": "Provider set to: {name}",
|
|
497
|
-
"repl.provider_usage": "/provider list - show entries (* = active, prio = fallback order) | /provider use <name> - switch without restart",
|
|
498
|
+
"repl.provider_usage": "/provider list - show entries (* = active, prio = fallback order) | /provider use <name> - switch without restart | /provider add <name> [--url <url>] [--key <key>] [--priority <n>] [--context-window <n>] [--rpm <n>] [--parallel <n>] - add to config",
|
|
499
|
+
"repl.provider_add_missing_value": "Missing value for {flag}",
|
|
500
|
+
"repl.provider_switch_hint": "Switch to it with: /provider use {name}",
|
|
498
501
|
"repl.model_list": "List models of the active provider or switch the model",
|
|
499
502
|
"repl.model_current": "Current model",
|
|
500
503
|
"repl.model_set": "Model set to: {name}",
|
package/dist/i18n/ru.json
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"error.llm_stream_idle_toolcall": "Поток LLM завис после начала tool_call — нет данных {timeout}мс. Скорее всего провайдер буферизирует SSE вместо потоковой передачи аргументов tool_call (наблюдается в LM Studio). Увеличьте \"retry.noDataTimeoutMs\" в ~/.mma/config.json (нужен рестарт)",
|
|
44
44
|
"error.llm_truncated": "Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан до какого-либо содержимого. Разбейте задачу на меньшие порции вывода или увеличьте \"maxCompletionTokens\" в конфиге",
|
|
45
45
|
"error.llm_truncated_toolcall": "Ответ упёрся в лимит токенов генерации ({tokens}) посреди tool_call — его аргументы обрезаны. Пишите файл частями (несколько вызовов write_file/edit_file) или увеличьте \"maxCompletionTokens\" в конфиге",
|
|
46
|
+
"error.llm_output_truncated": "Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан — этот ответ неполный. Увеличьте \"maxCompletionTokens\" в конфиге, чтобы разрешить длинные ответы",
|
|
46
47
|
"error.llm_provider_stream_error": "Провайдер вернул ошибку посреди стрима: {error}",
|
|
47
48
|
"error.llm_timeout": "Время запроса LLM истекло ({timeout}мс)",
|
|
48
49
|
"env.runtime_node": "Запущено под Node (v{version}) — вставка изображений из буфера и LSP на Windows работают урезанно. Установите Bun (https://bun.sh) для полного функционала.",
|
|
@@ -488,7 +489,9 @@
|
|
|
488
489
|
"repl.provider_list": "Список настроенных провайдеров или горячая смена активного",
|
|
489
490
|
"repl.provider_current": "Текущий провайдер",
|
|
490
491
|
"repl.provider_set": "Провайдер установлен: {name}",
|
|
491
|
-
"repl.provider_usage": "/provider list — записи (* = активный, prio = порядок fallback) | /provider use <имя> — смена без перезапуска",
|
|
492
|
+
"repl.provider_usage": "/provider list — записи (* = активный, prio = порядок fallback) | /provider use <имя> — смена без перезапуска | /provider add <имя> [--url <url>] [--key <ключ>] [--priority <n>] [--context-window <n>] [--rpm <n>] [--parallel <n>] — добавить в конфиг",
|
|
493
|
+
"repl.provider_add_missing_value": "Нет значения для {flag}",
|
|
494
|
+
"repl.provider_switch_hint": "Переключитесь на него: /provider use {name}",
|
|
492
495
|
"repl.model_list": "Список моделей активного провайдера или смена модели",
|
|
493
496
|
"repl.model_current": "Текущая модель",
|
|
494
497
|
"repl.model_set": "Модель установлена: {name}",
|
package/dist/main.js
CHANGED
|
@@ -2373,7 +2373,8 @@ var init_defaults = __esm(() => {
|
|
|
2373
2373
|
ui: {
|
|
2374
2374
|
spinner: true,
|
|
2375
2375
|
showContextStats: false,
|
|
2376
|
-
showCompaction: true
|
|
2376
|
+
showCompaction: true,
|
|
2377
|
+
verbose: false
|
|
2377
2378
|
},
|
|
2378
2379
|
mcpServers: {
|
|
2379
2380
|
context7: {
|
|
@@ -2484,6 +2485,7 @@ The path was joined onto the working directory because it does not exist as give
|
|
|
2484
2485
|
"error.llm_stream_idle_toolcall": 'LLM stream stalled after a tool_call started — no data for {timeout}ms. The provider most likely buffers SSE instead of streaming tool-call argument deltas (seen with LM Studio). Raise "retry.noDataTimeoutMs" in ~/.mma/config.json (restart required)',
|
|
2485
2486
|
"error.llm_truncated": 'Response hit the completion token limit ({tokens}) and was cut off before any content arrived. Split the task into smaller outputs or raise "maxCompletionTokens" in the config',
|
|
2486
2487
|
"error.llm_truncated_toolcall": 'Response hit the completion token limit ({tokens}) in the middle of a tool_call — its arguments were cut off. Write the file in smaller chunks (several write_file/edit_file calls) or raise "maxCompletionTokens" in the config',
|
|
2488
|
+
"error.llm_output_truncated": 'The response hit the completion token limit ({tokens}) and was cut off — this answer is incomplete. Raise "maxCompletionTokens" in the config to allow longer replies',
|
|
2487
2489
|
"error.llm_provider_stream_error": "Provider returned an error mid-stream: {error}",
|
|
2488
2490
|
"error.llm_timeout": "LLM request timed out ({timeout}ms)",
|
|
2489
2491
|
"env.runtime_node": "Running under Node (v{version}) — clipboard image paste, subagent performance and LSP spawn on Windows degrade. Install Bun (https://bun.sh) for full features.",
|
|
@@ -2945,7 +2947,9 @@ Excluded blocks: {count}`,
|
|
|
2945
2947
|
"repl.provider_list": "List configured providers or hot-swap the active one",
|
|
2946
2948
|
"repl.provider_current": "Current provider",
|
|
2947
2949
|
"repl.provider_set": "Provider set to: {name}",
|
|
2948
|
-
"repl.provider_usage": "/provider list - show entries (* = active, prio = fallback order) | /provider use <name> - switch without restart",
|
|
2950
|
+
"repl.provider_usage": "/provider list - show entries (* = active, prio = fallback order) | /provider use <name> - switch without restart | /provider add <name> [--url <url>] [--key <key>] [--priority <n>] [--context-window <n>] [--rpm <n>] [--parallel <n>] - add to config",
|
|
2951
|
+
"repl.provider_add_missing_value": "Missing value for {flag}",
|
|
2952
|
+
"repl.provider_switch_hint": "Switch to it with: /provider use {name}",
|
|
2949
2953
|
"repl.model_list": "List models of the active provider or switch the model",
|
|
2950
2954
|
"repl.model_current": "Current model",
|
|
2951
2955
|
"repl.model_set": "Model set to: {name}",
|
|
@@ -3318,6 +3322,7 @@ var init_ru = __esm(() => {
|
|
|
3318
3322
|
"error.llm_stream_idle_toolcall": 'Поток LLM завис после начала tool_call — нет данных {timeout}мс. Скорее всего провайдер буферизирует SSE вместо потоковой передачи аргументов tool_call (наблюдается в LM Studio). Увеличьте "retry.noDataTimeoutMs" в ~/.mma/config.json (нужен рестарт)',
|
|
3319
3323
|
"error.llm_truncated": 'Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан до какого-либо содержимого. Разбейте задачу на меньшие порции вывода или увеличьте "maxCompletionTokens" в конфиге',
|
|
3320
3324
|
"error.llm_truncated_toolcall": 'Ответ упёрся в лимит токенов генерации ({tokens}) посреди tool_call — его аргументы обрезаны. Пишите файл частями (несколько вызовов write_file/edit_file) или увеличьте "maxCompletionTokens" в конфиге',
|
|
3325
|
+
"error.llm_output_truncated": 'Ответ упёрся в лимит токенов генерации ({tokens}) и был обрезан — этот ответ неполный. Увеличьте "maxCompletionTokens" в конфиге, чтобы разрешить длинные ответы',
|
|
3321
3326
|
"error.llm_provider_stream_error": "Провайдер вернул ошибку посреди стрима: {error}",
|
|
3322
3327
|
"error.llm_timeout": "Время запроса LLM истекло ({timeout}мс)",
|
|
3323
3328
|
"env.runtime_node": "Запущено под Node (v{version}) — вставка изображений из буфера и LSP на Windows работают урезанно. Установите Bun (https://bun.sh) для полного функционала.",
|
|
@@ -3774,7 +3779,9 @@ var init_ru = __esm(() => {
|
|
|
3774
3779
|
"repl.provider_list": "Список настроенных провайдеров или горячая смена активного",
|
|
3775
3780
|
"repl.provider_current": "Текущий провайдер",
|
|
3776
3781
|
"repl.provider_set": "Провайдер установлен: {name}",
|
|
3777
|
-
"repl.provider_usage": "/provider list — записи (* = активный, prio = порядок fallback) | /provider use <имя> — смена без перезапуска",
|
|
3782
|
+
"repl.provider_usage": "/provider list — записи (* = активный, prio = порядок fallback) | /provider use <имя> — смена без перезапуска | /provider add <имя> [--url <url>] [--key <ключ>] [--priority <n>] [--context-window <n>] [--rpm <n>] [--parallel <n>] — добавить в конфиг",
|
|
3783
|
+
"repl.provider_add_missing_value": "Нет значения для {flag}",
|
|
3784
|
+
"repl.provider_switch_hint": "Переключитесь на него: /provider use {name}",
|
|
3778
3785
|
"repl.model_list": "Список моделей активного провайдера или смена модели",
|
|
3779
3786
|
"repl.model_current": "Текущая модель",
|
|
3780
3787
|
"repl.model_set": "Модель установлена: {name}",
|
|
@@ -6357,6 +6364,10 @@ class OpenAICompatProvider {
|
|
|
6357
6364
|
tokens: maxTokens
|
|
6358
6365
|
}), { terminal: true, recoverable: true });
|
|
6359
6366
|
}
|
|
6367
|
+
yield {
|
|
6368
|
+
type: "warning",
|
|
6369
|
+
content: t("error.llm_output_truncated", { tokens: maxTokens })
|
|
6370
|
+
};
|
|
6360
6371
|
}
|
|
6361
6372
|
} finally {
|
|
6362
6373
|
cleanup();
|
|
@@ -6416,12 +6427,13 @@ class OpenAICompatProvider {
|
|
|
6416
6427
|
};
|
|
6417
6428
|
}
|
|
6418
6429
|
async doNonStreaming(messages, tools, signal, options) {
|
|
6430
|
+
const maxTokens = options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096;
|
|
6419
6431
|
const body = buildRequestBody({
|
|
6420
6432
|
model: this.model,
|
|
6421
6433
|
messages,
|
|
6422
6434
|
tools,
|
|
6423
6435
|
stream: false,
|
|
6424
|
-
maxTokens
|
|
6436
|
+
maxTokens,
|
|
6425
6437
|
reasoningEffort: options?.reasoningEffort,
|
|
6426
6438
|
reasoningStrategy: options?.reasoningStrategy,
|
|
6427
6439
|
...this.cacheHints()
|
|
@@ -6471,6 +6483,12 @@ class OpenAICompatProvider {
|
|
|
6471
6483
|
});
|
|
6472
6484
|
}
|
|
6473
6485
|
}
|
|
6486
|
+
if (choice.finish_reason === "length" && msg.content && !msg.tool_calls) {
|
|
6487
|
+
chunks.push({
|
|
6488
|
+
type: "warning",
|
|
6489
|
+
content: t("error.llm_output_truncated", { tokens: maxTokens })
|
|
6490
|
+
});
|
|
6491
|
+
}
|
|
6474
6492
|
if (data.usage) {
|
|
6475
6493
|
chunks.push({
|
|
6476
6494
|
type: "done",
|
|
@@ -7281,6 +7299,7 @@ class ToolExecutor {
|
|
|
7281
7299
|
const reason = typeof proceed === "string" ? proceed : undefined;
|
|
7282
7300
|
return {
|
|
7283
7301
|
success: false,
|
|
7302
|
+
blocked: true,
|
|
7284
7303
|
output: reason ? t("tool.blocked_reason", { plugin: pluginName, reason }) : t("tool.blocked", { plugin: pluginName }),
|
|
7285
7304
|
toolCallId: call.id
|
|
7286
7305
|
};
|
|
@@ -10415,7 +10434,7 @@ class SessionLogger {
|
|
|
10415
10434
|
const caller = this.getCaller?.();
|
|
10416
10435
|
this.session?.appendMessage({
|
|
10417
10436
|
role: "assistant",
|
|
10418
|
-
content
|
|
10437
|
+
content,
|
|
10419
10438
|
timestamp: new Date().toISOString(),
|
|
10420
10439
|
provider: caller?.provider,
|
|
10421
10440
|
model: caller?.model
|
|
@@ -10444,7 +10463,7 @@ class SessionLogger {
|
|
|
10444
10463
|
const caller = this.getCaller?.();
|
|
10445
10464
|
this.session?.appendMessage({
|
|
10446
10465
|
role: "tool",
|
|
10447
|
-
content: sanitizeLogMessage(result.output
|
|
10466
|
+
content: sanitizeLogMessage(result.output),
|
|
10448
10467
|
name: call.name,
|
|
10449
10468
|
timestamp: new Date().toISOString(),
|
|
10450
10469
|
provider: caller?.provider,
|
|
@@ -13888,6 +13907,11 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13888
13907
|
if (signal?.aborted)
|
|
13889
13908
|
throw newAbortError();
|
|
13890
13909
|
};
|
|
13910
|
+
const verbose = config.ui?.verbose === true;
|
|
13911
|
+
const status = (message) => {
|
|
13912
|
+
if (verbose)
|
|
13913
|
+
onMeta?.(message);
|
|
13914
|
+
};
|
|
13891
13915
|
ensureNotAborted();
|
|
13892
13916
|
let orchestrator = deps.orchestratorOverride;
|
|
13893
13917
|
if (!orchestrator) {
|
|
@@ -13905,7 +13929,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13905
13929
|
logger.warn("MoE enabled but no orchestrator model configured — falling back to single-agent. Set orchestrator.model to activate MoE.");
|
|
13906
13930
|
return fallback();
|
|
13907
13931
|
}
|
|
13908
|
-
|
|
13932
|
+
status(`\uD83E\uDD16 Planning with MoE mode...
|
|
13909
13933
|
`);
|
|
13910
13934
|
onPhase?.("thinking");
|
|
13911
13935
|
const planResult = await orchestrator.plan(input, undefined, signal);
|
|
@@ -13927,7 +13951,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13927
13951
|
for (let cycle = 1;cycle <= maxCycles; cycle++) {
|
|
13928
13952
|
ensureNotAborted();
|
|
13929
13953
|
if (cycle > 1) {
|
|
13930
|
-
|
|
13954
|
+
status(t("moe.replan_started", { cycle, max: maxCycles }) + `
|
|
13931
13955
|
`);
|
|
13932
13956
|
emit("moe_replan", {
|
|
13933
13957
|
cycle,
|
|
@@ -13942,7 +13966,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13942
13966
|
if (!retry.valid) {
|
|
13943
13967
|
logger.warn(`MoE plan validation failed: ${retry.errors.join("; ")}`);
|
|
13944
13968
|
if (cycle === 1) {
|
|
13945
|
-
|
|
13969
|
+
status(`⚠️ Plan validation failed. Falling back to single-agent mode.
|
|
13946
13970
|
`);
|
|
13947
13971
|
return fallback();
|
|
13948
13972
|
}
|
|
@@ -13950,7 +13974,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13950
13974
|
}
|
|
13951
13975
|
currentPlan = applied;
|
|
13952
13976
|
if (validation.autoFixes.length > 0) {
|
|
13953
|
-
|
|
13977
|
+
status(`\uD83D\uDD27 Auto-fixed ${validation.autoFixes.length} plan issues.
|
|
13954
13978
|
`);
|
|
13955
13979
|
}
|
|
13956
13980
|
}
|
|
@@ -13967,19 +13991,19 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
13967
13991
|
onScopeRequest: async (subtaskId, req) => {
|
|
13968
13992
|
const decision = await orchestrator.resolveScopeRequest(subtaskId, req, signal);
|
|
13969
13993
|
if (decision.action === "approve") {
|
|
13970
|
-
|
|
13994
|
+
status(t("moe.scope_approved", {
|
|
13971
13995
|
subtask: subtaskId,
|
|
13972
13996
|
files: [...decision.write, ...decision.read].join(", ")
|
|
13973
13997
|
}) + `
|
|
13974
13998
|
`);
|
|
13975
13999
|
} else {
|
|
13976
|
-
|
|
14000
|
+
status(t("moe.scope_rejected", { subtask: subtaskId }) + `
|
|
13977
14001
|
`);
|
|
13978
14002
|
}
|
|
13979
14003
|
return decision;
|
|
13980
14004
|
}
|
|
13981
14005
|
});
|
|
13982
|
-
|
|
14006
|
+
status(t("moe.executing", { count: String(currentPlan.subtasks.length), cycle: String(cycle) }) + `
|
|
13983
14007
|
`);
|
|
13984
14008
|
const skipIds = new Set;
|
|
13985
14009
|
const carriedResults = [];
|
|
@@ -14015,7 +14039,7 @@ async function runWithMoE(deps, input, fallback, opts = {}) {
|
|
|
14015
14039
|
mergedResults.set(r.subtaskId, r);
|
|
14016
14040
|
}
|
|
14017
14041
|
const succeeded = [...mergedResults.values()].filter((r) => r.success).length;
|
|
14018
|
-
|
|
14042
|
+
status(`✅ ${t("moe.execution_complete", { succeeded: String(succeeded), total: String(mergedResults.size) })}
|
|
14019
14043
|
`);
|
|
14020
14044
|
const verifier = deps.verifierOverride ?? new StepVerifier(baseDir);
|
|
14021
14045
|
const knownTags = collectKnownToolTags(toolExecutor);
|
|
@@ -14687,6 +14711,17 @@ function summarizeToolArgs(args) {
|
|
|
14687
14711
|
return ` (${s})`;
|
|
14688
14712
|
return ` (${s.slice(0, TOOL_ARGS_SUMMARY_MAX_CHARS)}…(${s.length} chars total))`;
|
|
14689
14713
|
}
|
|
14714
|
+
function compactToolError(output, max = 200) {
|
|
14715
|
+
const text = String(output ?? "");
|
|
14716
|
+
for (const line of text.split(`
|
|
14717
|
+
`)) {
|
|
14718
|
+
const trimmed = line.trim();
|
|
14719
|
+
if (!trimmed)
|
|
14720
|
+
continue;
|
|
14721
|
+
return trimmed.length > max ? `${trimmed.slice(0, max - 1)}…` : trimmed;
|
|
14722
|
+
}
|
|
14723
|
+
return "";
|
|
14724
|
+
}
|
|
14690
14725
|
var init_tool_output = __esm(() => {
|
|
14691
14726
|
init_i18n();
|
|
14692
14727
|
});
|
|
@@ -14762,7 +14797,7 @@ class ToolBatchExecutor {
|
|
|
14762
14797
|
if (this.deps.isShutdownRequested())
|
|
14763
14798
|
break;
|
|
14764
14799
|
const duration = Date.now() - startTime;
|
|
14765
|
-
if (!result.success && !infoTools.has(call.name))
|
|
14800
|
+
if (!result.success && !result.blocked && !infoTools.has(call.name))
|
|
14766
14801
|
anyToolFailed = true;
|
|
14767
14802
|
if (result.success && call.name === "plan") {
|
|
14768
14803
|
const action = String(call.arguments.action ?? "");
|
|
@@ -14784,10 +14819,17 @@ class ToolBatchExecutor {
|
|
|
14784
14819
|
` + result.display + `
|
|
14785
14820
|
`);
|
|
14786
14821
|
}
|
|
14787
|
-
} else {
|
|
14822
|
+
} else if (result.success) {
|
|
14788
14823
|
const metaOut = pluginManager.runOnMeta({ iteration, logger, contextManager }, result.output);
|
|
14789
14824
|
onMeta?.(`
|
|
14790
14825
|
` + pc2.dim(metaOut) + `
|
|
14826
|
+
`);
|
|
14827
|
+
} else {
|
|
14828
|
+
const line = compactToolError(result.output);
|
|
14829
|
+
const metaOut = line ? pluginManager.runOnMeta({ iteration, logger, contextManager }, line) : "";
|
|
14830
|
+
if (metaOut)
|
|
14831
|
+
onMeta?.(`
|
|
14832
|
+
` + pc2.red(metaOut) + `
|
|
14791
14833
|
`);
|
|
14792
14834
|
}
|
|
14793
14835
|
if (result.diff) {
|
|
@@ -14804,6 +14846,7 @@ class ToolBatchExecutor {
|
|
|
14804
14846
|
name: call.name,
|
|
14805
14847
|
tool_call_id: call.id,
|
|
14806
14848
|
success: result.success,
|
|
14849
|
+
blocked: result.blocked,
|
|
14807
14850
|
arguments: call.arguments
|
|
14808
14851
|
});
|
|
14809
14852
|
answeredToolCallIds.add(call.id);
|
|
@@ -14822,7 +14865,7 @@ class ToolBatchExecutor {
|
|
|
14822
14865
|
slog?.logToolResult(call, result, duration, iteration);
|
|
14823
14866
|
}
|
|
14824
14867
|
this.deps.compactionService.compactAfterTool(state);
|
|
14825
|
-
if (!result.success && !infoTools.has(call.name)) {
|
|
14868
|
+
if (!result.success && !result.blocked && !infoTools.has(call.name)) {
|
|
14826
14869
|
const key = call.name;
|
|
14827
14870
|
const prev = state.toolFailureCounts.get(key) ?? { count: 0, error: "" };
|
|
14828
14871
|
prev.count++;
|
|
@@ -15693,6 +15736,13 @@ class Agent {
|
|
|
15693
15736
|
if (chunk.type === "done" && chunk.usage) {
|
|
15694
15737
|
tokenTracker.recordApiUsage(state, baseline, chunk.usage);
|
|
15695
15738
|
}
|
|
15739
|
+
if (chunk.type === "warning" && chunk.content) {
|
|
15740
|
+
logger.warn(`LLM warning: ${chunk.content}`);
|
|
15741
|
+
onChunk?.(`
|
|
15742
|
+
|
|
15743
|
+
> ⚠️ ${chunk.content}
|
|
15744
|
+
`);
|
|
15745
|
+
}
|
|
15696
15746
|
}
|
|
15697
15747
|
} catch (err) {
|
|
15698
15748
|
if (this.shutdownRequested || err?.name === "AbortError") {
|
|
@@ -16185,6 +16235,8 @@ function extractTriedAndFailed(messages) {
|
|
|
16185
16235
|
const failures = new Map;
|
|
16186
16236
|
for (const msg of messages) {
|
|
16187
16237
|
if (msg.role === "tool" && msg.name && msg.success === false) {
|
|
16238
|
+
if (msg.blocked)
|
|
16239
|
+
continue;
|
|
16188
16240
|
const key = `${msg.name}:${summarizeArgs(msg.arguments)}`;
|
|
16189
16241
|
const existing = failures.get(key);
|
|
16190
16242
|
const errorText = truncate(typeof msg.content === "string" ? msg.content : getMessageText(msg.content), 100);
|
|
@@ -19539,6 +19591,8 @@ class BridgeDriver {
|
|
|
19539
19591
|
headless: cfg.headless,
|
|
19540
19592
|
viewport: cfg.viewport,
|
|
19541
19593
|
timeoutMs: cfg.timeoutMs,
|
|
19594
|
+
executablePath: cfg.overrides?.executablePath,
|
|
19595
|
+
channel: cfg.overrides?.channel,
|
|
19542
19596
|
maxConsoleLineChars: this.maxConsoleLineChars
|
|
19543
19597
|
});
|
|
19544
19598
|
}
|
|
@@ -19661,6 +19715,41 @@ var init_bridge_client = __esm(() => {
|
|
|
19661
19715
|
|
|
19662
19716
|
// src/modules/browser/driver.ts
|
|
19663
19717
|
import { chromium } from "playwright";
|
|
19718
|
+
function resolveBrowserLaunchOverrides(env = process.env) {
|
|
19719
|
+
const executablePath = env.MMA_BROWSER_EXECUTABLE?.trim();
|
|
19720
|
+
const channel = env.MMA_BROWSER_CHANNEL?.trim();
|
|
19721
|
+
const out = {};
|
|
19722
|
+
if (executablePath)
|
|
19723
|
+
out.executablePath = executablePath;
|
|
19724
|
+
if (channel)
|
|
19725
|
+
out.channel = channel;
|
|
19726
|
+
return out;
|
|
19727
|
+
}
|
|
19728
|
+
function isMissingBrowserError(err) {
|
|
19729
|
+
return /Executable doesn't exist|is not found|looks like Playwright was just installed/i.test(String(err?.message ?? err));
|
|
19730
|
+
}
|
|
19731
|
+
async function launchChromiumWithFallback(launch, cfg, overrides = {}) {
|
|
19732
|
+
const base = { headless: cfg.headless, timeout: cfg.timeoutMs, ...overrides };
|
|
19733
|
+
try {
|
|
19734
|
+
return await launch(base);
|
|
19735
|
+
} catch (err) {
|
|
19736
|
+
const explicit = overrides.executablePath || overrides.channel;
|
|
19737
|
+
if (explicit || !isMissingBrowserError(err))
|
|
19738
|
+
throw err;
|
|
19739
|
+
let lastErr = err;
|
|
19740
|
+
for (const channel of SYSTEM_CHANNEL_FALLBACKS) {
|
|
19741
|
+
try {
|
|
19742
|
+
return await launch({ ...base, channel });
|
|
19743
|
+
} catch (e) {
|
|
19744
|
+
lastErr = e;
|
|
19745
|
+
}
|
|
19746
|
+
}
|
|
19747
|
+
throw lastErr;
|
|
19748
|
+
}
|
|
19749
|
+
}
|
|
19750
|
+
function launchChromium(cfg, overrides) {
|
|
19751
|
+
return launchChromiumWithFallback((opts) => chromium.launch(opts), cfg, overrides);
|
|
19752
|
+
}
|
|
19664
19753
|
|
|
19665
19754
|
class PlaywrightDriver {
|
|
19666
19755
|
browser = null;
|
|
@@ -19671,10 +19760,7 @@ class PlaywrightDriver {
|
|
|
19671
19760
|
async launch(cfg) {
|
|
19672
19761
|
if (this.browser)
|
|
19673
19762
|
return;
|
|
19674
|
-
this.browser = await
|
|
19675
|
-
headless: cfg.headless,
|
|
19676
|
-
timeout: cfg.timeoutMs
|
|
19677
|
-
});
|
|
19763
|
+
this.browser = await launchChromium(cfg, cfg.overrides ?? {});
|
|
19678
19764
|
this.context = await this.browser.newContext({ viewport: cfg.viewport });
|
|
19679
19765
|
this.page = await this.context.newPage();
|
|
19680
19766
|
this.page.setDefaultTimeout(cfg.timeoutMs);
|
|
@@ -19763,7 +19849,8 @@ function createBrowserDriver(config) {
|
|
|
19763
19849
|
const common = {
|
|
19764
19850
|
headless: config.headless,
|
|
19765
19851
|
viewport: { width: config.viewportWidth, height: config.viewportHeight },
|
|
19766
|
-
timeoutMs: config.navigationTimeout
|
|
19852
|
+
timeoutMs: config.navigationTimeout,
|
|
19853
|
+
overrides: resolveBrowserLaunchOverrides()
|
|
19767
19854
|
};
|
|
19768
19855
|
return (async () => {
|
|
19769
19856
|
if (process.versions.bun) {
|
|
@@ -19785,9 +19872,10 @@ async function launchBridge(config, common) {
|
|
|
19785
19872
|
await bridge.launch(common);
|
|
19786
19873
|
return bridge;
|
|
19787
19874
|
}
|
|
19788
|
-
var DIRECT_LAUNCH_TIMEOUT_MS = 8000;
|
|
19875
|
+
var DIRECT_LAUNCH_TIMEOUT_MS = 8000, SYSTEM_CHANNEL_FALLBACKS;
|
|
19789
19876
|
var init_driver = __esm(() => {
|
|
19790
19877
|
init_session();
|
|
19878
|
+
SYSTEM_CHANNEL_FALLBACKS = ["chrome", "msedge"];
|
|
19791
19879
|
});
|
|
19792
19880
|
|
|
19793
19881
|
// src/modules/browser/types.ts
|
|
@@ -22981,6 +23069,9 @@ var init_plan_tool = __esm(() => {
|
|
|
22981
23069
|
// src/modules/execution/module.ts
|
|
22982
23070
|
import { existsSync as existsSync39, readFileSync as readFileSync23 } from "fs";
|
|
22983
23071
|
import { resolve as resolve18 } from "path";
|
|
23072
|
+
function planPathToken(p) {
|
|
23073
|
+
return toForwardSlash(p).toLowerCase();
|
|
23074
|
+
}
|
|
22984
23075
|
|
|
22985
23076
|
class ExecutionModule {
|
|
22986
23077
|
name = "execution";
|
|
@@ -23259,38 +23350,28 @@ class ExecutionModule {
|
|
|
23259
23350
|
const step = this.tracker.getCurrentStep();
|
|
23260
23351
|
if (!step)
|
|
23261
23352
|
return null;
|
|
23262
|
-
|
|
23263
|
-
"plan",
|
|
23264
|
-
"todo",
|
|
23265
|
-
"verify",
|
|
23266
|
-
"list_dir",
|
|
23267
|
-
"read_file",
|
|
23268
|
-
"glob",
|
|
23269
|
-
"grep",
|
|
23270
|
-
"file_info",
|
|
23271
|
-
"load_skill"
|
|
23272
|
-
];
|
|
23273
|
-
if (allowedAlways.includes(call.name))
|
|
23353
|
+
if (PLAN_ALIGNMENT_EXEMPT_TOOLS.has(call.name))
|
|
23274
23354
|
return null;
|
|
23275
|
-
const stepPaths = extractFileLikeTokens(stripUrls(step.description)).map(
|
|
23355
|
+
const stepPaths = extractFileLikeTokens(stripUrls(step.description)).map(planPathToken);
|
|
23276
23356
|
if (stepPaths.length === 0)
|
|
23277
23357
|
return null;
|
|
23278
23358
|
const argStr = this.pathArgStrings(call.arguments).join(" ");
|
|
23279
|
-
const callPaths = extractFileLikeTokens(stripUrls(argStr)).map(
|
|
23359
|
+
const callPaths = extractFileLikeTokens(stripUrls(argStr)).map(planPathToken);
|
|
23280
23360
|
if (callPaths.length === 0)
|
|
23281
23361
|
return null;
|
|
23282
23362
|
const plan = this.tracker.getPlan();
|
|
23283
23363
|
const finishedPaths = new Set;
|
|
23284
23364
|
for (const s of plan.steps) {
|
|
23285
23365
|
if (s.status === "done" || s.status === "skipped") {
|
|
23286
|
-
extractFileLikeTokens(stripUrls(s.description)).map(
|
|
23366
|
+
extractFileLikeTokens(stripUrls(s.description)).map(planPathToken).forEach((p) => finishedPaths.add(p));
|
|
23287
23367
|
}
|
|
23288
23368
|
}
|
|
23369
|
+
const matchesAny = (p, tokens) => tokens.some((t2) => t2 === p || t2.includes(p) || p.includes(t2));
|
|
23370
|
+
const finished = [...finishedPaths];
|
|
23289
23371
|
const offPath = callPaths.some((p) => {
|
|
23290
|
-
if (
|
|
23372
|
+
if (matchesAny(p, finished))
|
|
23291
23373
|
return false;
|
|
23292
|
-
|
|
23293
|
-
return !stepPaths.some((s) => p.includes(s) || s.includes(p));
|
|
23374
|
+
return !matchesAny(p, stepPaths);
|
|
23294
23375
|
});
|
|
23295
23376
|
if (!offPath)
|
|
23296
23377
|
return null;
|
|
@@ -23462,7 +23543,7 @@ class ExecutionModule {
|
|
|
23462
23543
|
return getMessageText(firstUser.content).trim();
|
|
23463
23544
|
}
|
|
23464
23545
|
}
|
|
23465
|
-
var ERROR_SEARCH_MIN_INTERVAL_MS = 30000;
|
|
23546
|
+
var ERROR_SEARCH_MIN_INTERVAL_MS = 30000, PLAN_ALIGNMENT_EXEMPT_TOOLS;
|
|
23466
23547
|
var init_module = __esm(() => {
|
|
23467
23548
|
init_i18n();
|
|
23468
23549
|
init_tracker();
|
|
@@ -23471,10 +23552,36 @@ var init_module = __esm(() => {
|
|
|
23471
23552
|
init_auditor();
|
|
23472
23553
|
init_plan_store();
|
|
23473
23554
|
init_js_identifiers();
|
|
23555
|
+
init_path_utils();
|
|
23474
23556
|
init_web_search();
|
|
23475
23557
|
init_session_isolation();
|
|
23476
23558
|
init_plan_tool();
|
|
23477
23559
|
init_execution_plugin();
|
|
23560
|
+
PLAN_ALIGNMENT_EXEMPT_TOOLS = new Set([
|
|
23561
|
+
"plan",
|
|
23562
|
+
"todo",
|
|
23563
|
+
"verify",
|
|
23564
|
+
"enable_tools",
|
|
23565
|
+
"list_dir",
|
|
23566
|
+
"read_file",
|
|
23567
|
+
"glob",
|
|
23568
|
+
"grep",
|
|
23569
|
+
"file_info",
|
|
23570
|
+
"lsp_check",
|
|
23571
|
+
"project_map",
|
|
23572
|
+
"chunk_query",
|
|
23573
|
+
"web_search",
|
|
23574
|
+
"web_fetch",
|
|
23575
|
+
"web_browse",
|
|
23576
|
+
"recall",
|
|
23577
|
+
"search_history",
|
|
23578
|
+
"session_info",
|
|
23579
|
+
"process_list",
|
|
23580
|
+
"process_log",
|
|
23581
|
+
"question",
|
|
23582
|
+
"approve",
|
|
23583
|
+
"load_skill"
|
|
23584
|
+
]);
|
|
23478
23585
|
});
|
|
23479
23586
|
|
|
23480
23587
|
// src/modules/security/session-encryption.ts
|
|
@@ -36705,100 +36812,172 @@ function registerConfigCommands(ctx) {
|
|
|
36705
36812
|
});
|
|
36706
36813
|
}
|
|
36707
36814
|
function registerProviderCommands(ctx) {
|
|
36815
|
+
const list = () => {
|
|
36816
|
+
const providers = ctx.agent.listProviders();
|
|
36817
|
+
if (providers.length === 0) {
|
|
36818
|
+
console.log(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
|
|
36819
|
+
console.log(` ${ctx.config.provider.baseUrl}`);
|
|
36820
|
+
return;
|
|
36821
|
+
}
|
|
36822
|
+
console.log(t("repl.provider_current"));
|
|
36823
|
+
for (const p of providers) {
|
|
36824
|
+
const marker = p.active ? pc2.green("* ") : " ";
|
|
36825
|
+
console.log(` ${marker}${p.label} (${pc2.dim(p.type)}) ${pc2.dim(p.baseUrl)}`);
|
|
36826
|
+
}
|
|
36827
|
+
};
|
|
36828
|
+
const use = async (args) => {
|
|
36829
|
+
const name = args[0];
|
|
36830
|
+
if (!name) {
|
|
36831
|
+
console.log(t("repl.provider_usage"));
|
|
36832
|
+
return;
|
|
36833
|
+
}
|
|
36834
|
+
try {
|
|
36835
|
+
await ctx.agent.setProvider(name);
|
|
36836
|
+
ctx.refreshModelCache();
|
|
36837
|
+
console.log(pc2.green(t("repl.provider_set", { name })));
|
|
36838
|
+
} catch (e) {
|
|
36839
|
+
console.log(pc2.red(e.message));
|
|
36840
|
+
}
|
|
36841
|
+
};
|
|
36842
|
+
const add = async (args) => {
|
|
36843
|
+
const flags = {};
|
|
36844
|
+
let name;
|
|
36845
|
+
for (let i = 0;i < args.length; i++) {
|
|
36846
|
+
const arg = args[i];
|
|
36847
|
+
if (arg.startsWith("--")) {
|
|
36848
|
+
const value = args[i + 1];
|
|
36849
|
+
if (value === undefined || value.startsWith("--")) {
|
|
36850
|
+
console.log(pc2.red(t("repl.provider_add_missing_value", { flag: arg })));
|
|
36851
|
+
return;
|
|
36852
|
+
}
|
|
36853
|
+
flags[arg.slice(2)] = value;
|
|
36854
|
+
i++;
|
|
36855
|
+
} else if (name === undefined) {
|
|
36856
|
+
name = arg;
|
|
36857
|
+
}
|
|
36858
|
+
}
|
|
36859
|
+
if (!name) {
|
|
36860
|
+
console.log(t("repl.provider_usage"));
|
|
36861
|
+
return;
|
|
36862
|
+
}
|
|
36863
|
+
const entries = Array.isArray(ctx.config.provider.entries) ? ctx.config.provider.entries : [];
|
|
36864
|
+
if (entries.length === 0) {
|
|
36865
|
+
entries.push({
|
|
36866
|
+
type: ctx.config.provider.type,
|
|
36867
|
+
label: ctx.config.provider.type,
|
|
36868
|
+
baseUrl: ctx.config.provider.baseUrl,
|
|
36869
|
+
apiKey: ctx.config.provider.apiKey
|
|
36870
|
+
});
|
|
36871
|
+
}
|
|
36872
|
+
const baseUrl = flags.url || HOSTED_BASE_URLS[name] || HOSTED_BASE_URLS[`opencode-${name}`] || "";
|
|
36873
|
+
if (!baseUrl) {
|
|
36874
|
+
console.log(pc2.red(t("cli.provider_no_url", { name })));
|
|
36875
|
+
return;
|
|
36876
|
+
}
|
|
36877
|
+
entries.push({
|
|
36878
|
+
type: name,
|
|
36879
|
+
label: name,
|
|
36880
|
+
baseUrl,
|
|
36881
|
+
apiKey: flags.key,
|
|
36882
|
+
...flags.priority !== undefined ? { priority: Number(flags.priority) } : {},
|
|
36883
|
+
...flags["context-window"] !== undefined ? { contextWindow: Number(flags["context-window"]) } : {},
|
|
36884
|
+
...flags.rpm !== undefined || flags.parallel !== undefined ? {
|
|
36885
|
+
rateLimits: {
|
|
36886
|
+
maxRequestsPerMinute: Number(flags.rpm ?? 0) || 60,
|
|
36887
|
+
maxParallelTasks: Number(flags.parallel ?? 0) || 1
|
|
36888
|
+
}
|
|
36889
|
+
} : {}
|
|
36890
|
+
});
|
|
36891
|
+
ctx.config.provider.entries = entries;
|
|
36892
|
+
ctx.config.provider.active = ctx.config.provider.active || ctx.config.provider.type;
|
|
36893
|
+
const configPath = join54(ctx.configDir, "config.json");
|
|
36894
|
+
saveConfig(ctx.config, configPath, dirname24(configPath));
|
|
36895
|
+
await ctx.agent.reconfigure(ctx.config);
|
|
36896
|
+
console.log(pc2.green(t("cli.provider_added", { name })));
|
|
36897
|
+
console.log(t("repl.provider_switch_hint", { name }));
|
|
36898
|
+
};
|
|
36899
|
+
const handlers = {
|
|
36900
|
+
list,
|
|
36901
|
+
use,
|
|
36902
|
+
add
|
|
36903
|
+
};
|
|
36708
36904
|
ctx.registerCommand({
|
|
36709
36905
|
name: "provider",
|
|
36710
36906
|
description: t("repl.provider_list"),
|
|
36711
36907
|
usage: t("repl.provider_usage"),
|
|
36712
36908
|
action: async (args) => {
|
|
36713
36909
|
const subcmd = args[0];
|
|
36714
|
-
|
|
36715
|
-
|
|
36716
|
-
|
|
36717
|
-
console.log(`${t("repl.provider_current")} ${ctx.config.provider.type}`);
|
|
36718
|
-
console.log(` ${ctx.config.provider.baseUrl}`);
|
|
36719
|
-
} else {
|
|
36720
|
-
console.log(t("repl.provider_current"));
|
|
36721
|
-
for (const p of providers) {
|
|
36722
|
-
const marker = p.active ? pc2.green("* ") : " ";
|
|
36723
|
-
console.log(` ${marker}${p.label} (${pc2.dim(p.type)}) ${pc2.dim(p.baseUrl)}`);
|
|
36724
|
-
}
|
|
36725
|
-
}
|
|
36910
|
+
const handler = subcmd ? handlers[subcmd] : handlers.list;
|
|
36911
|
+
if (!handler) {
|
|
36912
|
+
console.log(t("repl.provider_usage"));
|
|
36726
36913
|
return;
|
|
36727
36914
|
}
|
|
36728
|
-
|
|
36729
|
-
|
|
36730
|
-
|
|
36731
|
-
|
|
36732
|
-
|
|
36733
|
-
|
|
36734
|
-
|
|
36735
|
-
|
|
36736
|
-
|
|
36737
|
-
|
|
36738
|
-
|
|
36739
|
-
|
|
36915
|
+
await handler(args.slice(1));
|
|
36916
|
+
}
|
|
36917
|
+
});
|
|
36918
|
+
const listModels = async () => {
|
|
36919
|
+
console.log(`${t("repl.model_current")} ${ctx.config.model}`);
|
|
36920
|
+
const { OpenAICompatProvider: OpenAICompatProvider2 } = await Promise.resolve().then(() => (init_openai_compat(), exports_openai_compat));
|
|
36921
|
+
const provider = new OpenAICompatProvider2({
|
|
36922
|
+
model: ctx.config.model,
|
|
36923
|
+
baseUrl: ctx.config.provider.baseUrl,
|
|
36924
|
+
apiKey: ctx.config.provider.apiKey,
|
|
36925
|
+
contextWindow: ctx.config.contextWindow
|
|
36926
|
+
});
|
|
36927
|
+
const { Spinner: Spinner2 } = await Promise.resolve().then(() => (init_spinner(), exports_spinner));
|
|
36928
|
+
const s = new Spinner2;
|
|
36929
|
+
s.start(t("cli.fetching_models"));
|
|
36930
|
+
try {
|
|
36931
|
+
const models = await provider.listModels();
|
|
36932
|
+
s.stop();
|
|
36933
|
+
if (models.length > 0) {
|
|
36934
|
+
ctx.refreshModelCache();
|
|
36935
|
+
console.log(t("cli.available_models"));
|
|
36936
|
+
const { getCertMark: getCertMark2 } = await Promise.resolve().then(() => (init_manifest(), exports_manifest));
|
|
36937
|
+
for (const m of models) {
|
|
36938
|
+
const marker = m === ctx.config.model ? pc2.green("* ") : " ";
|
|
36939
|
+
const mark = getCertMark2(m, ctx.config.provider.baseUrl, version2, process.cwd());
|
|
36940
|
+
const cert = mark === "certified" ? pc2.green("✔") : mark === "stale" ? pc2.yellow("○") : pc2.dim("·");
|
|
36941
|
+
const label = mark === "certified" ? ` ${pc2.green(t("cli.cert_label"))}` : mark === "stale" ? ` ${pc2.yellow(t("cli.cert_stale_label"))}` : "";
|
|
36942
|
+
console.log(` ${marker}${cert} ${m}${label}`);
|
|
36740
36943
|
}
|
|
36741
|
-
|
|
36944
|
+
} else {
|
|
36945
|
+
console.log(t("cli.no_models_found"));
|
|
36742
36946
|
}
|
|
36743
|
-
|
|
36947
|
+
} catch (err) {
|
|
36948
|
+
s.stop();
|
|
36949
|
+
console.log(t("cli.model_fetch_failed", { error: String(err) }));
|
|
36744
36950
|
}
|
|
36745
|
-
|
|
36951
|
+
console.log(t("cli.model_hint"));
|
|
36952
|
+
};
|
|
36953
|
+
const useModel = async (args) => {
|
|
36954
|
+
const name = args[0];
|
|
36955
|
+
if (!name) {
|
|
36956
|
+
console.log(t("repl.model_usage"));
|
|
36957
|
+
return;
|
|
36958
|
+
}
|
|
36959
|
+
ctx.config.model = name;
|
|
36960
|
+
const configPath = join54(ctx.configDir, "config.json");
|
|
36961
|
+
saveConfig(ctx.config, configPath, dirname24(configPath));
|
|
36962
|
+
await ctx.agent.reconfigure(ctx.config);
|
|
36963
|
+
console.log(pc2.green(t("repl.model_set", { name })));
|
|
36964
|
+
};
|
|
36965
|
+
const modelHandlers = {
|
|
36966
|
+
list: listModels,
|
|
36967
|
+
use: useModel
|
|
36968
|
+
};
|
|
36746
36969
|
ctx.registerCommand({
|
|
36747
36970
|
name: "model",
|
|
36748
36971
|
description: t("repl.model_list"),
|
|
36749
36972
|
usage: t("repl.model_usage"),
|
|
36750
36973
|
action: async (args) => {
|
|
36751
36974
|
const subcmd = args[0];
|
|
36752
|
-
|
|
36753
|
-
|
|
36754
|
-
|
|
36755
|
-
const provider = new OpenAICompatProvider2({
|
|
36756
|
-
model: ctx.config.model,
|
|
36757
|
-
baseUrl: ctx.config.provider.baseUrl,
|
|
36758
|
-
apiKey: ctx.config.provider.apiKey,
|
|
36759
|
-
contextWindow: ctx.config.contextWindow
|
|
36760
|
-
});
|
|
36761
|
-
const { Spinner: Spinner2 } = await Promise.resolve().then(() => (init_spinner(), exports_spinner));
|
|
36762
|
-
const s = new Spinner2;
|
|
36763
|
-
s.start(t("cli.fetching_models"));
|
|
36764
|
-
try {
|
|
36765
|
-
const models = await provider.listModels();
|
|
36766
|
-
s.stop();
|
|
36767
|
-
if (models.length > 0) {
|
|
36768
|
-
ctx.refreshModelCache();
|
|
36769
|
-
console.log(t("cli.available_models"));
|
|
36770
|
-
const { getCertMark: getCertMark2 } = await Promise.resolve().then(() => (init_manifest(), exports_manifest));
|
|
36771
|
-
for (const m of models) {
|
|
36772
|
-
const marker = m === ctx.config.model ? pc2.green("* ") : " ";
|
|
36773
|
-
const mark = getCertMark2(m, ctx.config.provider.baseUrl, version2, process.cwd());
|
|
36774
|
-
const cert = mark === "certified" ? pc2.green("✔") : mark === "stale" ? pc2.yellow("○") : pc2.dim("·");
|
|
36775
|
-
const label = mark === "certified" ? ` ${pc2.green(t("cli.cert_label"))}` : mark === "stale" ? ` ${pc2.yellow(t("cli.cert_stale_label"))}` : "";
|
|
36776
|
-
console.log(` ${marker}${cert} ${m}${label}`);
|
|
36777
|
-
}
|
|
36778
|
-
} else {
|
|
36779
|
-
console.log(t("cli.no_models_found"));
|
|
36780
|
-
}
|
|
36781
|
-
} catch (err) {
|
|
36782
|
-
s.stop();
|
|
36783
|
-
console.log(t("cli.model_fetch_failed", { error: String(err) }));
|
|
36784
|
-
}
|
|
36785
|
-
console.log(t("cli.model_hint"));
|
|
36786
|
-
return;
|
|
36787
|
-
}
|
|
36788
|
-
if (subcmd === "use") {
|
|
36789
|
-
const name = args[1];
|
|
36790
|
-
if (!name) {
|
|
36791
|
-
console.log(t("repl.model_usage"));
|
|
36792
|
-
return;
|
|
36793
|
-
}
|
|
36794
|
-
ctx.config.model = name;
|
|
36795
|
-
const configPath = join54(ctx.configDir, "config.json");
|
|
36796
|
-
saveConfig(ctx.config, configPath, dirname24(configPath));
|
|
36797
|
-
await ctx.agent.reconfigure(ctx.config);
|
|
36798
|
-
console.log(pc2.green(t("repl.model_set", { name })));
|
|
36975
|
+
const handler = subcmd ? modelHandlers[subcmd] : modelHandlers.list;
|
|
36976
|
+
if (!handler) {
|
|
36977
|
+
console.log(t("repl.model_usage"));
|
|
36799
36978
|
return;
|
|
36800
36979
|
}
|
|
36801
|
-
|
|
36980
|
+
await handler(args.slice(1));
|
|
36802
36981
|
}
|
|
36803
36982
|
});
|
|
36804
36983
|
ctx.registerCommand({
|
|
@@ -37107,88 +37286,97 @@ function registerSessionCommands(ctx) {
|
|
|
37107
37286
|
function registerSkillCommands(ctx) {
|
|
37108
37287
|
if (!ctx.skillsModule)
|
|
37109
37288
|
return;
|
|
37289
|
+
const listSkills = () => {
|
|
37290
|
+
const available = ctx.skillsModule.getAvailable();
|
|
37291
|
+
if (available.length === 0) {
|
|
37292
|
+
console.log(t("repl.no_skills"));
|
|
37293
|
+
return;
|
|
37294
|
+
}
|
|
37295
|
+
console.log(pc2.bold(t("repl.available_skills")));
|
|
37296
|
+
for (const skill of available) {
|
|
37297
|
+
const tokens = estimateTokens(skill.content);
|
|
37298
|
+
const loaded = ctx.skillsModule.getLoaded().some((s) => s.name === skill.name);
|
|
37299
|
+
const marker = loaded ? pc2.green(" [loaded]") : "";
|
|
37300
|
+
console.log(` ${pc2.cyan(skill.name)}${marker} ${pc2.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
37301
|
+
}
|
|
37302
|
+
};
|
|
37303
|
+
const listLoaded = () => {
|
|
37304
|
+
const loaded = ctx.skillsModule.getLoaded();
|
|
37305
|
+
const budget = ctx.skillsModule.getBudget();
|
|
37306
|
+
if (loaded.length === 0) {
|
|
37307
|
+
console.log(t("repl.no_loaded"));
|
|
37308
|
+
return;
|
|
37309
|
+
}
|
|
37310
|
+
console.log(pc2.bold(t("repl.loaded_skills")));
|
|
37311
|
+
for (const skill of loaded) {
|
|
37312
|
+
const tokens = estimateTokens(skill.content);
|
|
37313
|
+
console.log(` ${pc2.cyan(skill.name)} ${pc2.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
37314
|
+
}
|
|
37315
|
+
console.log(pc2.dim(`
|
|
37316
|
+
${t("repl.budget", { used: budget.used, total: budget.total, remaining: budget.remaining })}`));
|
|
37317
|
+
};
|
|
37318
|
+
const loadSkill = (args) => {
|
|
37319
|
+
const arg = args.join(" ");
|
|
37320
|
+
if (!arg) {
|
|
37321
|
+
console.log(t("repl.skill_load_usage"));
|
|
37322
|
+
return;
|
|
37323
|
+
}
|
|
37324
|
+
const result = ctx.skillsModule.loadByName(arg);
|
|
37325
|
+
if (result.success) {
|
|
37326
|
+
console.log(pc2.green(result.message));
|
|
37327
|
+
} else {
|
|
37328
|
+
console.log(pc2.red(result.message));
|
|
37329
|
+
}
|
|
37330
|
+
};
|
|
37331
|
+
const unloadSkill = (args) => {
|
|
37332
|
+
const arg = args.join(" ");
|
|
37333
|
+
if (!arg) {
|
|
37334
|
+
console.log(t("repl.skill_unload_usage"));
|
|
37335
|
+
return;
|
|
37336
|
+
}
|
|
37337
|
+
if (ctx.skillsModule.unload(arg)) {
|
|
37338
|
+
console.log(pc2.green(t("repl.skill_unloaded", { name: arg })));
|
|
37339
|
+
} else {
|
|
37340
|
+
console.log(pc2.red(t("repl.skill_not_loaded", { name: arg })));
|
|
37341
|
+
}
|
|
37342
|
+
};
|
|
37343
|
+
const searchSkills = (args) => {
|
|
37344
|
+
const arg = args.join(" ");
|
|
37345
|
+
if (!arg) {
|
|
37346
|
+
console.log(t("repl.skill_search_usage"));
|
|
37347
|
+
return;
|
|
37348
|
+
}
|
|
37349
|
+
const results = ctx.skillsModule.search(arg);
|
|
37350
|
+
if (results.length === 0) {
|
|
37351
|
+
console.log(t("repl.no_skill_match", { query: arg }));
|
|
37352
|
+
return;
|
|
37353
|
+
}
|
|
37354
|
+
console.log(pc2.bold(t("repl.skills_matching", { query: arg })));
|
|
37355
|
+
for (const skill of results) {
|
|
37356
|
+
const tokens = estimateTokens(skill.content);
|
|
37357
|
+
console.log(` ${pc2.cyan(skill.name)} ${pc2.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
37358
|
+
}
|
|
37359
|
+
};
|
|
37360
|
+
const skillHandlers = {
|
|
37361
|
+
list: listSkills,
|
|
37362
|
+
loaded: listLoaded,
|
|
37363
|
+
load: loadSkill,
|
|
37364
|
+
unload: unloadSkill,
|
|
37365
|
+
search: searchSkills
|
|
37366
|
+
};
|
|
37110
37367
|
ctx.registerCommand({
|
|
37111
37368
|
name: "skill",
|
|
37112
37369
|
description: t("repl.skill"),
|
|
37113
37370
|
usage: t("repl.skill_usage"),
|
|
37114
37371
|
action: (args) => {
|
|
37115
37372
|
const subcmd = args[0];
|
|
37116
|
-
const
|
|
37117
|
-
if (!
|
|
37118
|
-
|
|
37119
|
-
|
|
37120
|
-
console.log(t("repl.no_skills"));
|
|
37121
|
-
return;
|
|
37122
|
-
}
|
|
37123
|
-
console.log(pc2.bold(t("repl.available_skills")));
|
|
37124
|
-
for (const skill of available) {
|
|
37125
|
-
const tokens = estimateTokens(skill.content);
|
|
37126
|
-
const loaded = ctx.skillsModule.getLoaded().some((s) => s.name === skill.name);
|
|
37127
|
-
const marker = loaded ? pc2.green(" [loaded]") : "";
|
|
37128
|
-
console.log(` ${pc2.cyan(skill.name)}${marker} ${pc2.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
37129
|
-
}
|
|
37130
|
-
return;
|
|
37131
|
-
}
|
|
37132
|
-
if (subcmd === "loaded") {
|
|
37133
|
-
const loaded = ctx.skillsModule.getLoaded();
|
|
37134
|
-
const budget = ctx.skillsModule.getBudget();
|
|
37135
|
-
if (loaded.length === 0) {
|
|
37136
|
-
console.log(t("repl.no_loaded"));
|
|
37137
|
-
return;
|
|
37138
|
-
}
|
|
37139
|
-
console.log(pc2.bold(t("repl.loaded_skills")));
|
|
37140
|
-
for (const skill of loaded) {
|
|
37141
|
-
const tokens = estimateTokens(skill.content);
|
|
37142
|
-
console.log(` ${pc2.cyan(skill.name)} ${pc2.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
37143
|
-
}
|
|
37144
|
-
console.log(pc2.dim(`
|
|
37145
|
-
${t("repl.budget", { used: budget.used, total: budget.total, remaining: budget.remaining })}`));
|
|
37146
|
-
return;
|
|
37147
|
-
}
|
|
37148
|
-
if (subcmd === "load") {
|
|
37149
|
-
if (!arg) {
|
|
37150
|
-
console.log(t("repl.skill_load_usage"));
|
|
37151
|
-
return;
|
|
37152
|
-
}
|
|
37153
|
-
const result = ctx.skillsModule.loadByName(arg);
|
|
37154
|
-
if (result.success) {
|
|
37155
|
-
console.log(pc2.green(result.message));
|
|
37156
|
-
} else {
|
|
37157
|
-
console.log(pc2.red(result.message));
|
|
37158
|
-
}
|
|
37159
|
-
return;
|
|
37160
|
-
}
|
|
37161
|
-
if (subcmd === "unload") {
|
|
37162
|
-
if (!arg) {
|
|
37163
|
-
console.log(t("repl.skill_unload_usage"));
|
|
37164
|
-
return;
|
|
37165
|
-
}
|
|
37166
|
-
if (ctx.skillsModule.unload(arg)) {
|
|
37167
|
-
console.log(pc2.green(t("repl.skill_unloaded", { name: arg })));
|
|
37168
|
-
} else {
|
|
37169
|
-
console.log(pc2.red(t("repl.skill_not_loaded", { name: arg })));
|
|
37170
|
-
}
|
|
37373
|
+
const handler = subcmd ? skillHandlers[subcmd] : skillHandlers.list;
|
|
37374
|
+
if (!handler) {
|
|
37375
|
+
console.log(pc2.red(t("repl.skill_unknown_sub", { subcmd })));
|
|
37376
|
+
console.log(t("repl.skill_usage"));
|
|
37171
37377
|
return;
|
|
37172
37378
|
}
|
|
37173
|
-
|
|
37174
|
-
if (!arg) {
|
|
37175
|
-
console.log(t("repl.skill_search_usage"));
|
|
37176
|
-
return;
|
|
37177
|
-
}
|
|
37178
|
-
const results = ctx.skillsModule.search(arg);
|
|
37179
|
-
if (results.length === 0) {
|
|
37180
|
-
console.log(t("repl.no_skill_match", { query: arg }));
|
|
37181
|
-
return;
|
|
37182
|
-
}
|
|
37183
|
-
console.log(pc2.bold(t("repl.skills_matching", { query: arg })));
|
|
37184
|
-
for (const skill of results) {
|
|
37185
|
-
const tokens = estimateTokens(skill.content);
|
|
37186
|
-
console.log(` ${pc2.cyan(skill.name)} ${pc2.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
37187
|
-
}
|
|
37188
|
-
return;
|
|
37189
|
-
}
|
|
37190
|
-
console.log(pc2.red(t("repl.skill_unknown_sub", { subcmd })));
|
|
37191
|
-
console.log(t("repl.skill_usage"));
|
|
37379
|
+
handler(args.slice(1));
|
|
37192
37380
|
}
|
|
37193
37381
|
});
|
|
37194
37382
|
}
|
|
@@ -37221,6 +37409,7 @@ var init_repl_commands = __esm(() => {
|
|
|
37221
37409
|
init_i18n();
|
|
37222
37410
|
init_setup();
|
|
37223
37411
|
init_config2();
|
|
37412
|
+
init_presets();
|
|
37224
37413
|
init_budget();
|
|
37225
37414
|
init_token_counter();
|
|
37226
37415
|
init_map_command();
|
|
@@ -39549,22 +39738,17 @@ class SkillNameProvider {
|
|
|
39549
39738
|
return ctx.tokenIndex >= 2 && ctx.tokens[0] === "/skill" && (ctx.tokens[1] === "load" || ctx.tokens[1] === "unload");
|
|
39550
39739
|
}
|
|
39551
39740
|
complete(ctx) {
|
|
39552
|
-
const
|
|
39553
|
-
|
|
39554
|
-
|
|
39555
|
-
|
|
39556
|
-
|
|
39557
|
-
|
|
39558
|
-
return
|
|
39559
|
-
|
|
39560
|
-
if (
|
|
39561
|
-
|
|
39562
|
-
|
|
39563
|
-
if (!ctx.partial)
|
|
39564
|
-
return names;
|
|
39565
|
-
return names.filter((n) => n.toLowerCase().includes(ctx.partial.toLowerCase()));
|
|
39566
|
-
}
|
|
39567
|
-
return [];
|
|
39741
|
+
const sources = {
|
|
39742
|
+
load: () => this.skillsModule.getAvailable().map((s) => s.name),
|
|
39743
|
+
unload: () => this.skillsModule.getLoaded().map((s) => s.name)
|
|
39744
|
+
};
|
|
39745
|
+
const source = sources[ctx.tokens[1]];
|
|
39746
|
+
if (!source)
|
|
39747
|
+
return [];
|
|
39748
|
+
const names = source();
|
|
39749
|
+
if (!ctx.partial)
|
|
39750
|
+
return names;
|
|
39751
|
+
return names.filter((n) => n.toLowerCase().includes(ctx.partial.toLowerCase()));
|
|
39568
39752
|
}
|
|
39569
39753
|
}
|
|
39570
39754
|
|
|
@@ -39840,7 +40024,7 @@ function toDisplayPath(baseDir, p) {
|
|
|
39840
40024
|
}
|
|
39841
40025
|
var GUTTER = " ";
|
|
39842
40026
|
var MAX_OUTPUT_LINES = 50;
|
|
39843
|
-
var
|
|
40027
|
+
var SPINNERLESS_TOOLS = new Set(["question", "approve"]);
|
|
39844
40028
|
function toolMarker(tool) {
|
|
39845
40029
|
switch (tool) {
|
|
39846
40030
|
case "write_file":
|
|
@@ -39914,7 +40098,7 @@ class Renderer {
|
|
|
39914
40098
|
`), this.width, (text) => this.out.write(text));
|
|
39915
40099
|
}
|
|
39916
40100
|
showLoader() {
|
|
39917
|
-
this.spinner.start("thinking");
|
|
40101
|
+
this.spinner.start(t("ui.thinking"));
|
|
39918
40102
|
}
|
|
39919
40103
|
text(chunk) {
|
|
39920
40104
|
this.endCard();
|
|
@@ -39968,6 +40152,7 @@ ${pc2.dim("→")} Thought: `);
|
|
|
39968
40152
|
this.thoughtStarted = true;
|
|
39969
40153
|
this.thoughtStartMs = Date.now();
|
|
39970
40154
|
this.thoughtHeaderPrinted = false;
|
|
40155
|
+
this.spinner.start(t("ui.thinking"));
|
|
39971
40156
|
}
|
|
39972
40157
|
thinkingEnd() {
|
|
39973
40158
|
this.spinner.stop();
|
|
@@ -39987,14 +40172,18 @@ ${pc2.dim("→")} Thought: `);
|
|
|
39987
40172
|
const displayArgs = PATH_TOOLS.has(tool) && typeof args.path === "string" ? { ...args, path: toDisplayPath(this.baseDir, args.path) } : args;
|
|
39988
40173
|
const summary = summarizeArgs2(displayArgs);
|
|
39989
40174
|
const marker = icon || toolMarker(tool);
|
|
40175
|
+
const label = friendlyTool(tool);
|
|
39990
40176
|
this.card = { tool, args, start: Date.now() };
|
|
39991
40177
|
if (stepContext) {
|
|
39992
40178
|
this.out.write(`
|
|
39993
40179
|
${pc2.dim("↓")} ${pc2.cyan(stepContext)}
|
|
39994
40180
|
`);
|
|
39995
40181
|
}
|
|
39996
|
-
this.out.write(`${pc2.dim(marker)} ${
|
|
40182
|
+
this.out.write(`${pc2.dim(marker)} ${label}${summary ? ` ${pc2.dim(summary)}` : ""}
|
|
39997
40183
|
`);
|
|
40184
|
+
if (!SPINNERLESS_TOOLS.has(tool)) {
|
|
40185
|
+
this.spinner.start(`${label}${summary ? ` ${summary}` : ""}`);
|
|
40186
|
+
}
|
|
39998
40187
|
}
|
|
39999
40188
|
planBlock(lines) {
|
|
40000
40189
|
this.endCard();
|
|
@@ -40417,7 +40606,7 @@ class Repl {
|
|
|
40417
40606
|
this.completer.registerProvider(new SubcommandProvider("skill", ["list", "loaded", "load", "unload", "search"]));
|
|
40418
40607
|
this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
|
|
40419
40608
|
}
|
|
40420
|
-
this.completer.registerProvider(new SubcommandProvider("provider", ["list", "use"]));
|
|
40609
|
+
this.completer.registerProvider(new SubcommandProvider("provider", ["list", "use", "add"]));
|
|
40421
40610
|
this.completer.registerProvider(new SubcommandProvider("model", ["list", "use"]));
|
|
40422
40611
|
const entryLabels = this.config.provider?.entries?.map((e) => e.label || e.type) ?? [];
|
|
40423
40612
|
if (entryLabels.length > 0) {
|
|
@@ -41,13 +41,46 @@ function readInt(v, fallback) {
|
|
|
41
41
|
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// System browsers to try when the bundled Chromium revision is absent (CDN
|
|
45
|
+
// blocked / offline). Mirrors driver.ts so both transports behave the same.
|
|
46
|
+
const SYSTEM_CHANNEL_FALLBACKS = ["chrome", "msedge"];
|
|
47
|
+
|
|
48
|
+
function isMissingBrowserError(err) {
|
|
49
|
+
return /Executable doesn't exist|is not found|looks like Playwright was just installed/i.test(
|
|
50
|
+
String(err && err.message ? err.message : err),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function launchChromium(params, extra) {
|
|
55
|
+
const base = {
|
|
56
|
+
headless: params?.headless !== false,
|
|
57
|
+
timeout: readInt(params?.timeoutMs, 30000),
|
|
58
|
+
...extra,
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
return await chromium.launch(base);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// An explicit executable/channel is the user's choice — fail loudly.
|
|
64
|
+
if (base.executablePath || base.channel || !isMissingBrowserError(err)) throw err;
|
|
65
|
+
let lastErr = err;
|
|
66
|
+
for (const channel of SYSTEM_CHANNEL_FALLBACKS) {
|
|
67
|
+
try {
|
|
68
|
+
return await chromium.launch({ ...base, channel });
|
|
69
|
+
} catch (e) {
|
|
70
|
+
lastErr = e;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
throw lastErr;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
44
77
|
async function handleLaunch(params) {
|
|
45
78
|
if (browser) return { ok: true };
|
|
46
79
|
maxLineChars = readInt(params?.maxConsoleLineChars, 400);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
const extra = {};
|
|
81
|
+
if (params?.executablePath) extra.executablePath = String(params.executablePath);
|
|
82
|
+
if (params?.channel) extra.channel = String(params.channel);
|
|
83
|
+
browser = await launchChromium(params, extra);
|
|
51
84
|
context = await browser.newContext({
|
|
52
85
|
viewport: params?.viewport || { width: 1280, height: 720 },
|
|
53
86
|
});
|