@goodandready/dsh-moa 0.2.11 → 0.2.13
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 +49 -2
- package/README.ru.md +287 -0
- package/README.zh.md +243 -0
- package/docs/README.ru.md +48 -1
- package/docs/README.zh.md +11 -1
- package/docs/design/DESIGN.md +3 -0
- package/docs/plans/59-power-pack-plan.md +73 -0
- package/lib/client.js +225 -0
- package/lib/file-workspace.js +46 -0
- package/lib/history.js +56 -2
- package/lib/index.js +87 -2
- package/lib/moa-parser.js +24 -0
- package/lib/moa-prompts.js +93 -10
- package/lib/moa-runner.js +68 -53
- package/package.json +1 -1
package/docs/README.ru.md
CHANGED
|
@@ -16,11 +16,21 @@
|
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
|
-
<a href="
|
|
19
|
+
<a href="README.md"><b>🇬🇧 English</b></a> •
|
|
20
20
|
<a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
|
|
21
21
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
+
<table align="center">
|
|
25
|
+
<tr>
|
|
26
|
+
<td align="center">
|
|
27
|
+
⭐ <strong>Если вам нравится этот плагин, поставьте ему звезду на GitHub</strong> — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.
|
|
28
|
+
<br><br>
|
|
29
|
+
🐛 <strong>Если вы нашли баг или хотите предложить новый функционал</strong>, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</table>
|
|
33
|
+
|
|
24
34
|
</div>
|
|
25
35
|
|
|
26
36
|
---
|
|
@@ -143,8 +153,44 @@ dsh plugin --profile web add @goodandready/dsh-moa
|
|
|
143
153
|
|
|
144
154
|
---
|
|
145
155
|
|
|
156
|
+
## ⚡ 10 встроенных пресетов и инженерные персоны
|
|
157
|
+
|
|
158
|
+
В версии v0.2.13 добавлены 10 специализированных пресетов под реальные задачи разработки:
|
|
159
|
+
|
|
160
|
+
| Имя пресета | Назначение | Судья по умолчанию | Рецензия (Round 2) | Слепая оценка |
|
|
161
|
+
| :--- | :--- | :--- | :---: | :---: |
|
|
162
|
+
| `default` | Сбалансированная генерация | `codex:gpt-5.6-sol` | Опционально | Выкл |
|
|
163
|
+
| `code-review` | Глубокое взаимное ревью кода | `codex:gpt-5.6-sol` | Вкл | Вкл |
|
|
164
|
+
| `fast-audit` | Сверхбыстрый экспресс-аудит (Fast Mode) | `codex:gpt-5.6-sol` | Выкл | Выкл |
|
|
165
|
+
| `deep-architect` | Проектирование распределенных систем | `codex:gpt-5.6-sol` | Вкл | Выкл |
|
|
166
|
+
| `bug-hunter` | Поиск скрытых багов и граничных случаев | `codex:gpt-5.6-sol` | Вкл | Выкл |
|
|
167
|
+
| `refactor-cleanup` | Чистка легаси и удаление оверинжиниринга | `codex:gpt-5.6-sol` | Выкл | Выкл |
|
|
168
|
+
| `frontend-ui` | Качественные адаптивные веб-интерфейсы | `codex:gpt-5.6-sol` | Выкл | Выкл |
|
|
169
|
+
| `security-audit` | Анализ уязвимостей и санитайзинг | `codex:gpt-5.6-sol` | Вкл | Вкл |
|
|
170
|
+
| `math-logic` | Алгоритмы, математическая логика и доказательства | `codex:gpt-5.6-sol` | Вкл | Выкл |
|
|
171
|
+
| `creative-brainstorm`| Нестандартные идеи и дивергентный поиск | `codex:gpt-5.6-sol` | Выкл | Выкл |
|
|
172
|
+
|
|
173
|
+
### Инженерные персоны кандидатов (`role_persona`)
|
|
174
|
+
Задавайте кандидатам четкие роли для максимального разнообразия подходов:
|
|
175
|
+
- **`minimalist` (Ponytail)**: решение на стандартной библиотеке, 0 лишних зависимостей, минимальный код.
|
|
176
|
+
- **`robustness`**: защитное программирование, валидация границ, идемпотентность, устойчивость к сбоям.
|
|
177
|
+
- **`performance`**: минимальная алгоритмическая сложность, экономия памяти, zero-copy.
|
|
178
|
+
- **`tester`**: test-driven архитектура, 100% покрытие веток, четкие ассерты.
|
|
179
|
+
- **`general`**: сбалансированный подход общего назначения.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 🤝 Консилиум Раунд 2, Синтаксический гейт и Override
|
|
184
|
+
|
|
185
|
+
- **Консилиум (Раунд 2)**: флаг `peer_critique_enabled: true` включает этап взаимного ревью, в котором кандидаты изучают предложения оппонентов и дорабатывают код перед финальным судейством.
|
|
186
|
+
- **Синтаксический гейт**: мгновенная валидация JS/JSON через `node:vm` и `JSON.parse`. Если в коде кандидата есть опечатка, он помечается `[⚠️ Syntax Warning]`, а судья получает жесткую директиву: *если архитектура кандидата превосходит конкурентов, исправить синтаксис в финальном решении и присудить ему победу*.
|
|
187
|
+
- **Пользовательский выбор кандидата (Override)**: флаг `allow_candidate_override: true` сохраняет папки `.moa/candidate-N/`. Любой кандидат может быть повышен в проект командой `/moa promote <runId> <candidateIndex>` или кнопкой в карточке настроек.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
146
191
|
## ⚙️ Конфигурация (`settings.yaml`)
|
|
147
192
|
|
|
193
|
+
|
|
148
194
|
Настройка пресетов и пайплайнов моделей доступна в `settings.yaml` или через интерфейс (Настройки → Плагины → Mixture of Agents):
|
|
149
195
|
|
|
150
196
|
```yaml
|
|
@@ -199,6 +245,7 @@ dsh-moa:
|
|
|
199
245
|
| `presets[].quorum_enabled` | `boolean` | `false` | Защита от зависших моделей (stragglers): запуск синтеза при ответе от >= 60% кандидатов |
|
|
200
246
|
| `presets[].grace_period_sec` | `number` | `10` | Грейс-период (в секундах) ожидания оставшихся моделей после достижения кворума |
|
|
201
247
|
| `presets[].aggregator_fallbacks` | `array` | `[]` | Список запасных моделей-судей при сбоях основной модели агрегатора |
|
|
248
|
+
| `presets[].blind_evaluation` | `boolean` | `false` | Обезличивание имен кандидатов («Candidate 1», «Candidate 2») для исключения предвзятости судьи |
|
|
202
249
|
| `presets[].reference_timeout_sec` | `number` | `60` | Таймаут опроса каждого кандидата в секундах |
|
|
203
250
|
| `presets[].aggregator_timeout_sec` | `number` | `180` | Таймаут синтеза решения судьей в секундах |
|
|
204
251
|
| `presets[].reference_temperature` / `.aggregator_temperature` | `number` | `0.6` / `0.4` | Температуры сэмплирования советников и судьи |
|
package/docs/README.zh.md
CHANGED
|
@@ -16,11 +16,21 @@
|
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
|
-
<a href="
|
|
19
|
+
<a href="README.md"><b>🇬🇧 English</b></a> •
|
|
20
20
|
<a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
|
|
21
21
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
+
<table align="center">
|
|
25
|
+
<tr>
|
|
26
|
+
<td align="center">
|
|
27
|
+
⭐ <strong>如果您喜欢这个插件,请在 GitHub 上为它点亮 Star</strong> — 这能让我知道插件对您有用,并鼓励我继续开发和维护它。
|
|
28
|
+
<br><br>
|
|
29
|
+
🐛 <strong>如果您发现 Bug 或希望增加功能</strong>,请使用任意语言在 GitHub 上提交 Issue — 我会评估您的建议,并在后续版本中实现有价值的改进。
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</table>
|
|
33
|
+
|
|
24
34
|
</div>
|
|
25
35
|
|
|
26
36
|
---
|
package/docs/design/DESIGN.md
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
- 2026-09-10 — Унификация UI с дизайн-стандартом `dsh-clinebot` (статусные чипы в header, секционные карточки, дизайн-токены `--dsw-alias-*`, глубокий аудит устойчивости).
|
|
59
59
|
- 2026-09-11 — English-canonical пользовательские строки (сервер и клиент); ru-перевод предоставляет translation-плагин, собственный ru-дубль из пакета удалён. Причина: стандарт DSH (dsh-plugin-authoring); пересмотр — только по явному решению владельца.
|
|
60
60
|
- 2026-09-11 — Заглушка Live Canvas удалена (фабрикация `http://localhost:3000/preview/...`); заявления README сняты. Реальная интеграция с `dsh-live-canvas` — отдельная задача (Gitea #46). Changed: прежний пункт про автопревью больше не действует.
|
|
61
|
+
- 2026-09-12 — Реализован режим слепого судейства (`blind_evaluation: boolean`), гарантированное зеркалирование языка запроса (Language Mirroring Guard в промптах судьи и куратора), интерактивный лидерборд моделей (винрейт, запуски, победы, средняя стоимость) в секции аналитики UI и раздельные настройки таймаутов кандидатов и судьи в карточке настроек (Gitea Issue #57).
|
|
61
62
|
- 2026-09-11 — Статусный бейдж карточки отражает фактический `GET /dsh-moa/status` (online / offline / disabled); телеметрия Total Runs / Avg Run Cost берётся из `GET /dsh-moa/history`. Причина: карточка не должна показывать состояния, которые она не проверяла.
|
|
62
63
|
- 2026-09-11 — Переключатель `enabled` в карточке сохраняется через `settingsScope`/REST и влияет на `/moa`-turn и `POST /dsh-moa/run`.
|
|
63
64
|
- 2026-09-11 (вечер) — Интеграция с Live Canvas реализована через собственный REST-контракт `@goodandready/dsh-live-canvas` (`POST /dsh-live-canvas/api/preview`, self-call на порт хоста `ctx.webServer.port`) с тихой деградацией при отсутствии плагина (любая ошибка → ответ без preview-ссылки). Заменяет прежнее решение об удалении фабрикованной заглушки: ссылка `/dsh-live-canvas/sandbox/<id>` теперь создаётся реальной песочницей.
|
|
@@ -66,3 +67,5 @@
|
|
|
66
67
|
- 2026-09-12 — Реализация кураторского синтеза (`curator_synthesis`), строгой рубрики антипаттернов (`ANTIPATTERNS_RUBRIC`), живого потокового стриминга куратора (`stream_aggregator`), кворума кандидатов с льготным периодом (`quorum_enabled`, `grace_period_sec`), авторетрая транзиентных ошибок (`callWithTransientRetry`) и цепочки запасных судей (`aggregator_fallbacks`). Все опции конфигурируются в пресетах с сохранением 100% обратной совместимости.
|
|
67
68
|
|
|
68
69
|
- 2026-09-12 (вечер) — Декомпозиция lib/moa-runner.js на специализированные модули (`lib/moa-prompts.js`, `lib/moa-parser.js`, `lib/moa-runner.js`) с соблюдением канонического лимита <= 800 строк. Реализация автоматической ротации истории при превышении 10 МБ (`rotateHistoryFileIfNeeded`) и неблокирующего асинхронного сохранения (`recordMoaRunAsync`). Настраиваемые таймауты кандидатов и агрегатора (`reference_timeout_sec`, `aggregator_timeout_sec`).
|
|
70
|
+
|
|
71
|
+
- 2026-09-13 — Расширение функционала MoA (Gitea Issue #59): интерактивный селектор альтернативного кандидата в чате (User Candidate Override: allow_candidate_override), пакет из 10 специализированных пресетов с ролями кандидатов (role_persona), локальный гейт синтаксической проверки (Syntax Pre-check Gate) с директивой автоисправления синтаксиса судьёй, режим взаимного рецензирования («Консилиум» / Peer Critique Round 2: peer_critique_enabled), фильтрация лидерборда по пресетам и выгрузка телеметрии в CSV/JSON.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Plan 59: MoA Power Pack v0.2.13
|
|
2
|
+
|
|
3
|
+
## Objectives
|
|
4
|
+
Implement the 5 approved feature expansions for `@goodandready/dsh-moa`:
|
|
5
|
+
1. User Candidate Override (`allow_candidate_override: boolean`).
|
|
6
|
+
2. 10 specialized built-in presets + candidate role personas (`role_persona`).
|
|
7
|
+
3. Syntax Pre-check Gate with Judge Auto-Fix Directive.
|
|
8
|
+
4. Consilium / Peer Critique Round 2 (`peer_critique_enabled: boolean`).
|
|
9
|
+
5. Analytics filtering by preset and CSV/JSON export.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Technical Specifications
|
|
14
|
+
|
|
15
|
+
### 1. User Candidate Override
|
|
16
|
+
- **Schema**: `PresetSchema` field `allow_candidate_override: z.boolean().default(false)`.
|
|
17
|
+
- **Backend**:
|
|
18
|
+
- `writeCandidateWorkspace` stores candidates in `.moa-workspaces/<runId>/candidate-<index>`.
|
|
19
|
+
- Add API route `POST /dsh-moa/promote` with `{ runId, candidateIndex }` that promotes specified candidate files to the project root.
|
|
20
|
+
- In `formatMoAResponse`, if enabled, render interactive action hints:
|
|
21
|
+
`🔄 Alternative Candidate Override: /moa promote <runId> <candidateIndex>`
|
|
22
|
+
- **Client**: Add toggle in Section 1 / Section 3 of `lib/client.js`.
|
|
23
|
+
|
|
24
|
+
### 2. 10 Built-in Presets & Candidate Role Personas
|
|
25
|
+
- **Presets**:
|
|
26
|
+
1. `default`: General MoA ensemble.
|
|
27
|
+
2. `code-review`: Zero regressions, backward compatibility, edge cases.
|
|
28
|
+
3. `fast-audit`: Quorum mitigation, low latency.
|
|
29
|
+
4. `deep-architect`: System architecture, API contracts, curator synthesis.
|
|
30
|
+
5. `bug-hunter`: Concurrency, race conditions, memory leaks, null safety.
|
|
31
|
+
6. `refactor-cleanup`: Ponytail minimalism, stdlib first, zero external dependencies.
|
|
32
|
+
7. `frontend-ui`: Ergonomics, DSH design tokens, responsive layout, no AI slop.
|
|
33
|
+
8. `security-audit`: OWASP Top 10, input sanitization, safe error handling.
|
|
34
|
+
9. `math-logic`: Algorithmic correctness, asymptotic proofs.
|
|
35
|
+
10. `creative-brainstorm`: High divergence, alternative architectures.
|
|
36
|
+
- **Candidate Roles (`role_persona`)**:
|
|
37
|
+
- `minimalist`: Ponytail style, stdlib only, fewest lines.
|
|
38
|
+
- `robustness`: Paranoid validation, edge cases, defensive typing.
|
|
39
|
+
- `performance`: Memory efficiency, asymptotic complexity.
|
|
40
|
+
- `tester`: TDD, unit test coverage, mock isolation.
|
|
41
|
+
- `general`: Standard expert proposer.
|
|
42
|
+
|
|
43
|
+
### 3. Syntax Pre-check Gate with Judge Auto-fix Directive
|
|
44
|
+
- **Checker**:
|
|
45
|
+
- JS/MJS/CJS: `new vm.Script(code)` from built-in `node:vm` (instant, memory-safe).
|
|
46
|
+
- JSON: `JSON.parse(code)`.
|
|
47
|
+
- Python: `python3 -m py_compile`.
|
|
48
|
+
- **Judge Directive**:
|
|
49
|
+
- If a candidate has a syntax warning, inform the judge:
|
|
50
|
+
`CRITICAL JUDGE DIRECTIVE: If Candidate X has the best overall architecture or solution despite this syntax error, DO NOT reject them! You MUST correct the syntax error directly during final code synthesis and declare Candidate X the winner.`
|
|
51
|
+
|
|
52
|
+
### 4. Consilium / Peer Critique Round 2
|
|
53
|
+
- **Schema**: `peer_critique_enabled: z.boolean().default(false)`.
|
|
54
|
+
- **Pipeline**:
|
|
55
|
+
- If enabled and `candidates.length >= 2`, run Round 2:
|
|
56
|
+
- Pass anonymized candidate proposals to each proposer.
|
|
57
|
+
- Collect refined solutions with peer critique.
|
|
58
|
+
- Supply improved proposals to Judge/Curator.
|
|
59
|
+
|
|
60
|
+
### 5. Analytics Filtering & CSV/JSON Export
|
|
61
|
+
- **Leaderboard Filter**: `getMoaLeaderboard(historyFilePath, limit, filterPreset)`.
|
|
62
|
+
- **Export Endpoints**:
|
|
63
|
+
- `GET /dsh-moa/leaderboard?preset=<name>&format=json|csv`.
|
|
64
|
+
- **Client UI**:
|
|
65
|
+
- Dropdown filter by preset in Section 4.
|
|
66
|
+
- `Export CSV` / `Export JSON` action buttons.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Verification
|
|
71
|
+
- Unit tests for all 5 features in `test/moa-power-pack.test.mjs`.
|
|
72
|
+
- Total test suite passing with 0 regressions.
|
|
73
|
+
- Live deployment to `/home/vadim/.dsh/profiles/web` on MiniPC and endpoint verification.
|
package/lib/client.js
CHANGED
|
@@ -55,6 +55,26 @@ window.__ModuleLoader__.load({
|
|
|
55
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
56
|
'aggregator.stream_label': 'Live Stream Curator/Judge Thinking',
|
|
57
57
|
'aggregator.stream_hint': 'Streams aggregator tokens directly to chat in real time for zero-latency initial response.',
|
|
58
|
+
'aggregator.peer_label': 'Consilium: Peer Critique (Round 2)',
|
|
59
|
+
'aggregator.peer_hint': 'Candidates critique each other\'s solutions and refine their code before final judge synthesis.',
|
|
60
|
+
'aggregator.override_label': 'Allow Candidate Override Actions',
|
|
61
|
+
'aggregator.override_hint': 'Keeps candidate workspaces in .moa to let you apply any candidate files via chat command.',
|
|
62
|
+
'proposers.role_label': 'Persona:',
|
|
63
|
+
'leaderboard.filter_all': 'All Presets',
|
|
64
|
+
'leaderboard.export_csv': 'Export CSV',
|
|
65
|
+
'leaderboard.export_json': 'Export JSON',
|
|
66
|
+
'aggregator.blind_label': 'Blind Review (Anonymize Candidates for Judge)',
|
|
67
|
+
'aggregator.blind_hint': 'When enabled: candidate model names and providers are hidden from the judge prompt to prevent family/brand bias.',
|
|
68
|
+
'aggregator.timeout_label': 'Judge synthesis timeout (sec):',
|
|
69
|
+
'proposers.timeout_label': 'Candidate timeout (sec):',
|
|
70
|
+
'leaderboard.title': '🏆 Model Win-Rate Leaderboard',
|
|
71
|
+
'leaderboard.desc': 'Performance metrics calculated from recorded MoA run verdicts.',
|
|
72
|
+
'leaderboard.model': 'Model',
|
|
73
|
+
'leaderboard.runs': 'Runs',
|
|
74
|
+
'leaderboard.wins': 'Wins',
|
|
75
|
+
'leaderboard.winrate': 'Win Rate',
|
|
76
|
+
'leaderboard.cost': 'Avg Cost',
|
|
77
|
+
'leaderboard.empty': 'No historical runs recorded yet.',
|
|
58
78
|
'aggregator.fallbacks_title': 'Fallback Judges Chain',
|
|
59
79
|
'aggregator.fallbacks_desc': 'Sequential backup models invoked automatically if the primary judge experiences rate limits or outages.',
|
|
60
80
|
'aggregator.add_fallback_btn': '+ Add Fallback Judge',
|
|
@@ -448,6 +468,9 @@ window.__ModuleLoader__.load({
|
|
|
448
468
|
setEnabled,
|
|
449
469
|
hostStatus,
|
|
450
470
|
stats,
|
|
471
|
+
leaderboard = [],
|
|
472
|
+
leaderboardPresetFilter = 'all',
|
|
473
|
+
setLeaderboardPresetFilter = () => {},
|
|
451
474
|
handleSave,
|
|
452
475
|
saveStatus,
|
|
453
476
|
reload,
|
|
@@ -831,6 +854,71 @@ window.__ModuleLoader__.load({
|
|
|
831
854
|
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
832
855
|
t('aggregator.stream_hint')
|
|
833
856
|
),
|
|
857
|
+
React.createElement(
|
|
858
|
+
'label',
|
|
859
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500, marginTop: 8 } },
|
|
860
|
+
React.createElement('input', {
|
|
861
|
+
type: 'checkbox',
|
|
862
|
+
checked: Boolean(currentPreset.blind_evaluation),
|
|
863
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, blind_evaluation: e.target.checked })),
|
|
864
|
+
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
865
|
+
}),
|
|
866
|
+
t('aggregator.blind_label')
|
|
867
|
+
),
|
|
868
|
+
React.createElement(
|
|
869
|
+
'div',
|
|
870
|
+
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
871
|
+
t('aggregator.blind_hint')
|
|
872
|
+
),
|
|
873
|
+
React.createElement(
|
|
874
|
+
'div',
|
|
875
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 } },
|
|
876
|
+
React.createElement('span', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)' } }, t('aggregator.timeout_label')),
|
|
877
|
+
React.createElement('input', {
|
|
878
|
+
type: 'number',
|
|
879
|
+
className: 'moa-input',
|
|
880
|
+
style: { width: 80, height: 28, padding: '0 6px', fontSize: 12 },
|
|
881
|
+
min: 30,
|
|
882
|
+
max: 600,
|
|
883
|
+
value: currentPreset.aggregator_timeout_sec ?? 180,
|
|
884
|
+
onChange: (e) => {
|
|
885
|
+
const val = parseInt(e.target.value, 10)
|
|
886
|
+
updateCurrentPreset((p) => ({ ...p, aggregator_timeout_sec: isNaN(val) ? 180 : val }))
|
|
887
|
+
},
|
|
888
|
+
})
|
|
889
|
+
),
|
|
890
|
+
React.createElement(
|
|
891
|
+
'label',
|
|
892
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500, marginTop: 8 } },
|
|
893
|
+
React.createElement('input', {
|
|
894
|
+
type: 'checkbox',
|
|
895
|
+
checked: Boolean(currentPreset.peer_critique_enabled),
|
|
896
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, peer_critique_enabled: e.target.checked })),
|
|
897
|
+
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
898
|
+
}),
|
|
899
|
+
t('aggregator.peer_label')
|
|
900
|
+
),
|
|
901
|
+
React.createElement(
|
|
902
|
+
'div',
|
|
903
|
+
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
904
|
+
t('aggregator.peer_hint')
|
|
905
|
+
),
|
|
906
|
+
React.createElement(
|
|
907
|
+
'label',
|
|
908
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500, marginTop: 8 } },
|
|
909
|
+
React.createElement('input', {
|
|
910
|
+
type: 'checkbox',
|
|
911
|
+
checked: Boolean(currentPreset.allow_candidate_override),
|
|
912
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, allow_candidate_override: e.target.checked })),
|
|
913
|
+
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
914
|
+
}),
|
|
915
|
+
t('aggregator.override_label')
|
|
916
|
+
),
|
|
917
|
+
React.createElement(
|
|
918
|
+
'div',
|
|
919
|
+
{ style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginLeft: 22 } },
|
|
920
|
+
t('aggregator.override_hint')
|
|
921
|
+
),
|
|
834
922
|
/* Fallback Judges Chain */
|
|
835
923
|
React.createElement(
|
|
836
924
|
'div',
|
|
@@ -968,6 +1056,23 @@ window.__ModuleLoader__.load({
|
|
|
968
1056
|
updateCurrentPreset((p) => ({ ...p, grace_period_sec: isNaN(val) ? 10 : val }))
|
|
969
1057
|
},
|
|
970
1058
|
})
|
|
1059
|
+
),
|
|
1060
|
+
React.createElement(
|
|
1061
|
+
'div',
|
|
1062
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 } },
|
|
1063
|
+
React.createElement('span', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)' } }, t('proposers.timeout_label')),
|
|
1064
|
+
React.createElement('input', {
|
|
1065
|
+
type: 'number',
|
|
1066
|
+
className: 'moa-input',
|
|
1067
|
+
style: { width: 70, height: 28, padding: '0 6px', fontSize: 12 },
|
|
1068
|
+
min: 10,
|
|
1069
|
+
max: 300,
|
|
1070
|
+
value: currentPreset.reference_timeout_sec ?? 60,
|
|
1071
|
+
onChange: (e) => {
|
|
1072
|
+
const val = parseInt(e.target.value, 10)
|
|
1073
|
+
updateCurrentPreset((p) => ({ ...p, reference_timeout_sec: isNaN(val) ? 60 : val }))
|
|
1074
|
+
},
|
|
1075
|
+
})
|
|
971
1076
|
)
|
|
972
1077
|
),
|
|
973
1078
|
React.createElement(
|
|
@@ -989,6 +1094,28 @@ window.__ModuleLoader__.load({
|
|
|
989
1094
|
t,
|
|
990
1095
|
})
|
|
991
1096
|
),
|
|
1097
|
+
React.createElement(
|
|
1098
|
+
'select',
|
|
1099
|
+
{
|
|
1100
|
+
className: 'moa-select',
|
|
1101
|
+
style: { width: 105, height: 32, fontSize: 12, padding: '0 6px' },
|
|
1102
|
+
value: ref.role_persona || 'general',
|
|
1103
|
+
title: t('proposers.role_label'),
|
|
1104
|
+
onChange: (e) => {
|
|
1105
|
+
const newRole = e.target.value
|
|
1106
|
+
updateCurrentPreset((p) => {
|
|
1107
|
+
const refs = [...(p.reference_models || [])]
|
|
1108
|
+
refs[idx] = { ...refs[idx], role_persona: newRole }
|
|
1109
|
+
return { ...p, reference_models: refs }
|
|
1110
|
+
})
|
|
1111
|
+
},
|
|
1112
|
+
},
|
|
1113
|
+
React.createElement('option', { value: 'general' }, 'General'),
|
|
1114
|
+
React.createElement('option', { value: 'minimalist' }, 'Minimalist'),
|
|
1115
|
+
React.createElement('option', { value: 'robustness' }, 'Robustness'),
|
|
1116
|
+
React.createElement('option', { value: 'performance' }, 'Performance'),
|
|
1117
|
+
React.createElement('option', { value: 'tester' }, 'Tester')
|
|
1118
|
+
),
|
|
992
1119
|
currentRefs.length > 1 &&
|
|
993
1120
|
React.createElement(
|
|
994
1121
|
'button',
|
|
@@ -1072,6 +1199,87 @@ window.__ModuleLoader__.load({
|
|
|
1072
1199
|
React.createElement('div', { className: 'moa-stat-val' }, String(currentRefs.length)),
|
|
1073
1200
|
React.createElement('div', { className: 'moa-stat-lbl' }, t('stats.active_models'))
|
|
1074
1201
|
)
|
|
1202
|
+
),
|
|
1203
|
+
Array.isArray(leaderboard) && leaderboard.length > 0 && React.createElement(
|
|
1204
|
+
'div',
|
|
1205
|
+
{ style: { marginTop: 12, display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
1206
|
+
React.createElement(
|
|
1207
|
+
'div',
|
|
1208
|
+
{ style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap' } },
|
|
1209
|
+
React.createElement('div', { style: { fontSize: 13, fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } }, t('leaderboard.title')),
|
|
1210
|
+
React.createElement(
|
|
1211
|
+
'div',
|
|
1212
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1213
|
+
React.createElement(
|
|
1214
|
+
'select',
|
|
1215
|
+
{
|
|
1216
|
+
className: 'moa-select',
|
|
1217
|
+
style: { height: 26, fontSize: 11, padding: '0 6px' },
|
|
1218
|
+
value: leaderboardPresetFilter,
|
|
1219
|
+
onChange: (e) => setLeaderboardPresetFilter(e.target.value),
|
|
1220
|
+
},
|
|
1221
|
+
React.createElement('option', { value: 'all' }, t('leaderboard.filter_all')),
|
|
1222
|
+
presets.map((p) => React.createElement('option', { key: p.name, value: p.name }, p.name))
|
|
1223
|
+
),
|
|
1224
|
+
React.createElement(
|
|
1225
|
+
'a',
|
|
1226
|
+
{
|
|
1227
|
+
href: `/dsh-moa/history/export?format=csv${leaderboardPresetFilter !== 'all' ? '&preset=' + encodeURIComponent(leaderboardPresetFilter) : ''}`,
|
|
1228
|
+
download: 'moa-history.csv',
|
|
1229
|
+
className: 'moa-btn',
|
|
1230
|
+
style: { height: 26, fontSize: 11, padding: '0 8px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center' },
|
|
1231
|
+
},
|
|
1232
|
+
'CSV'
|
|
1233
|
+
),
|
|
1234
|
+
React.createElement(
|
|
1235
|
+
'a',
|
|
1236
|
+
{
|
|
1237
|
+
href: `/dsh-moa/history/export?format=json${leaderboardPresetFilter !== 'all' ? '&preset=' + encodeURIComponent(leaderboardPresetFilter) : ''}`,
|
|
1238
|
+
download: 'moa-history.json',
|
|
1239
|
+
className: 'moa-btn',
|
|
1240
|
+
style: { height: 26, fontSize: 11, padding: '0 8px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center' },
|
|
1241
|
+
},
|
|
1242
|
+
'JSON'
|
|
1243
|
+
)
|
|
1244
|
+
)
|
|
1245
|
+
),
|
|
1246
|
+
React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginTop: -4 } }, t('leaderboard.desc')),
|
|
1247
|
+
React.createElement(
|
|
1248
|
+
'div',
|
|
1249
|
+
{ style: { overflowX: 'auto', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8 } },
|
|
1250
|
+
React.createElement(
|
|
1251
|
+
'table',
|
|
1252
|
+
{ style: { width: '100%', borderCollapse: 'collapse', fontSize: 12, textAlign: 'left' } },
|
|
1253
|
+
React.createElement(
|
|
1254
|
+
'thead',
|
|
1255
|
+
{ style: { background: 'var(--dsw-alias-bg-layer-2)', borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
|
|
1256
|
+
React.createElement(
|
|
1257
|
+
'tr',
|
|
1258
|
+
null,
|
|
1259
|
+
React.createElement('th', { style: { padding: '6px 10px' } }, t('leaderboard.model')),
|
|
1260
|
+
React.createElement('th', { style: { padding: '6px 8px' } }, t('leaderboard.runs')),
|
|
1261
|
+
React.createElement('th', { style: { padding: '6px 8px' } }, t('leaderboard.wins')),
|
|
1262
|
+
React.createElement('th', { style: { padding: '6px 8px' } }, t('leaderboard.winrate')),
|
|
1263
|
+
React.createElement('th', { style: { padding: '6px 10px' } }, t('leaderboard.cost'))
|
|
1264
|
+
)
|
|
1265
|
+
),
|
|
1266
|
+
React.createElement(
|
|
1267
|
+
'tbody',
|
|
1268
|
+
null,
|
|
1269
|
+
leaderboard.map((m, mIdx) =>
|
|
1270
|
+
React.createElement(
|
|
1271
|
+
'tr',
|
|
1272
|
+
{ key: mIdx, style: { borderBottom: mIdx < leaderboard.length - 1 ? '1px solid var(--dsw-alias-border-l2)' : 'none' } },
|
|
1273
|
+
React.createElement('td', { style: { padding: '6px 10px', fontWeight: 500, color: 'var(--dsw-alias-label-primary)' } }, m.modelKey || m.model),
|
|
1274
|
+
React.createElement('td', { style: { padding: '6px 8px', color: 'var(--dsw-alias-label-secondary)' } }, String(m.runs)),
|
|
1275
|
+
React.createElement('td', { style: { padding: '6px 8px', color: 'var(--dsw-alias-state-success-primary)' } }, String(m.wins)),
|
|
1276
|
+
React.createElement('td', { style: { padding: '6px 8px', fontWeight: 600 } }, (m.winRate ?? 0) + '%'),
|
|
1277
|
+
React.createElement('td', { style: { padding: '6px 10px', color: 'var(--dsw-alias-label-secondary)' } }, (m.avgCostUsd > 0) ? ('$' + m.avgCostUsd.toFixed(4)) : 'Free')
|
|
1278
|
+
)
|
|
1279
|
+
)
|
|
1280
|
+
)
|
|
1281
|
+
)
|
|
1282
|
+
)
|
|
1075
1283
|
)
|
|
1076
1284
|
),
|
|
1077
1285
|
|
|
@@ -1151,6 +1359,8 @@ window.__ModuleLoader__.load({
|
|
|
1151
1359
|
const [enabled, setEnabled] = React.useState(true)
|
|
1152
1360
|
const [hostStatus, setHostStatus] = React.useState('connecting')
|
|
1153
1361
|
const [stats, setStats] = React.useState({ totalRuns: null, avgCostUsd: null })
|
|
1362
|
+
const [leaderboard, setLeaderboard] = React.useState([])
|
|
1363
|
+
const [leaderboardPresetFilter, setLeaderboardPresetFilter] = React.useState('all')
|
|
1154
1364
|
|
|
1155
1365
|
React.useEffect(() => {
|
|
1156
1366
|
if (!scope || !snapshot) return
|
|
@@ -1182,6 +1392,18 @@ window.__ModuleLoader__.load({
|
|
|
1182
1392
|
setHostStatus('offline')
|
|
1183
1393
|
})
|
|
1184
1394
|
|
|
1395
|
+
const lbUrl = leaderboardPresetFilter !== 'all'
|
|
1396
|
+
? `/dsh-moa/leaderboard?preset=${encodeURIComponent(leaderboardPresetFilter)}`
|
|
1397
|
+
: '/dsh-moa/leaderboard'
|
|
1398
|
+
fetch(lbUrl, { cache: 'no-store' })
|
|
1399
|
+
.then((r) => r.json())
|
|
1400
|
+
.then((data) => {
|
|
1401
|
+
if (data && data.ok && Array.isArray(data.models)) {
|
|
1402
|
+
setLeaderboard(data.models.slice(0, 10))
|
|
1403
|
+
}
|
|
1404
|
+
})
|
|
1405
|
+
.catch(() => {})
|
|
1406
|
+
|
|
1185
1407
|
fetch('/dsh-moa/history?limit=100', { cache: 'no-store' })
|
|
1186
1408
|
.then((r) => r.json())
|
|
1187
1409
|
.then((data) => {
|
|
@@ -1290,6 +1512,9 @@ window.__ModuleLoader__.load({
|
|
|
1290
1512
|
setEnabled,
|
|
1291
1513
|
hostStatus,
|
|
1292
1514
|
stats,
|
|
1515
|
+
leaderboard,
|
|
1516
|
+
leaderboardPresetFilter,
|
|
1517
|
+
setLeaderboardPresetFilter,
|
|
1293
1518
|
handleSave,
|
|
1294
1519
|
saveStatus,
|
|
1295
1520
|
reload,
|
package/lib/file-workspace.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import vm from 'node:vm'
|
|
1
2
|
import fs from 'node:fs/promises'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
|
|
@@ -275,3 +276,48 @@ async function getFilesRecursively(dir) {
|
|
|
275
276
|
await scan(dir)
|
|
276
277
|
return results
|
|
277
278
|
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Verifies the syntactic validity of generated code files.
|
|
283
|
+
* Uses Node.js in-memory vm.Script for JS (with ES module export/import transformation) and JSON.parse for JSON.
|
|
284
|
+
*/
|
|
285
|
+
export function verifyFileSyntax(files = []) {
|
|
286
|
+
if (!Array.isArray(files)) return []
|
|
287
|
+
const warnings = []
|
|
288
|
+
|
|
289
|
+
for (const file of files) {
|
|
290
|
+
if (!file || typeof file.content !== 'string' || !file.relativePath) continue
|
|
291
|
+
const ext = path.extname(file.relativePath).toLowerCase()
|
|
292
|
+
|
|
293
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
294
|
+
try {
|
|
295
|
+
const transformed = file.content
|
|
296
|
+
.replace(/^([ \t]*)export\s+default\s+/gm, '$1')
|
|
297
|
+
.replace(/^([ \t]*)export\s*\{[^}]*\}\s*;?/gm, '$1')
|
|
298
|
+
.replace(/^([ \t]*)export\s+(const|let|var|function|class|async\s+function)\s+/gm, '$1$2 ')
|
|
299
|
+
.replace(/^([ \t]*)import\s+[^;\n]+;?/gm, '$1')
|
|
300
|
+
.replace(/^([ \t]*)export\s+\*\s+from\s+[^;\n]+;?/gm, '$1')
|
|
301
|
+
new vm.Script(transformed)
|
|
302
|
+
} catch (err) {
|
|
303
|
+
warnings.push({
|
|
304
|
+
file: file.relativePath,
|
|
305
|
+
error: err?.message || String(err),
|
|
306
|
+
line: err?.lineNumber || 1,
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
} else if (ext === '.json') {
|
|
310
|
+
try {
|
|
311
|
+
JSON.parse(file.content)
|
|
312
|
+
} catch (err) {
|
|
313
|
+
warnings.push({
|
|
314
|
+
file: file.relativePath,
|
|
315
|
+
error: err?.message || String(err),
|
|
316
|
+
line: 1,
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return warnings
|
|
323
|
+
}
|
package/lib/history.js
CHANGED
|
@@ -159,7 +159,7 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = DEFAULT_HISTORY
|
|
|
159
159
|
/**
|
|
160
160
|
* Calculates win-rate leaderboard and model performance statistics across history.
|
|
161
161
|
*/
|
|
162
|
-
export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE) {
|
|
162
|
+
export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE, options = {}) {
|
|
163
163
|
try {
|
|
164
164
|
if (!fs.existsSync(filePath)) {
|
|
165
165
|
return { totalRuns: 0, models: [] }
|
|
@@ -170,11 +170,15 @@ export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE) {
|
|
|
170
170
|
const totalRuns = lines.length
|
|
171
171
|
|
|
172
172
|
const modelStats = new Map()
|
|
173
|
+
const presetFilter = typeof options === 'string' ? options : (options?.presetFilter || options?.preset || null)
|
|
173
174
|
|
|
175
|
+
let filteredRuns = 0
|
|
174
176
|
for (const line of lines) {
|
|
175
177
|
try {
|
|
176
178
|
const item = JSON.parse(line)
|
|
177
179
|
if (!item) continue
|
|
180
|
+
if (presetFilter && presetFilter !== 'all' && item.preset !== presetFilter) continue
|
|
181
|
+
filteredRuns++
|
|
178
182
|
|
|
179
183
|
const winner = item.winnerModel ? String(item.winnerModel).trim() : null
|
|
180
184
|
|
|
@@ -218,7 +222,7 @@ export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE) {
|
|
|
218
222
|
}))
|
|
219
223
|
.sort((a, b) => b.wins - a.wins || b.winRate - a.winRate)
|
|
220
224
|
|
|
221
|
-
return { totalRuns, models }
|
|
225
|
+
return { totalRuns: presetFilter && presetFilter !== 'all' ? filteredRuns : totalRuns, models, preset: presetFilter }
|
|
222
226
|
} catch (err) {
|
|
223
227
|
console.warn('[dsh-moa] Failed to calculate leaderboard:', err)
|
|
224
228
|
return { totalRuns: 0, models: [] }
|
|
@@ -244,3 +248,53 @@ export function getMoaRunById(runId, filePath = DEFAULT_HISTORY_FILE) {
|
|
|
244
248
|
return null
|
|
245
249
|
}
|
|
246
250
|
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Exports history data in JSON or CSV format with optional preset filtering.
|
|
254
|
+
*/
|
|
255
|
+
export function exportMoaHistory(filePath = DEFAULT_HISTORY_FILE, options = {}) {
|
|
256
|
+
try {
|
|
257
|
+
if (!fs.existsSync(filePath)) {
|
|
258
|
+
return options?.format === 'csv' ? 'id,timestamp,preset,winnerModel,totalTokens,totalCostUsd,durationMs\n' : '[]'
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
262
|
+
const lines = raw.trim().split('\n').filter(Boolean)
|
|
263
|
+
const presetFilter = typeof options === 'string' ? options : (options?.presetFilter || options?.preset || null)
|
|
264
|
+
const format = options?.format === 'csv' ? 'csv' : 'json'
|
|
265
|
+
|
|
266
|
+
const records = []
|
|
267
|
+
for (const line of lines) {
|
|
268
|
+
try {
|
|
269
|
+
const item = JSON.parse(line)
|
|
270
|
+
if (!item) continue
|
|
271
|
+
if (presetFilter && presetFilter !== 'all' && item.preset !== presetFilter) continue
|
|
272
|
+
records.push(item)
|
|
273
|
+
} catch {}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (format === 'csv') {
|
|
277
|
+
const header = 'id,timestamp,preset,winnerModel,totalTokens,totalCostUsd,durationMs,isRefinement,isFastMode'
|
|
278
|
+
const rows = records.map((r) => {
|
|
279
|
+
const escapeCsv = (val) => `"${String(val ?? '').replace(/"/g, '""')}"`
|
|
280
|
+
return [
|
|
281
|
+
escapeCsv(r.id),
|
|
282
|
+
escapeCsv(r.timestamp),
|
|
283
|
+
escapeCsv(r.preset),
|
|
284
|
+
escapeCsv(r.winnerModel),
|
|
285
|
+
Number(r.totalTokens || 0),
|
|
286
|
+
Number(r.totalCostUsd || 0).toFixed(4),
|
|
287
|
+
Number(r.durationMs || 0),
|
|
288
|
+
Boolean(r.isRefinement),
|
|
289
|
+
Boolean(r.isFastMode),
|
|
290
|
+
].join(',')
|
|
291
|
+
})
|
|
292
|
+
return [header, ...rows].join('\n')
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return JSON.stringify(records, null, 2)
|
|
296
|
+
} catch (err) {
|
|
297
|
+
console.warn('[dsh-moa] Failed to export history:', err)
|
|
298
|
+
return options?.format === 'csv' ? '' : '[]'
|
|
299
|
+
}
|
|
300
|
+
}
|