@goodandready/dsh-moa 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -37
- package/docs/README.ru.md +66 -37
- package/docs/README.zh.md +70 -27
- package/docs/design/DESIGN.md +16 -6
- package/docs/plans/47-client-js-split-plan.md +57 -0
- package/docs/plans/55-resilience-plan.md +33 -0
- package/lib/client.js +260 -85
- package/lib/file-workspace.js +3 -1
- package/lib/history.js +88 -33
- package/lib/index.js +70 -13
- package/lib/live-canvas.js +34 -0
- package/lib/moa-parser.js +181 -0
- package/lib/moa-prompts.js +251 -0
- package/lib/moa-runner.js +477 -473
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# План: разбиение lib/client.js (issue #47)
|
|
2
|
+
|
|
3
|
+
Статус: принято решение, реализация отложена до появления подтверждённой механики бандлера.
|
|
4
|
+
Дата: 2026-09-11. Исполнитель: zcode. Связанная issue: #47.
|
|
5
|
+
|
|
6
|
+
## Контекст
|
|
7
|
+
|
|
8
|
+
Корневой чек-лист code review требует файл ≤800 строк; `lib/client.js` — ~1240 строк
|
|
9
|
+
после батча качества. `MoAEditor` ≈500 строк, словари ≈110 строк, CSS ≈85 строк.
|
|
10
|
+
Прямое разбиение упирается в механику клиентского бандлера DSH.
|
|
11
|
+
|
|
12
|
+
## Собранный evidence (2026-09-11)
|
|
13
|
+
|
|
14
|
+
1. **Харнесс отдаёт ровно один модуль на пакет**: на тест-контуре (DSH 0.1.5-rc.2)
|
|
15
|
+
клиент плагина обслуживается по единому URL
|
|
16
|
+
`/plugins/??@goodandready/dsh-moa/client.js&rev=<hash>` (combo-загрузчик);
|
|
17
|
+
реестр модулей в индекс-странице ссылается только на `client.js` пакета.
|
|
18
|
+
2. **Ни один плагин семейства не использует относительный require в клиентском
|
|
19
|
+
коде**: dsh-lanmode (23 файла в lib/), dsh-tts (client.js 1851 строка),
|
|
20
|
+
dsh-model-sync (17 файлов), dsh-clinebot (5 файлов), dsh-grok-xsearch (14
|
|
21
|
+
файлов) — везде `require('./...')` в client.js отсутствует. Многофайловость
|
|
22
|
+
живёт только в серверной половине.
|
|
23
|
+
3. Клиентская factory получает `require` (пакетные зависимости: `react`,
|
|
24
|
+
`@deepseek-ai/dsh-client-ui-primitives`) — документированных свидетельств
|
|
25
|
+
поддержки относительных require внутри клиентского модуля нет.
|
|
26
|
+
|
|
27
|
+
## Варианты
|
|
28
|
+
|
|
29
|
+
- **A. Оставить один файл (принято сейчас).** Нулевой риск для загрузки карточки;
|
|
30
|
+
известный компромисс с чек-листом зафиксирован в issue.
|
|
31
|
+
- **B. Сборочная склейка (concat/минимальный bundler перед `npm pack`).** С.Split
|
|
32
|
+
исходников на части + prepublish-скрипт, собирающий `lib/client.js`.
|
|
33
|
+
Плюс: файлы в репозитории маленькие, публикуется по-прежнему один модуль.
|
|
34
|
+
Минус: в семействе нет ни одного плагина со сборочным шагом — вводит новую
|
|
35
|
+
конвенцию, усложняет publish-пайплайн и локальную отладку.
|
|
36
|
+
- **C. Относительный require в клиенте.** Самый чистый вариант, но требует
|
|
37
|
+
подтверждённой поддержки в бандлере харнесса (эксперимент на тест-контуре).
|
|
38
|
+
|
|
39
|
+
## Решение и критерии пересмотра
|
|
40
|
+
|
|
41
|
+
- Сейчас — вариант A: риск поломки загрузки карточки выше пользы, а файл после
|
|
42
|
+
батча качества сократился (1240 строк против 1311) и остаётся цельным по смыслу.
|
|
43
|
+
- Пересмотреть к варианту C, если появится подтверждённый пример относительного
|
|
44
|
+
require в клиентском модуле любого плагина семейства (эксперимент: маленький
|
|
45
|
+
пакет-проба `require('./client-part.js')` на тест-контуре + проверка загрузки
|
|
46
|
+
карточки без ошибок в консоли).
|
|
47
|
+
- Пересмотреть к варианту B, если файл превысит ~1800 строк или в семействе
|
|
48
|
+
появится первый плагин со сборочным шагом.
|
|
49
|
+
|
|
50
|
+
## Чек-лист разбиения (когда механика подтверждена)
|
|
51
|
+
|
|
52
|
+
1. Вынести словари и `makeT` в `lib/client-dict.js`, стили — в `lib/client-styles.js`,
|
|
53
|
+
`SearchableModelPicker` — в `lib/client-picker.js`.
|
|
54
|
+
2. `lib/client.js` остаётся единственной точкой входа `__ModuleLoader__.load`.
|
|
55
|
+
3. Проверки: клиентский модуль отдаётся 200 по per-plugin URL; карточка
|
|
56
|
+
рендерится (settings.plugin.item) без ошибок в консоли; локали регистрируются;
|
|
57
|
+
HMR/обновление соседнего плагина не ломает стили (атрибут data-dsh-plugin).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Task Plan: MoA Resilience, Async I/O, Configurable Timeouts and Modularization
|
|
2
|
+
|
|
3
|
+
Issue: #55
|
|
4
|
+
Branch: `feat/moa-resilience-refactor`
|
|
5
|
+
Worktree: `/mnt/external/Project/DEV/dhsplugins/dsh-moa/.worktrees/feat/moa-resilience-refactor`
|
|
6
|
+
|
|
7
|
+
## Objectives
|
|
8
|
+
1. Modularize `lib/moa-runner.js` into clean, focused modules under 800 lines:
|
|
9
|
+
- `lib/moa-prompts.js`: Rubric, system prompts, synthesis and questionnaire prompts.
|
|
10
|
+
- `lib/moa-parser.js`: Output parsing, winner extraction, recommended assembler, file block extraction.
|
|
11
|
+
- `lib/moa-runner.js`: Focused pipeline orchestrator.
|
|
12
|
+
2. Optimize storage in `lib/history.js`:
|
|
13
|
+
- Non-blocking async append via `fs.promises.appendFile`.
|
|
14
|
+
- File size rotation (>10MB or >5000 lines -> `.jsonl.1` backup).
|
|
15
|
+
- Fast tail reader for paginated history to avoid loading gigantic files in memory.
|
|
16
|
+
3. Configurable timeouts in `lib/index.js` and `PresetSchema`:
|
|
17
|
+
- `reference_timeout_sec` (default: 60s).
|
|
18
|
+
- `aggregator_timeout_sec` (default: 180s).
|
|
19
|
+
- Forward timeouts into `callLlm` and `AbortController`.
|
|
20
|
+
4. Fallback recovery on malformed judge output:
|
|
21
|
+
- When judge output is empty or truncated, cleanly fall back to `aggregator_fallbacks` before degrading.
|
|
22
|
+
5. Verification & Tests:
|
|
23
|
+
- Add unit tests in `test/moa-resilience.test.mjs`.
|
|
24
|
+
- Verify existing 60/60 tests pass without regression.
|
|
25
|
+
- Update `docs/design/DESIGN.md` and README files.
|
|
26
|
+
|
|
27
|
+
## Phases
|
|
28
|
+
- [x] Phase 1: Planning & Setup (Issue #55, isolated worktree created, test suite verified)
|
|
29
|
+
- [ ] Phase 2: Modularization of moa-runner (`lib/moa-prompts.js`, `lib/moa-parser.js`)
|
|
30
|
+
- [ ] Phase 3: History Storage Rotation & Async I/O (`lib/history.js`)
|
|
31
|
+
- [ ] Phase 4: Configurable Timeouts & Fallback Recovery (`lib/index.js`, `lib/moa-runner.js`)
|
|
32
|
+
- [ ] Phase 5: Test Coverage & Verification (`test/moa-resilience.test.mjs`)
|
|
33
|
+
- [ ] Phase 6: Documentation, PR, Merge, Release & Deploy
|
package/lib/client.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// Plugin Settings Card (settings.plugin.item).
|
|
2
2
|
//
|
|
3
3
|
// Aligned with dsh-clinebot design language:
|
|
4
|
-
// - Header with real
|
|
4
|
+
// - Header with real status chips (host online/offline/disabled via /dsh-moa/status)
|
|
5
5
|
// - Clean section cards (.moa-section-card) using native DSH design tokens (--dsw-alias-*)
|
|
6
6
|
// - Segmented preset selectors and count pills
|
|
7
7
|
// - Refined temperature sliders and searchable model picker
|
|
8
|
-
// -
|
|
8
|
+
// - Telemetry summary grid fed by /dsh-moa/history
|
|
9
9
|
// - Robust error boundary and settingsScope reactivity
|
|
10
|
+
//
|
|
11
|
+
// English is the canonical source language; the ru translation is provided
|
|
12
|
+
// by the DSH translation plugin at runtime (no bundled ru duplicate).
|
|
10
13
|
|
|
11
14
|
window.__ModuleLoader__.load({
|
|
12
15
|
id: '@goodandready/dsh-moa',
|
|
@@ -48,6 +51,16 @@ window.__ModuleLoader__.load({
|
|
|
48
51
|
'aggregator.criteria_placeholder': 'e.g. Priority: performance, zero external dependencies, robust edge-case handling...',
|
|
49
52
|
'aggregator.questions_label': 'Ask clarifying questions on broad tasks (Questionnaire)',
|
|
50
53
|
'aggregator.questions_hint': 'When enabled: for short/vague prompts, the judge synthesizes a 2-4 question checklist before code generation.',
|
|
54
|
+
'aggregator.curator_label': 'Curator Synthesis & Master Assembler Advice',
|
|
55
|
+
'aggregator.curator_hint': 'When enabled: the curator highlights the finest components of each candidate, applies the antipatterns rubric, and advises which agent model should assemble the solution.',
|
|
56
|
+
'aggregator.stream_label': 'Live Stream Curator/Judge Thinking',
|
|
57
|
+
'aggregator.stream_hint': 'Streams aggregator tokens directly to chat in real time for zero-latency initial response.',
|
|
58
|
+
'aggregator.fallbacks_title': 'Fallback Judges Chain',
|
|
59
|
+
'aggregator.fallbacks_desc': 'Sequential backup models invoked automatically if the primary judge experiences rate limits or outages.',
|
|
60
|
+
'aggregator.add_fallback_btn': '+ Add Fallback Judge',
|
|
61
|
+
'proposers.quorum_label': 'Quorum Fast-Forward (Straggler Mitigation)',
|
|
62
|
+
'proposers.quorum_hint': 'When 67%+ candidates finish, start a grace timer (default: 10s) and proceed without waiting for hanging providers.',
|
|
63
|
+
'proposers.grace_label': 'Grace Period (sec):',
|
|
51
64
|
'proposers.title': '👥 Candidate Models (Proposers)',
|
|
52
65
|
'proposers.desc': 'Generate independent solutions in parallel to maximize diversity and solution space coverage.',
|
|
53
66
|
'proposers.parallel_count': 'Parallel Proposers',
|
|
@@ -71,65 +84,12 @@ window.__ModuleLoader__.load({
|
|
|
71
84
|
'actions.save': 'Save Configuration',
|
|
72
85
|
'actions.saving': 'Saving…',
|
|
73
86
|
'actions.saved': 'Configuration saved successfully!',
|
|
74
|
-
'actions.retry': '
|
|
87
|
+
'actions.retry': 'Reload Configuration',
|
|
88
|
+
'badge.disabled': 'MoA Disabled',
|
|
89
|
+
'config.enabled_label': 'Enable MoA (slash command and turn routing)',
|
|
75
90
|
'slash.desc': 'Run turn with Mixture of Agents ensemble synthesis (/moa [preset] <prompt>)',
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
const ru = {
|
|
79
|
-
title: 'Mixture of Agents (MoA)',
|
|
80
|
-
description: 'Ансамбль моделей: параллельная генерация советниками и синтез ведущим судьей через /moa <prompt>.',
|
|
81
|
-
'header.title': 'Оркестрация Mixture of Agents (MoA)',
|
|
82
|
-
'header.sub': 'Параллельная генерация вариантов советниками с оценкой и продвижением лучшего кода судьей. Модель сессии восстанавливается автоматически.',
|
|
83
|
-
'badge.online': 'MoA Активен',
|
|
84
|
-
'badge.offline': 'Хост недоступен',
|
|
85
|
-
'badge.preset': 'Пресет: {name}',
|
|
86
|
-
'badge.models': '{count} моделей доступно',
|
|
87
|
-
'badge.candidates': '{count} советников',
|
|
88
|
-
'presets.title': '📋 Управление пресетами',
|
|
89
|
-
'presets.desc': 'Выберите активный пресет или создайте специализированную конфигурацию (например: для код-ревью, математики или быстрого скрипта).',
|
|
90
|
-
'presets.active_badge': 'По умолчанию',
|
|
91
|
-
'presets.set_default': 'Сделать по умолчанию',
|
|
92
|
-
'presets.new_btn': '+ Создать пресет',
|
|
93
|
-
'presets.del_btn': 'Удалить',
|
|
94
|
-
'presets.del_confirm': 'Вы уверены, что хотите удалить пресет "{name}"?',
|
|
95
|
-
'presets.create': 'Создать',
|
|
96
|
-
'presets.cancel': 'Отмена',
|
|
97
|
-
'presets.name_placeholder': 'Название нового пресета (например: fast-audit)',
|
|
98
|
-
'aggregator.title': '⚖️ Ведущая модель / Судья (Синтез и аудит)',
|
|
99
|
-
'aggregator.desc': 'Сопоставляет варианты кандидатов, устраняет ошибки, отбирает лучший код и синтезирует итоговое решение.',
|
|
100
|
-
'aggregator.temp_label': 'Температура судьи',
|
|
101
|
-
'aggregator.temp_hint': 'Рекомендуется 0.2 – 0.4 для строгой проверки и синтеза без выдумок.',
|
|
102
|
-
'aggregator.criteria_label': 'Критерии оценки судьи (опционально):',
|
|
103
|
-
'aggregator.criteria_placeholder': 'например: Приоритет — производительность, чистый код, минимум зависимостей, тесты...',
|
|
104
|
-
'aggregator.questions_label': 'Спрашивать уточняющие вопросы при неясных задачах (Опросник)',
|
|
105
|
-
'aggregator.questions_hint': 'Если включено: при коротких задачах судья сформирует опросник. Если выключено: MoA сразу переходит к генерации решения.',
|
|
106
|
-
'proposers.title': '👥 Модели-кандидаты (Советники)',
|
|
107
|
-
'proposers.desc': 'Параллельно формируют независимые варианты решения одной задачи.',
|
|
108
|
-
'proposers.parallel_count': 'Параллельных моделей',
|
|
109
|
-
'proposers.candidate': 'Кандидат',
|
|
110
|
-
'proposers.add_btn': '+ Добавить модель в параллель',
|
|
111
|
-
'proposers.temp_label': 'Температура кандидатов',
|
|
112
|
-
'proposers.temp_hint': 'Рекомендуется 0.6 – 0.8 для получения независимых и разнообразных вариантов.',
|
|
113
|
-
'stats.title': '📈 Аналитика и телеметрия MoA',
|
|
114
|
-
'stats.desc': 'Мониторинг запусков, статистика побед моделей и оценка расхода токенов.',
|
|
115
|
-
'stats.total_runs': 'Всего запусков MoA',
|
|
116
|
-
'stats.avg_cost': 'Средняя стоимость',
|
|
117
|
-
'stats.active_models': 'Настроено моделей',
|
|
118
|
-
'stats.preset_count': 'Всего пресетов',
|
|
119
|
-
'picker.select': 'Выберите модель…',
|
|
120
|
-
'picker.search_placeholder': 'Поиск модели по названию или провайдеру…',
|
|
121
|
-
'picker.found': 'Найдено',
|
|
122
|
-
'picker.of': 'из',
|
|
123
|
-
'picker.clear': 'Сбросить',
|
|
124
|
-
'picker.none': 'Модели не найдены.',
|
|
125
|
-
'picker.custom': 'Использовать: ',
|
|
126
|
-
'actions.save': 'Сохранить настройки',
|
|
127
|
-
'actions.saving': 'Сохранение…',
|
|
128
|
-
'actions.saved': 'Настройки успешно сохранены!',
|
|
129
|
-
'actions.retry': 'Повторить подключение',
|
|
130
|
-
'slash.desc': 'Запуск ансамбля Mixture of Agents (/moa [preset] <prompt>)',
|
|
131
|
-
}
|
|
132
|
-
|
|
133
93
|
function makeT(dict, fallback) {
|
|
134
94
|
return function t(key, vars) {
|
|
135
95
|
let val = (dict && dict[key]) || (fallback && fallback[key]) || key
|
|
@@ -163,7 +123,15 @@ window.__ModuleLoader__.load({
|
|
|
163
123
|
})
|
|
164
124
|
)
|
|
165
125
|
}
|
|
166
|
-
|
|
126
|
+
let Chevron = FallbackChevron
|
|
127
|
+
try {
|
|
128
|
+
const primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
129
|
+
if (primitives && typeof primitives.IconChevronDownOutline14 === 'function') {
|
|
130
|
+
Chevron = primitives.IconChevronDownOutline14
|
|
131
|
+
}
|
|
132
|
+
} catch (noPrimitives) {
|
|
133
|
+
Chevron = FallbackChevron
|
|
134
|
+
}
|
|
167
135
|
|
|
168
136
|
const STYLES = `
|
|
169
137
|
/* dsh-moa modern clinebot-aligned styling */
|
|
@@ -174,8 +142,8 @@ window.__ModuleLoader__.load({
|
|
|
174
142
|
|
|
175
143
|
.moa-badge-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:2px}
|
|
176
144
|
.moa-badge{font-size:12px;padding:3px 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:5px;font-weight:500}
|
|
177
|
-
.moa-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:
|
|
178
|
-
.moa-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:
|
|
145
|
+
.moa-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent)}
|
|
146
|
+
.moa-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 10%, transparent)}
|
|
179
147
|
.moa-badge-brand{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}
|
|
180
148
|
|
|
181
149
|
.moa-section-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:18px 20px;display:flex;flex-direction:column;gap:14px}
|
|
@@ -196,8 +164,8 @@ window.__ModuleLoader__.load({
|
|
|
196
164
|
.moa-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-1));border-color:var(--dsw-alias-label-secondary)}
|
|
197
165
|
.moa-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}
|
|
198
166
|
.moa-btn-primary:hover:not(:disabled){background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important;opacity:0.9}
|
|
199
|
-
.moa-btn-danger{color:var(--dsw-alias-state-error-primary
|
|
200
|
-
.moa-btn-danger:hover:not(:disabled){background:
|
|
167
|
+
.moa-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 35%, transparent)}
|
|
168
|
+
.moa-btn-danger:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent) !important;border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 50%, transparent)}
|
|
201
169
|
.moa-btn-sub{padding:5px 12px;font-size:12px;border-radius:6px}
|
|
202
170
|
|
|
203
171
|
/* Segmented Buttons */
|
|
@@ -223,9 +191,9 @@ window.__ModuleLoader__.load({
|
|
|
223
191
|
.moa-picker-btn{width:100%;min-height:38px;padding:6px 12px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between;cursor:pointer;font-size:13px;text-align:left;box-sizing:border-box;gap:8px}
|
|
224
192
|
.moa-picker-btn:hover{border-color:var(--dsw-alias-label-secondary)}
|
|
225
193
|
.moa-picker-summary{display:flex;align-items:center;gap:8px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
226
|
-
.moa-prov-chip{font-size:11px;font-weight:600;text-transform:uppercase;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-
|
|
194
|
+
.moa-prov-chip{font-size:11px;font-weight:600;text-transform:uppercase;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);flex-shrink:0}
|
|
227
195
|
.moa-model-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}
|
|
228
|
-
.moa-picker-popover{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;box-shadow:0 12px 36px
|
|
196
|
+
.moa-picker-popover{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;box-shadow:0 12px 36px color-mix(in srgb, var(--dsw-alias-label-tertiary) 45%, transparent);z-index:1000;max-height:360px;display:flex;flex-direction:column;overflow:hidden}
|
|
229
197
|
.moa-picker-search-bar{padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:4px}
|
|
230
198
|
.moa-search-input{width:100%;height:32px;padding:0 10px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;color:var(--dsw-alias-label-primary);font-size:13px;outline:none;box-sizing:border-box}
|
|
231
199
|
.moa-search-input:focus{border-color:var(--dsw-alias-label-primary)}
|
|
@@ -233,13 +201,13 @@ window.__ModuleLoader__.load({
|
|
|
233
201
|
.moa-picker-list{overflow-y:auto;max-height:280px;padding:6px;display:flex;flex-direction:column;gap:2px}
|
|
234
202
|
.moa-group-title{font-size:11px;font-weight:700;text-transform:uppercase;color:var(--dsw-alias-label-tertiary);padding:8px 8px 4px}
|
|
235
203
|
.moa-picker-item{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;border-radius:6px;font-size:13px;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;border:none;background:transparent;width:100%;gap:8px}
|
|
236
|
-
.moa-picker-item:hover, .moa-picker-item-selected{background:var(--dsw-alias-interactive-bg-hover,
|
|
237
|
-
.moa-picker-check{color:var(--dsw-alias-state-success-primary
|
|
204
|
+
.moa-picker-item:hover, .moa-picker-item-selected{background:var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2))}
|
|
205
|
+
.moa-picker-check{color:var(--dsw-alias-state-success-primary);font-weight:bold;font-size:14px;flex-shrink:0}
|
|
238
206
|
.moa-custom-opt{padding:8px 10px;border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);font-size:12px;color:var(--dsw-alias-label-primary);cursor:pointer;display:flex;align-items:center;gap:6px}
|
|
239
207
|
|
|
240
208
|
/* Alerts & Collapsible Card */
|
|
241
|
-
.moa-alert-ok{padding:10px 14px;border-radius:8px;background:
|
|
242
|
-
.moa-alert-err{padding:10px 14px;border-radius:8px;background:
|
|
209
|
+
.moa-alert-ok{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent);color:var(--dsw-alias-state-success-primary);font-size:13px;font-weight:500}
|
|
210
|
+
.moa-alert-err{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);color:var(--dsw-alias-state-error-primary);font-size:13px;font-weight:500}
|
|
243
211
|
.moa-card{list-style:none;margin:0;padding:0;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;overflow:hidden;transition:border-color .15s ease}
|
|
244
212
|
.moa-head{width:100%;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;background:transparent;border:none;color:inherit;cursor:pointer;text-align:left}
|
|
245
213
|
.moa-head:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
@@ -476,6 +444,10 @@ window.__ModuleLoader__.load({
|
|
|
476
444
|
defaultPreset,
|
|
477
445
|
setDefaultPreset,
|
|
478
446
|
availableModels,
|
|
447
|
+
enabled,
|
|
448
|
+
setEnabled,
|
|
449
|
+
hostStatus,
|
|
450
|
+
stats,
|
|
479
451
|
handleSave,
|
|
480
452
|
saveStatus,
|
|
481
453
|
reload,
|
|
@@ -624,6 +596,11 @@ window.__ModuleLoader__.load({
|
|
|
624
596
|
const currentRefs = currentPreset.reference_models || []
|
|
625
597
|
const judgeCriteria = currentPreset.judge_criteria || ''
|
|
626
598
|
const isDefault = defaultPreset === currentPreset.name
|
|
599
|
+
const hostStatusBadge = hostStatus === 'online'
|
|
600
|
+
? React.createElement('span', { className: 'moa-badge moa-badge-ok' }, '🟢 ' + t('badge.online'))
|
|
601
|
+
: hostStatus === 'disabled'
|
|
602
|
+
? React.createElement('span', { className: 'moa-badge moa-badge-warn' }, '⏸ ' + t('badge.disabled'))
|
|
603
|
+
: React.createElement('span', { className: 'moa-badge moa-badge-warn' }, '🔴 ' + t('badge.offline'))
|
|
627
604
|
|
|
628
605
|
return React.createElement(
|
|
629
606
|
'div',
|
|
@@ -637,11 +614,11 @@ window.__ModuleLoader__.load({
|
|
|
637
614
|
'div',
|
|
638
615
|
{ className: 'moa-page-title' },
|
|
639
616
|
React.createElement('span', null, '🧠 ' + t('header.title')),
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
617
|
+
React.createElement(
|
|
618
|
+
'div',
|
|
619
|
+
{ className: 'moa-badge-row' },
|
|
620
|
+
hostStatusBadge,
|
|
621
|
+
React.createElement('span', { className: 'moa-badge moa-badge-brand' }, t('badge.preset', { name: currentPreset.name })),
|
|
645
622
|
React.createElement('span', { className: 'moa-badge moa-badge-brand' }, t('badge.models', { count: availableModels.length })),
|
|
646
623
|
React.createElement('span', { className: 'moa-badge moa-badge-brand' }, t('badge.candidates', { count: currentRefs.length }))
|
|
647
624
|
)
|
|
@@ -821,6 +798,109 @@ window.__ModuleLoader__.load({
|
|
|
821
798
|
'div',
|
|
822
799
|
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
823
800
|
t('aggregator.questions_hint')
|
|
801
|
+
),
|
|
802
|
+
React.createElement(
|
|
803
|
+
'label',
|
|
804
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500, marginTop: 8 } },
|
|
805
|
+
React.createElement('input', {
|
|
806
|
+
type: 'checkbox',
|
|
807
|
+
checked: Boolean(currentPreset.curator_synthesis),
|
|
808
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, curator_synthesis: e.target.checked })),
|
|
809
|
+
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
810
|
+
}),
|
|
811
|
+
t('aggregator.curator_label')
|
|
812
|
+
),
|
|
813
|
+
React.createElement(
|
|
814
|
+
'div',
|
|
815
|
+
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
816
|
+
t('aggregator.curator_hint')
|
|
817
|
+
),
|
|
818
|
+
React.createElement(
|
|
819
|
+
'label',
|
|
820
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500, marginTop: 8 } },
|
|
821
|
+
React.createElement('input', {
|
|
822
|
+
type: 'checkbox',
|
|
823
|
+
checked: currentPreset.stream_aggregator !== false,
|
|
824
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, stream_aggregator: e.target.checked })),
|
|
825
|
+
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
826
|
+
}),
|
|
827
|
+
t('aggregator.stream_label')
|
|
828
|
+
),
|
|
829
|
+
React.createElement(
|
|
830
|
+
'div',
|
|
831
|
+
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
832
|
+
t('aggregator.stream_hint')
|
|
833
|
+
),
|
|
834
|
+
/* Fallback Judges Chain */
|
|
835
|
+
React.createElement(
|
|
836
|
+
'div',
|
|
837
|
+
{ style: { marginTop: 10, display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
838
|
+
React.createElement(
|
|
839
|
+
'div',
|
|
840
|
+
{ style: { fontSize: 13, fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } },
|
|
841
|
+
t('aggregator.fallbacks_title')
|
|
842
|
+
),
|
|
843
|
+
React.createElement(
|
|
844
|
+
'div',
|
|
845
|
+
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginTop: -4 } },
|
|
846
|
+
t('aggregator.fallbacks_desc')
|
|
847
|
+
),
|
|
848
|
+
(Array.isArray(currentPreset.aggregator_fallbacks) ? currentPreset.aggregator_fallbacks : []).map((fb, fbIdx) =>
|
|
849
|
+
React.createElement(
|
|
850
|
+
'div',
|
|
851
|
+
{ key: fbIdx, style: { display: 'flex', alignItems: 'center', gap: 8 } },
|
|
852
|
+
React.createElement('span', { className: 'moa-cand-badge' }, `Fallback #${fbIdx + 1}`),
|
|
853
|
+
React.createElement(
|
|
854
|
+
'div',
|
|
855
|
+
{ style: { flex: 1 } },
|
|
856
|
+
React.createElement(SearchableModelPicker, {
|
|
857
|
+
value: fb,
|
|
858
|
+
onChange: (val) => {
|
|
859
|
+
updateCurrentPreset((p) => {
|
|
860
|
+
const list = [...(p.aggregator_fallbacks || [])]
|
|
861
|
+
list[fbIdx] = val
|
|
862
|
+
return { ...p, aggregator_fallbacks: list }
|
|
863
|
+
})
|
|
864
|
+
},
|
|
865
|
+
allModels,
|
|
866
|
+
t,
|
|
867
|
+
})
|
|
868
|
+
),
|
|
869
|
+
React.createElement(
|
|
870
|
+
'button',
|
|
871
|
+
{
|
|
872
|
+
type: 'button',
|
|
873
|
+
className: 'moa-btn moa-btn-danger moa-btn-sub',
|
|
874
|
+
onClick: () => {
|
|
875
|
+
updateCurrentPreset((p) => ({
|
|
876
|
+
...p,
|
|
877
|
+
aggregator_fallbacks: (p.aggregator_fallbacks || []).filter((_, i) => i !== fbIdx),
|
|
878
|
+
}))
|
|
879
|
+
},
|
|
880
|
+
},
|
|
881
|
+
'✕'
|
|
882
|
+
)
|
|
883
|
+
)
|
|
884
|
+
),
|
|
885
|
+
React.createElement(
|
|
886
|
+
'div',
|
|
887
|
+
null,
|
|
888
|
+
React.createElement(
|
|
889
|
+
'button',
|
|
890
|
+
{
|
|
891
|
+
type: 'button',
|
|
892
|
+
className: 'moa-btn moa-btn-sub',
|
|
893
|
+
style: { marginTop: 4 },
|
|
894
|
+
onClick: () => {
|
|
895
|
+
updateCurrentPreset((p) => ({
|
|
896
|
+
...p,
|
|
897
|
+
aggregator_fallbacks: [...(p.aggregator_fallbacks || []), { provider: 'opencode-go', model: 'deepseek-v4-flash' }],
|
|
898
|
+
}))
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
t('aggregator.add_fallback_btn')
|
|
902
|
+
)
|
|
903
|
+
)
|
|
824
904
|
)
|
|
825
905
|
)
|
|
826
906
|
)
|
|
@@ -853,6 +933,43 @@ window.__ModuleLoader__.load({
|
|
|
853
933
|
)
|
|
854
934
|
),
|
|
855
935
|
React.createElement('div', { className: 'moa-section-desc' }, t('proposers.desc')),
|
|
936
|
+
React.createElement(
|
|
937
|
+
'div',
|
|
938
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 12px', background: 'var(--dsw-alias-bg-layer-2)', borderRadius: 8, border: '1px solid var(--dsw-alias-border-l2)' } },
|
|
939
|
+
React.createElement(
|
|
940
|
+
'label',
|
|
941
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500 } },
|
|
942
|
+
React.createElement('input', {
|
|
943
|
+
type: 'checkbox',
|
|
944
|
+
checked: Boolean(currentPreset.quorum_enabled),
|
|
945
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, quorum_enabled: e.target.checked })),
|
|
946
|
+
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
947
|
+
}),
|
|
948
|
+
t('proposers.quorum_label')
|
|
949
|
+
),
|
|
950
|
+
React.createElement(
|
|
951
|
+
'div',
|
|
952
|
+
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
953
|
+
t('proposers.quorum_hint')
|
|
954
|
+
),
|
|
955
|
+
currentPreset.quorum_enabled && React.createElement(
|
|
956
|
+
'div',
|
|
957
|
+
{ style: { marginLeft: 22, display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 } },
|
|
958
|
+
React.createElement('span', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)' } }, t('proposers.grace_label')),
|
|
959
|
+
React.createElement('input', {
|
|
960
|
+
type: 'number',
|
|
961
|
+
className: 'moa-input',
|
|
962
|
+
style: { width: 70, height: 28, padding: '0 6px', fontSize: 12 },
|
|
963
|
+
min: 2,
|
|
964
|
+
max: 60,
|
|
965
|
+
value: currentPreset.grace_period_sec ?? 10,
|
|
966
|
+
onChange: (e) => {
|
|
967
|
+
const val = parseInt(e.target.value, 10)
|
|
968
|
+
updateCurrentPreset((p) => ({ ...p, grace_period_sec: isNaN(val) ? 10 : val }))
|
|
969
|
+
},
|
|
970
|
+
})
|
|
971
|
+
)
|
|
972
|
+
),
|
|
856
973
|
React.createElement(
|
|
857
974
|
'div',
|
|
858
975
|
{ className: 'moa-cand-list' },
|
|
@@ -940,18 +1057,37 @@ window.__ModuleLoader__.load({
|
|
|
940
1057
|
React.createElement(
|
|
941
1058
|
'div',
|
|
942
1059
|
{ className: 'moa-stat-box' },
|
|
943
|
-
React.createElement('div', { className: 'moa-stat-val' }, String(
|
|
944
|
-
React.createElement('div', { className: 'moa-stat-lbl' }, t('stats.
|
|
1060
|
+
React.createElement('div', { className: 'moa-stat-val' }, (stats.totalRuns === null || stats.totalRuns === undefined) ? '—' : String(stats.totalRuns)),
|
|
1061
|
+
React.createElement('div', { className: 'moa-stat-lbl' }, t('stats.total_runs'))
|
|
945
1062
|
),
|
|
946
1063
|
React.createElement(
|
|
947
1064
|
'div',
|
|
948
1065
|
{ className: 'moa-stat-box' },
|
|
949
|
-
React.createElement('div', { className: 'moa-stat-val' },
|
|
950
|
-
React.createElement('div', { className: 'moa-stat-lbl' }, t('
|
|
1066
|
+
React.createElement('div', { className: 'moa-stat-val' }, (stats.avgCostUsd === null || stats.avgCostUsd === undefined) ? '—' : `~\$${stats.avgCostUsd.toFixed(4)}`),
|
|
1067
|
+
React.createElement('div', { className: 'moa-stat-lbl' }, t('stats.avg_cost'))
|
|
1068
|
+
),
|
|
1069
|
+
React.createElement(
|
|
1070
|
+
'div',
|
|
1071
|
+
{ className: 'moa-stat-box' },
|
|
1072
|
+
React.createElement('div', { className: 'moa-stat-val' }, String(currentRefs.length)),
|
|
1073
|
+
React.createElement('div', { className: 'moa-stat-lbl' }, t('stats.active_models'))
|
|
951
1074
|
)
|
|
952
1075
|
)
|
|
953
1076
|
),
|
|
954
1077
|
|
|
1078
|
+
/* ── ENABLED TOGGLE ── */
|
|
1079
|
+
React.createElement(
|
|
1080
|
+
'label',
|
|
1081
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500, alignSelf: 'flex-start' } },
|
|
1082
|
+
React.createElement('input', {
|
|
1083
|
+
type: 'checkbox',
|
|
1084
|
+
checked: enabled !== false,
|
|
1085
|
+
onChange: (e) => setEnabled(e.target.checked),
|
|
1086
|
+
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
1087
|
+
}),
|
|
1088
|
+
t('config.enabled_label')
|
|
1089
|
+
),
|
|
1090
|
+
|
|
955
1091
|
/* ── FOOTER ACTIONS & SAVE ── */
|
|
956
1092
|
React.createElement(
|
|
957
1093
|
'div',
|
|
@@ -989,7 +1125,9 @@ window.__ModuleLoader__.load({
|
|
|
989
1125
|
|
|
990
1126
|
function useMoASettings(props) {
|
|
991
1127
|
const effectiveCtx = (props && props.ctx) || rootCtx
|
|
992
|
-
|
|
1128
|
+
// English is canonical; prefer the core-provided translator for the
|
|
1129
|
+
// active UI language (the slot declares locale: NS), fall back to en.
|
|
1130
|
+
const t = typeof props?.t === 'function' ? props.t : makeT(en, null)
|
|
993
1131
|
|
|
994
1132
|
const scope = React.useMemo(() => {
|
|
995
1133
|
if (!effectiveCtx) return undefined
|
|
@@ -1010,6 +1148,9 @@ window.__ModuleLoader__.load({
|
|
|
1010
1148
|
const [defaultPreset, setDefaultPreset] = React.useState('default')
|
|
1011
1149
|
const [availableModels, setAvailableModels] = React.useState([])
|
|
1012
1150
|
const [saveStatus, setSaveStatus] = React.useState('')
|
|
1151
|
+
const [enabled, setEnabled] = React.useState(true)
|
|
1152
|
+
const [hostStatus, setHostStatus] = React.useState('connecting')
|
|
1153
|
+
const [stats, setStats] = React.useState({ totalRuns: null, avgCostUsd: null })
|
|
1013
1154
|
|
|
1014
1155
|
React.useEffect(() => {
|
|
1015
1156
|
if (!scope || !snapshot) return
|
|
@@ -1018,6 +1159,7 @@ window.__ModuleLoader__.load({
|
|
|
1018
1159
|
const val = snapshot.value || {}
|
|
1019
1160
|
if (Array.isArray(val.presets)) setPresets(val.presets)
|
|
1020
1161
|
if (val.default_preset) setDefaultPreset(val.default_preset)
|
|
1162
|
+
if (typeof val.enabled === 'boolean') setEnabled(val.enabled)
|
|
1021
1163
|
setStatus('ready')
|
|
1022
1164
|
} else if (snapStatus === 'unavailable') {
|
|
1023
1165
|
setStatus('unavailable')
|
|
@@ -1027,6 +1169,35 @@ window.__ModuleLoader__.load({
|
|
|
1027
1169
|
const reload = React.useCallback(() => {
|
|
1028
1170
|
if (!scope) setStatus('loading')
|
|
1029
1171
|
|
|
1172
|
+
fetch('/dsh-moa/status', { cache: 'no-store' })
|
|
1173
|
+
.then((r) => r.json())
|
|
1174
|
+
.then((data) => {
|
|
1175
|
+
if (data && data.ok) {
|
|
1176
|
+
setHostStatus(data.enabled === false ? 'disabled' : 'online')
|
|
1177
|
+
} else {
|
|
1178
|
+
setHostStatus('offline')
|
|
1179
|
+
}
|
|
1180
|
+
})
|
|
1181
|
+
.catch(() => {
|
|
1182
|
+
setHostStatus('offline')
|
|
1183
|
+
})
|
|
1184
|
+
|
|
1185
|
+
fetch('/dsh-moa/history?limit=100', { cache: 'no-store' })
|
|
1186
|
+
.then((r) => r.json())
|
|
1187
|
+
.then((data) => {
|
|
1188
|
+
if (data && data.ok && Array.isArray(data.runs)) {
|
|
1189
|
+
const runs = data.runs
|
|
1190
|
+
const withCost = runs.filter((run) => typeof run.totalCostUsd === 'number')
|
|
1191
|
+
const avgCostUsd = withCost.length > 0
|
|
1192
|
+
? withCost.reduce((acc, run) => acc + run.totalCostUsd, 0) / withCost.length
|
|
1193
|
+
: null
|
|
1194
|
+
setStats({ totalRuns: data.total || runs.length, avgCostUsd })
|
|
1195
|
+
}
|
|
1196
|
+
})
|
|
1197
|
+
.catch((err) => {
|
|
1198
|
+
console.warn('[dsh-moa] History fetch failed:', err)
|
|
1199
|
+
})
|
|
1200
|
+
|
|
1030
1201
|
fetch('/dsh-moa/presets', { cache: 'no-store' })
|
|
1031
1202
|
.then((r) => {
|
|
1032
1203
|
if (!r.ok) throw new Error(`HTTP ${r.status}`)
|
|
@@ -1067,6 +1238,7 @@ window.__ModuleLoader__.load({
|
|
|
1067
1238
|
|
|
1068
1239
|
if (scope && typeof scope.set === 'function') {
|
|
1069
1240
|
Promise.all([
|
|
1241
|
+
scope.set('enabled', enabled),
|
|
1070
1242
|
scope.set('presets', presets),
|
|
1071
1243
|
scope.set('default_preset', defaultPreset),
|
|
1072
1244
|
])
|
|
@@ -1088,7 +1260,7 @@ window.__ModuleLoader__.load({
|
|
|
1088
1260
|
fetch('/dsh-moa/presets', {
|
|
1089
1261
|
method: 'POST',
|
|
1090
1262
|
headers: { 'Content-Type': 'application/json' },
|
|
1091
|
-
body: JSON.stringify({ presets, default_preset: defaultPreset }),
|
|
1263
|
+
body: JSON.stringify({ enabled, presets, default_preset: defaultPreset }),
|
|
1092
1264
|
})
|
|
1093
1265
|
.then((r) => r.json())
|
|
1094
1266
|
.then((res) => {
|
|
@@ -1114,6 +1286,10 @@ window.__ModuleLoader__.load({
|
|
|
1114
1286
|
defaultPreset,
|
|
1115
1287
|
setDefaultPreset,
|
|
1116
1288
|
availableModels,
|
|
1289
|
+
enabled,
|
|
1290
|
+
setEnabled,
|
|
1291
|
+
hostStatus,
|
|
1292
|
+
stats,
|
|
1117
1293
|
handleSave,
|
|
1118
1294
|
saveStatus,
|
|
1119
1295
|
reload,
|
|
@@ -1227,7 +1403,7 @@ window.__ModuleLoader__.load({
|
|
|
1227
1403
|
|
|
1228
1404
|
if (typeof ctx.effect === 'function') {
|
|
1229
1405
|
ctx.effect(() => {
|
|
1230
|
-
const undo = [addLocale('en', en)
|
|
1406
|
+
const undo = [addLocale('en', en)]
|
|
1231
1407
|
return () => {
|
|
1232
1408
|
for (const off of undo) {
|
|
1233
1409
|
if (typeof off === 'function') {
|
|
@@ -1238,7 +1414,6 @@ window.__ModuleLoader__.load({
|
|
|
1238
1414
|
}, 'dsh-moa: dictionaries')
|
|
1239
1415
|
} else {
|
|
1240
1416
|
addLocale('en', en)
|
|
1241
|
-
addLocale('ru', ru)
|
|
1242
1417
|
}
|
|
1243
1418
|
|
|
1244
1419
|
if (ctx.slots) {
|
|
@@ -1275,7 +1450,7 @@ window.__ModuleLoader__.load({
|
|
|
1275
1450
|
trigger: '/',
|
|
1276
1451
|
name: 'moa',
|
|
1277
1452
|
order: 35,
|
|
1278
|
-
description:
|
|
1453
|
+
description: en['slash.desc'],
|
|
1279
1454
|
candidates: (_session, req) => {
|
|
1280
1455
|
if (req.position !== 'leading') return Promise.resolve([])
|
|
1281
1456
|
const q = req.query.trim().toLowerCase()
|
|
@@ -1283,7 +1458,7 @@ window.__ModuleLoader__.load({
|
|
|
1283
1458
|
return Promise.resolve([
|
|
1284
1459
|
{
|
|
1285
1460
|
name: 'moa',
|
|
1286
|
-
description:
|
|
1461
|
+
description: en['slash.desc'],
|
|
1287
1462
|
},
|
|
1288
1463
|
])
|
|
1289
1464
|
},
|
|
@@ -1308,4 +1483,4 @@ window.__ModuleLoader__.load({
|
|
|
1308
1483
|
return module.exports
|
|
1309
1484
|
},
|
|
1310
1485
|
})
|
|
1311
|
-
|
|
1486
|
+
|
package/lib/file-workspace.js
CHANGED
|
@@ -107,7 +107,9 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
|
|
|
107
107
|
const list = await fs.readdir(currentDir, { withFileTypes: true })
|
|
108
108
|
for (const item of list) {
|
|
109
109
|
if (totalChars >= maxCharBudget) break
|
|
110
|
-
|
|
110
|
+
// Skip all dotfiles, including .env and .env.* variants: their
|
|
111
|
+
// contents must never be shipped to third-party model providers.
|
|
112
|
+
if (item.name.startsWith('.')) continue
|
|
111
113
|
|
|
112
114
|
const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name
|
|
113
115
|
const full = path.join(currentDir, item.name)
|