@goodandready/dsh-moa 0.2.3 → 0.2.5
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 +21 -0
- package/docs/README.ru.md +21 -0
- package/lib/client.js +226 -124
- package/lib/file-workspace.js +94 -5
- package/lib/history.js +191 -0
- package/lib/index.js +221 -188
- package/lib/moa-runner.js +216 -87
- package/lib/pricing.js +194 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -37,6 +37,10 @@ Single-model AI generation often suffers from blind spots, single-perspective bi
|
|
|
37
37
|
4. **Instant Live Canvas Previewing**: When web applications or UI components are generated, `dsh-moa` integrates seamlessly with `@goodandready/dsh-live-canvas`, automatically spawning sandboxes for 1-click browser previewing.
|
|
38
38
|
5. **Token-Saving Chat Summarization**: Replaces massive code dumps in chat bubbles with compact file listings and clean architectural summaries.
|
|
39
39
|
6. **One-Shot Session Model Restoration**: Executes cleanly as a one-shot turn modifier, automatically reverting back to the user's primary session model immediately after completion.
|
|
40
|
+
7. **Dynamic Model Pricing Catalog & Token Estimation**: Real-time rate resolution for 300+ models fetched automatically in the background from OpenRouter's public catalog (cached locally in `~/.dsh/storages/dsh-moa-catalog.json` for 24h), plus support for direct vendor rates and custom `prices` overrides in `settings.yaml`.
|
|
41
|
+
8. **Refinement Mode (Incremental Edits)**: Automatically detects existing codebase context to generate precise delta modifications instead of destructive full-file rewrites.
|
|
42
|
+
9. **Fast Mode & Custom Judge Criteria**: Ultra-fast single-model preset for quick tasks and customizable evaluation guidelines for the judge.
|
|
43
|
+
10. **Run History & Win-Rate Leaderboard**: Persistent logging with built-in REST endpoints (`/dsh-moa/history` and `/dsh-moa/leaderboard`).
|
|
40
44
|
|
|
41
45
|
---
|
|
42
46
|
|
|
@@ -152,6 +156,13 @@ dsh-moa:
|
|
|
152
156
|
aggregator:
|
|
153
157
|
provider: "your-reasoning-provider"
|
|
154
158
|
model: "your-judge-model"
|
|
159
|
+
prices:
|
|
160
|
+
"my-provider/my-model":
|
|
161
|
+
input: 0.20
|
|
162
|
+
output: 0.80
|
|
163
|
+
"ollama/*":
|
|
164
|
+
input: 0
|
|
165
|
+
output: 0
|
|
155
166
|
code-review:
|
|
156
167
|
references:
|
|
157
168
|
- provider: "your-fast-provider"
|
|
@@ -175,6 +186,16 @@ dsh-moa:
|
|
|
175
186
|
|
|
176
187
|
---
|
|
177
188
|
|
|
189
|
+
## 📊 REST API & Endpoints
|
|
190
|
+
|
|
191
|
+
| Endpoint | Method | Description |
|
|
192
|
+
|:---|:---|:---|
|
|
193
|
+
| `/dsh-moa/presets` | `GET` | Returns list of configured MoA presets |
|
|
194
|
+
| `/dsh-moa/history?limit=20&offset=0` | `GET` | Returns recent MoA runs with candidates, winner, cost, and tokens |
|
|
195
|
+
| `/dsh-moa/leaderboard` | `GET` | Computes model win-rate leaderboard and average execution costs |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
178
199
|
## 🧪 Testing
|
|
179
200
|
|
|
180
201
|
Run the automated test suite:
|
package/docs/README.ru.md
CHANGED
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
4. **Мгновенный предпросмотр через Live Canvas**: При создании веб-приложений и UI-компонентов `dsh-moa` бесшовно интегрируется с `@goodandready/dsh-live-canvas`, автоматически инициализируя контейнер для предпросмотра в браузере в 1 клик.
|
|
38
38
|
5. **Экономия токенов в чате**: Вместо вывода огромных листингов кода в чат формируется компактный отчет с перечнем созданных файлов и архитектурным резюме.
|
|
39
39
|
6. **Одноразовый модификатор сессии**: Команда выполняется в рамках одного такта и автоматически возвращает исходную модель сессии пользователя сразу после завершения.
|
|
40
|
+
7. **Динамический каталог тарифов и подсчет токенов**: Актуальные цены на 300+ моделей автоматически подтягиваются из публичного каталога OpenRouter (без ключей и авторизации), кешируются в `~/.dsh/storages/dsh-moa-catalog.json` на 24 часа, а также поддерживают прямые вендорские тарифы и пользовательские оверрайды `prices` в `settings.yaml`.
|
|
41
|
+
8. **Режим доработки (Refinement Mode)**: Автоматически считывает контекст существующего проекта и генерирует точечные дельта-правки без перетирания всей кодовой базы.
|
|
42
|
+
9. **Быстрый режим (Fast Mode) и критерии судьи**: Режим для одиночных быстрых задач без судьи и гибкая настройка фокуса оценки (безопасность, производительность, минимализм).
|
|
43
|
+
10. **История запусков и лидерборд моделей**: Персистентное логирование запусков и REST-эндпоинты (`/dsh-moa/history` и `/dsh-moa/leaderboard`).
|
|
40
44
|
|
|
41
45
|
---
|
|
42
46
|
|
|
@@ -152,6 +156,13 @@ dsh-moa:
|
|
|
152
156
|
aggregator:
|
|
153
157
|
provider: "your-reasoning-provider"
|
|
154
158
|
model: "your-judge-model"
|
|
159
|
+
prices:
|
|
160
|
+
"my-provider/my-model":
|
|
161
|
+
input: 0.20
|
|
162
|
+
output: 0.80
|
|
163
|
+
"ollama/*":
|
|
164
|
+
input: 0
|
|
165
|
+
output: 0
|
|
155
166
|
code-review:
|
|
156
167
|
references:
|
|
157
168
|
- provider: "your-fast-provider"
|
|
@@ -175,6 +186,16 @@ dsh-moa:
|
|
|
175
186
|
|
|
176
187
|
---
|
|
177
188
|
|
|
189
|
+
## 📊 REST API эндпоинты
|
|
190
|
+
|
|
191
|
+
| Эндпоинт | Метод | Описание |
|
|
192
|
+
|:---|:---|:---|
|
|
193
|
+
| `/dsh-moa/presets` | `GET` | Возвращает список настроенных пресетов MoA |
|
|
194
|
+
| `/dsh-moa/history?limit=20&offset=0` | `GET` | История запусков с кандидатами, победителем, токенами и ценой |
|
|
195
|
+
| `/dsh-moa/leaderboard` | `GET` | Лидерборд побед моделей и средняя стоимость генерации |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
178
199
|
## 🧪 Тестирование
|
|
179
200
|
|
|
180
201
|
Запуск автоматического набора тестов:
|
package/lib/client.js
CHANGED
|
@@ -6,6 +6,7 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
let React = require('react')
|
|
7
7
|
|
|
8
8
|
const NS = 'dsh-moa'
|
|
9
|
+
let localeRegistered = false
|
|
9
10
|
|
|
10
11
|
let ChevronIcon = null
|
|
11
12
|
try {
|
|
@@ -378,6 +379,10 @@ window.__ModuleLoader__.load({
|
|
|
378
379
|
updateCurrentPreset((p) => ({ ...p, reference_temperature: val }))
|
|
379
380
|
}
|
|
380
381
|
|
|
382
|
+
const setJudgeCriteria = (criteria) => {
|
|
383
|
+
updateCurrentPreset((p) => ({ ...p, judge_criteria: criteria }))
|
|
384
|
+
}
|
|
385
|
+
|
|
381
386
|
const setReferenceModel = (idx, prov, mod) => {
|
|
382
387
|
updateCurrentPreset((p) => {
|
|
383
388
|
const refs = [...(p.reference_models || [])]
|
|
@@ -468,6 +473,7 @@ window.__ModuleLoader__.load({
|
|
|
468
473
|
const currentRefs = currentPreset.reference_models || []
|
|
469
474
|
const aggTemp = currentPreset.aggregator_temperature ?? 0.4
|
|
470
475
|
const refTemp = currentPreset.reference_temperature ?? 0.6
|
|
476
|
+
const judgeCriteria = currentPreset.judge_criteria || ''
|
|
471
477
|
|
|
472
478
|
return React.createElement(
|
|
473
479
|
React.Fragment,
|
|
@@ -621,6 +627,30 @@ window.__ModuleLoader__.load({
|
|
|
621
627
|
})
|
|
622
628
|
),
|
|
623
629
|
React.createElement('div', { className: 'moa-hint' }, t('aggTempHint') || 'Рекомендуется 0.2 – 0.4 для точной проверки и синтеза без галлюцинаций.')
|
|
630
|
+
),
|
|
631
|
+
React.createElement(
|
|
632
|
+
'div',
|
|
633
|
+
{ className: 'moa-field', style: { marginTop: 4, borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8 } },
|
|
634
|
+
React.createElement('label', { className: 'moa-label', style: { fontSize: 12 } }, t('judgeCriteriaLabel') || 'Критерии оценки судьи (опционально):'),
|
|
635
|
+
React.createElement('textarea', {
|
|
636
|
+
className: 'moa-textarea',
|
|
637
|
+
rows: 2,
|
|
638
|
+
style: {
|
|
639
|
+
width: '100%',
|
|
640
|
+
boxSizing: 'border-box',
|
|
641
|
+
padding: '8px 10px',
|
|
642
|
+
borderRadius: 8,
|
|
643
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
644
|
+
background: 'var(--dsw-alias-bg-layer-3)',
|
|
645
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
646
|
+
fontSize: 13,
|
|
647
|
+
resize: 'vertical',
|
|
648
|
+
outline: 'none',
|
|
649
|
+
},
|
|
650
|
+
placeholder: t('judgeCriteriaPlaceholder') || 'например: Приоритет — производительность, чистый код, минимум зависимостей...',
|
|
651
|
+
value: judgeCriteria,
|
|
652
|
+
onChange: (e) => setJudgeCriteria(e.target.value),
|
|
653
|
+
})
|
|
624
654
|
)
|
|
625
655
|
),
|
|
626
656
|
/* Proposers / Advisors Box with Temperature Slider */
|
|
@@ -744,8 +774,120 @@ window.__ModuleLoader__.load({
|
|
|
744
774
|
)
|
|
745
775
|
}
|
|
746
776
|
|
|
777
|
+
let rootCtx = null
|
|
778
|
+
|
|
779
|
+
const en = {
|
|
780
|
+
title: 'Mixture of Agents (MoA)',
|
|
781
|
+
description: 'Run tasks with multiple parallel proposer models and a synthesizing judge model',
|
|
782
|
+
slashMoa: 'Run task through Mixture of Agents (/moa <prompt>)',
|
|
783
|
+
defaultPreset: 'Default Preset (used on /moa without arguments)',
|
|
784
|
+
isDefault: 'default',
|
|
785
|
+
presetSelectLabel: 'Active Preset to Configure:',
|
|
786
|
+
createNewPreset: 'Create new preset...',
|
|
787
|
+
makeDefault: 'Make Default',
|
|
788
|
+
makeDefaultHint: 'Use this preset for /moa when no preset argument is given',
|
|
789
|
+
deletePreset: 'Delete preset',
|
|
790
|
+
delete: 'Delete',
|
|
791
|
+
confirmDeletePreset: 'Are you sure you want to delete preset',
|
|
792
|
+
newPresetPlaceholder: 'New preset name (e.g. fast-audit)',
|
|
793
|
+
create: 'Create',
|
|
794
|
+
cancel: 'Cancel',
|
|
795
|
+
aggregatorTitle: 'Judge / Aggregator Model (who reviews and synthesizes)',
|
|
796
|
+
aggregatorDesc: 'Evaluates candidate proposals, corrects hallucinations, and outputs the final unified solution.',
|
|
797
|
+
temperature: 'Temperature',
|
|
798
|
+
aggTempHint: 'Recommended 0.2 - 0.4 for rigorous verification and factual synthesis.',
|
|
799
|
+
proposersTitle: 'Candidate Models (Advisors)',
|
|
800
|
+
proposersDesc: 'Generates independent candidate answers in parallel to provide diverse perspectives.',
|
|
801
|
+
refTempLabel: 'Candidates Temperature (solution diversity):',
|
|
802
|
+
refTempHint: 'Recommended 0.6 - 0.8 for diverse creative hypotheses.',
|
|
803
|
+
parallelCount: 'Parallel workers',
|
|
804
|
+
candidate: 'Candidate',
|
|
805
|
+
addCandidate: 'Add parallel model slot',
|
|
806
|
+
remove: 'Remove',
|
|
807
|
+
save: 'Save Changes',
|
|
808
|
+
saved: 'Saved successfully',
|
|
809
|
+
loading: 'Saving…',
|
|
810
|
+
retry: 'Retry connection',
|
|
811
|
+
unavailable: 'MoA service temporarily unavailable (reconnecting...)',
|
|
812
|
+
searchPlaceholder: 'Search by model name or provider...',
|
|
813
|
+
modelsFound: 'Found',
|
|
814
|
+
of: 'of',
|
|
815
|
+
clear: 'Clear',
|
|
816
|
+
noModelsMatch: 'No models found matching query.',
|
|
817
|
+
useCustom: 'Use custom',
|
|
818
|
+
selectModel: 'Select model...',
|
|
819
|
+
judgeCriteriaLabel: 'Custom Judge Evaluation Criteria (optional):',
|
|
820
|
+
judgeCriteriaPlaceholder: 'e.g. Priority: performance, zero external dependencies, robust edge-case handling...',
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const ru = {
|
|
824
|
+
title: 'Mixture of Agents (MoA)',
|
|
825
|
+
description: 'Запуск задач через ансамбль параллельных моделей-советников и модель-судью',
|
|
826
|
+
slashMoa: 'Запустить задачу через архитектуру Mixture of Agents (/moa <prompt>)',
|
|
827
|
+
defaultPreset: 'Основной пресет (при вызове /moa без параметров)',
|
|
828
|
+
isDefault: 'основной',
|
|
829
|
+
presetSelectLabel: 'Текущий пресет для настройки:',
|
|
830
|
+
createNewPreset: 'Создать новый пресет...',
|
|
831
|
+
makeDefault: 'Сделать основным',
|
|
832
|
+
makeDefaultHint: 'Использовать этот пресет для /moa при вызове без параметров',
|
|
833
|
+
deletePreset: 'Удалить пресет',
|
|
834
|
+
delete: 'Удалить',
|
|
835
|
+
confirmDeletePreset: 'Вы уверены, что хотите удалить пресет',
|
|
836
|
+
newPresetPlaceholder: 'Название нового пресета (например: fast-audit)',
|
|
837
|
+
create: 'Создать',
|
|
838
|
+
cancel: 'Отмена',
|
|
839
|
+
aggregatorTitle: 'Ведущая модель / Судья (кто проверяет и объединяет)',
|
|
840
|
+
aggregatorDesc: 'Сопоставляет варианты кандидатов, устраняет ошибки и синтезирует итоговое решение.',
|
|
841
|
+
temperature: 'Температура',
|
|
842
|
+
aggTempHint: 'Рекомендуется 0.2 – 0.4 для строгой проверки и синтеза без выдумок.',
|
|
843
|
+
proposersTitle: 'Модели-кандидаты (советники)',
|
|
844
|
+
proposersDesc: 'Параллельно формируют независимые варианты решения одной задачи.',
|
|
845
|
+
refTempLabel: 'Температура кандидатов (разнообразие решений):',
|
|
846
|
+
refTempHint: 'Рекомендуется 0.6 – 0.8 для получения независимых и креативных вариантов.',
|
|
847
|
+
parallelCount: 'Параллельных моделей',
|
|
848
|
+
candidate: 'Кандидат',
|
|
849
|
+
addCandidate: 'Добавить модель в параллель',
|
|
850
|
+
remove: 'Удалить',
|
|
851
|
+
save: 'Сохранить',
|
|
852
|
+
saved: 'Успешно сохранено',
|
|
853
|
+
loading: 'Сохранение…',
|
|
854
|
+
retry: 'Повторить подключение',
|
|
855
|
+
unavailable: 'Служба MoA временно недоступна (переподключение...)',
|
|
856
|
+
searchPlaceholder: 'Поиск модели по названию или провайдеру...',
|
|
857
|
+
modelsFound: 'Найдено',
|
|
858
|
+
of: 'из',
|
|
859
|
+
clear: 'Сбросить',
|
|
860
|
+
noModelsMatch: 'Модели не найдены.',
|
|
861
|
+
useCustom: 'Использовать кастомное',
|
|
862
|
+
selectModel: 'Выберите модель...',
|
|
863
|
+
judgeCriteriaLabel: 'Критерии оценки судьи (опционально):',
|
|
864
|
+
judgeCriteriaPlaceholder: 'например: Приоритет — производительность, минимум зависимостей, обработка граничных случаев...',
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function makeT(ctx, props) {
|
|
868
|
+
return (key) => {
|
|
869
|
+
if (props && typeof props.t === 'function') {
|
|
870
|
+
try {
|
|
871
|
+
const res = props.t(key)
|
|
872
|
+
if (res && res !== key) return res
|
|
873
|
+
} catch (_) {}
|
|
874
|
+
}
|
|
875
|
+
const effectiveCtx = (props && props.ctx) || ctx || rootCtx
|
|
876
|
+
if (effectiveCtx && effectiveCtx.locale && typeof effectiveCtx.locale.bind === 'function') {
|
|
877
|
+
try {
|
|
878
|
+
const bound = effectiveCtx.locale.bind(NS)
|
|
879
|
+
const res = bound(key)
|
|
880
|
+
if (res && res !== key) return res
|
|
881
|
+
} catch (_) {}
|
|
882
|
+
}
|
|
883
|
+
const curLocale = (effectiveCtx && effectiveCtx.locale && effectiveCtx.locale.getSnapshot && effectiveCtx.locale.getSnapshot().active) || 'ru'
|
|
884
|
+
const dict = curLocale === 'en' ? en : ru
|
|
885
|
+
return dict[key] || ru[key] || en[key] || key
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
747
889
|
function useMoASettings(props) {
|
|
748
|
-
const t =
|
|
890
|
+
const t = makeT(rootCtx, props)
|
|
749
891
|
const [status, setStatus] = React.useState('loading')
|
|
750
892
|
const [presets, setPresets] = React.useState([])
|
|
751
893
|
const [defaultPreset, setDefaultPreset] = React.useState('default')
|
|
@@ -858,6 +1000,64 @@ window.__ModuleLoader__.load({
|
|
|
858
1000
|
)
|
|
859
1001
|
}
|
|
860
1002
|
|
|
1003
|
+
|
|
1004
|
+
// ── ERROR BOUNDARY ──────────────────────────────────────────────
|
|
1005
|
+
class MoAErrorBoundary extends React.Component {
|
|
1006
|
+
constructor(props) {
|
|
1007
|
+
super(props)
|
|
1008
|
+
this.state = { hasError: false, error: null, errorInfo: null }
|
|
1009
|
+
}
|
|
1010
|
+
static getDerivedStateFromError(error) {
|
|
1011
|
+
return { hasError: true, error }
|
|
1012
|
+
}
|
|
1013
|
+
componentDidCatch(error, errorInfo) {
|
|
1014
|
+
console.error('[dsh-moa] MoAErrorBoundary caught error:', error, errorInfo)
|
|
1015
|
+
this.setState({ errorInfo })
|
|
1016
|
+
}
|
|
1017
|
+
render() {
|
|
1018
|
+
if (this.state.hasError) {
|
|
1019
|
+
return React.createElement(
|
|
1020
|
+
'div',
|
|
1021
|
+
{
|
|
1022
|
+
style: {
|
|
1023
|
+
padding: '16px',
|
|
1024
|
+
borderRadius: '8px',
|
|
1025
|
+
border: '1px solid #ef4444',
|
|
1026
|
+
background: 'rgba(239,68,68,0.08)',
|
|
1027
|
+
color: '#ef4444',
|
|
1028
|
+
fontSize: '13px',
|
|
1029
|
+
fontFamily: 'monospace',
|
|
1030
|
+
whiteSpace: 'pre-wrap',
|
|
1031
|
+
wordBreak: 'break-word',
|
|
1032
|
+
},
|
|
1033
|
+
},
|
|
1034
|
+
React.createElement('div', { style: { fontWeight: 700, marginBottom: 8 } }, '⚠ MoA Settings Error'),
|
|
1035
|
+
React.createElement('div', null, String(this.state.error)),
|
|
1036
|
+
this.state.errorInfo && React.createElement(
|
|
1037
|
+
'details',
|
|
1038
|
+
{ style: { marginTop: 8 } },
|
|
1039
|
+
React.createElement('summary', null, 'Component stack'),
|
|
1040
|
+
React.createElement('pre', { style: { fontSize: 11 } }, this.state.errorInfo.componentStack || '')
|
|
1041
|
+
),
|
|
1042
|
+
React.createElement(
|
|
1043
|
+
'button',
|
|
1044
|
+
{
|
|
1045
|
+
type: 'button',
|
|
1046
|
+
style: {
|
|
1047
|
+
marginTop: 12, padding: '6px 16px', border: '1px solid #ef4444',
|
|
1048
|
+
background: 'transparent', color: '#ef4444', borderRadius: 6,
|
|
1049
|
+
cursor: 'pointer', fontSize: 12,
|
|
1050
|
+
},
|
|
1051
|
+
onClick: () => this.setState({ hasError: false, error: null, errorInfo: null }),
|
|
1052
|
+
},
|
|
1053
|
+
'Retry'
|
|
1054
|
+
)
|
|
1055
|
+
)
|
|
1056
|
+
}
|
|
1057
|
+
return this.props.children
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
861
1061
|
function MoACard(props) {
|
|
862
1062
|
ensureStyles()
|
|
863
1063
|
const state = useMoASettings(props)
|
|
@@ -913,134 +1113,39 @@ window.__ModuleLoader__.load({
|
|
|
913
1113
|
|
|
914
1114
|
exports.inject = ['slots', 'locale', 'inputTriggers']
|
|
915
1115
|
exports.apply = function apply(ctx) {
|
|
916
|
-
|
|
1116
|
+
rootCtx = ctx
|
|
1117
|
+
const t = makeT(ctx)
|
|
1118
|
+
const addLocale = (locale, dictionary) => {
|
|
917
1119
|
try {
|
|
918
1120
|
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
919
|
-
return ctx.locale.register(NS,
|
|
920
|
-
en: {
|
|
921
|
-
title: 'Mixture of Agents (MoA)',
|
|
922
|
-
description: 'Run tasks with multiple parallel proposer models and a synthesizing judge model',
|
|
923
|
-
slashMoa: 'Run task through Mixture of Agents (/moa <prompt>)',
|
|
924
|
-
defaultPreset: 'Default Preset (used on /moa without arguments)',
|
|
925
|
-
isDefault: 'default',
|
|
926
|
-
presetSelectLabel: 'Active Preset to Configure:',
|
|
927
|
-
createNewPreset: 'Create new preset...',
|
|
928
|
-
makeDefault: 'Make Default',
|
|
929
|
-
makeDefaultHint: 'Use this preset for /moa when no preset argument is given',
|
|
930
|
-
deletePreset: 'Delete preset',
|
|
931
|
-
delete: 'Delete',
|
|
932
|
-
confirmDeletePreset: 'Are you sure you want to delete preset',
|
|
933
|
-
newPresetPlaceholder: 'New preset name (e.g. fast-audit)',
|
|
934
|
-
create: 'Create',
|
|
935
|
-
cancel: 'Cancel',
|
|
936
|
-
aggregatorTitle: 'Judge / Aggregator Model (who reviews and synthesizes)',
|
|
937
|
-
aggregatorDesc: 'Evaluates candidate proposals, corrects hallucinations, and outputs the final unified solution.',
|
|
938
|
-
temperature: 'Temperature',
|
|
939
|
-
aggTempHint: 'Recommended 0.2 - 0.4 for rigorous verification and factual synthesis.',
|
|
940
|
-
proposersTitle: 'Candidate Models (Advisors)',
|
|
941
|
-
proposersDesc: 'Generates independent candidate answers in parallel to provide diverse perspectives.',
|
|
942
|
-
refTempLabel: 'Candidates Temperature (solution diversity):',
|
|
943
|
-
refTempHint: 'Recommended 0.6 - 0.8 for diverse creative hypotheses.',
|
|
944
|
-
parallelCount: 'Parallel workers',
|
|
945
|
-
candidate: 'Candidate',
|
|
946
|
-
addCandidate: 'Add parallel model slot',
|
|
947
|
-
remove: 'Remove',
|
|
948
|
-
save: 'Save Changes',
|
|
949
|
-
saved: 'Saved successfully',
|
|
950
|
-
loading: 'Saving…',
|
|
951
|
-
retry: 'Retry connection',
|
|
952
|
-
unavailable: 'MoA service temporarily unavailable (reconnecting...)',
|
|
953
|
-
searchPlaceholder: 'Search by model name or provider...',
|
|
954
|
-
modelsFound: 'Found',
|
|
955
|
-
of: 'of',
|
|
956
|
-
clear: 'Clear',
|
|
957
|
-
noModelsMatch: 'No models found matching query.',
|
|
958
|
-
useCustom: 'Use custom',
|
|
959
|
-
selectModel: 'Select model...',
|
|
960
|
-
},
|
|
961
|
-
ru: {
|
|
962
|
-
title: 'Mixture of Agents (MoA)',
|
|
963
|
-
description: 'Запуск задач через ансамбль параллельных моделей-советников и модель-судью',
|
|
964
|
-
slashMoa: 'Запустить задачу через архитектуру Mixture of Agents (/moa <prompt>)',
|
|
965
|
-
defaultPreset: 'Основной пресет (при вызове /moa без параметров)',
|
|
966
|
-
isDefault: 'основной',
|
|
967
|
-
presetSelectLabel: 'Текущий пресет для настройки:',
|
|
968
|
-
createNewPreset: 'Создать новый пресет...',
|
|
969
|
-
makeDefault: 'Сделать основным',
|
|
970
|
-
makeDefaultHint: 'Использовать этот пресет для /moa при вызове без параметров',
|
|
971
|
-
deletePreset: 'Удалить пресет',
|
|
972
|
-
delete: 'Удалить',
|
|
973
|
-
confirmDeletePreset: 'Вы уверены, что хотите удалить пресет',
|
|
974
|
-
newPresetPlaceholder: 'Название нового пресета (например: fast-audit)',
|
|
975
|
-
create: 'Создать',
|
|
976
|
-
cancel: 'Отмена',
|
|
977
|
-
aggregatorTitle: 'Ведущая модель / Судья (кто проверяет и объединяет)',
|
|
978
|
-
aggregatorDesc: 'Сопоставляет варианты кандидатов, устраняет ошибки и синтезирует итоговое решение.',
|
|
979
|
-
temperature: 'Температура',
|
|
980
|
-
aggTempHint: 'Рекомендуется 0.2 – 0.4 для строгой проверки и синтеза без выдумок.',
|
|
981
|
-
proposersTitle: 'Модели-кандидаты (советники)',
|
|
982
|
-
proposersDesc: 'Параллельно формируют независимые варианты решения одной задачи.',
|
|
983
|
-
refTempLabel: 'Температура кандидатов (разнообразие решений):',
|
|
984
|
-
refTempHint: 'Рекомендуется 0.6 – 0.8 для получения независимых и креативных вариантов.',
|
|
985
|
-
parallelCount: 'Параллельных моделей',
|
|
986
|
-
candidate: 'Кандидат',
|
|
987
|
-
addCandidate: 'Добавить модель в параллель',
|
|
988
|
-
remove: 'Удалить',
|
|
989
|
-
save: 'Сохранить',
|
|
990
|
-
saved: 'Успешно сохранено',
|
|
991
|
-
loading: 'Сохранение…',
|
|
992
|
-
retry: 'Повторить подключение',
|
|
993
|
-
unavailable: 'Служба MoA временно недоступна (переподключение...)',
|
|
994
|
-
searchPlaceholder: 'Поиск модели по названию или провайдеру...',
|
|
995
|
-
modelsFound: 'Найдено',
|
|
996
|
-
of: 'из',
|
|
997
|
-
clear: 'Сбросить',
|
|
998
|
-
noModelsMatch: 'Модели не найдены.',
|
|
999
|
-
useCustom: 'Использовать кастомное',
|
|
1000
|
-
selectModel: 'Выберите модель...',
|
|
1001
|
-
},
|
|
1002
|
-
})
|
|
1121
|
+
return ctx.locale.register(NS, locale, dictionary)
|
|
1003
1122
|
}
|
|
1004
|
-
} catch
|
|
1005
|
-
|
|
1123
|
+
} catch {
|
|
1124
|
+
// Если язык уже занят (например, dsh-russian-lang) или зарегистрирован ранее — уступаем без ошибки
|
|
1125
|
+
return () => {}
|
|
1006
1126
|
}
|
|
1127
|
+
return () => {}
|
|
1007
1128
|
}
|
|
1008
1129
|
|
|
1009
1130
|
if (typeof ctx.effect === 'function') {
|
|
1010
|
-
ctx.effect(() =>
|
|
1131
|
+
ctx.effect(() => {
|
|
1132
|
+
const undo = [addLocale('en', en), addLocale('ru', ru)]
|
|
1133
|
+
return () => {
|
|
1134
|
+
for (const off of undo) {
|
|
1135
|
+
if (typeof off === 'function') {
|
|
1136
|
+
try { off() } catch {}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}, 'dsh-moa: dictionaries')
|
|
1011
1141
|
} else {
|
|
1012
|
-
|
|
1142
|
+
addLocale('en', en)
|
|
1143
|
+
addLocale('ru', ru)
|
|
1013
1144
|
}
|
|
1014
1145
|
|
|
1015
|
-
const t = (key) => {
|
|
1016
|
-
try {
|
|
1017
|
-
if (ctx.locale && typeof ctx.locale.bind === 'function') {
|
|
1018
|
-
return ctx.locale.bind(NS)(key) || key
|
|
1019
|
-
}
|
|
1020
|
-
} catch {}
|
|
1021
|
-
return key
|
|
1022
|
-
}
|
|
1023
1146
|
|
|
1024
1147
|
if (ctx.slots) {
|
|
1025
|
-
const
|
|
1026
|
-
try {
|
|
1027
|
-
ctx.slots.register(
|
|
1028
|
-
{
|
|
1029
|
-
name: 'settings.section',
|
|
1030
|
-
id: NS,
|
|
1031
|
-
order: 35,
|
|
1032
|
-
locale: NS,
|
|
1033
|
-
label: () => t('title'),
|
|
1034
|
-
inject: () => ({ ctx, t }),
|
|
1035
|
-
},
|
|
1036
|
-
MoASection
|
|
1037
|
-
)
|
|
1038
|
-
} catch (err) {
|
|
1039
|
-
console.warn('[dsh-moa] Failed to register settings.section:', err)
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
const registerPluginCard = () => {
|
|
1148
|
+
const registerCard = () => {
|
|
1044
1149
|
try {
|
|
1045
1150
|
ctx.slots.register(
|
|
1046
1151
|
{
|
|
@@ -1048,10 +1153,9 @@ window.__ModuleLoader__.load({
|
|
|
1048
1153
|
key: NS,
|
|
1049
1154
|
order: 35,
|
|
1050
1155
|
locale: NS,
|
|
1051
|
-
|
|
1052
|
-
inject: () => ({ ctx, t }),
|
|
1156
|
+
inject: () => ({ ctx }),
|
|
1053
1157
|
},
|
|
1054
|
-
MoACard
|
|
1158
|
+
(props) => React.createElement(MoAErrorBoundary, null, React.createElement(MoACard, props))
|
|
1055
1159
|
)
|
|
1056
1160
|
} catch (err) {
|
|
1057
1161
|
console.warn('[dsh-moa] Failed to register settings.plugin.item:', err)
|
|
@@ -1059,11 +1163,9 @@ window.__ModuleLoader__.load({
|
|
|
1059
1163
|
}
|
|
1060
1164
|
|
|
1061
1165
|
if (typeof ctx.slots.inject === 'function') {
|
|
1062
|
-
ctx.slots.inject('settings.
|
|
1063
|
-
ctx.slots.inject('settings.plugin.item', registerPluginCard)
|
|
1166
|
+
ctx.slots.inject('settings.plugin.item', registerCard)
|
|
1064
1167
|
} else {
|
|
1065
|
-
|
|
1066
|
-
registerPluginCard()
|
|
1168
|
+
registerCard()
|
|
1067
1169
|
}
|
|
1068
1170
|
}
|
|
1069
1171
|
|
package/lib/file-workspace.js
CHANGED
|
@@ -85,6 +85,89 @@ function sanitizePath(filePath) {
|
|
|
85
85
|
return normalized.replace(/^[\\\/]+/, '')
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Collects readable text files from current workspace to feed as context for candidate models (#6).
|
|
90
|
+
* Ignores node_modules, .git, .moa, binary files, large bundles.
|
|
91
|
+
*/
|
|
92
|
+
export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
|
|
93
|
+
if (!baseDir) return { files: [], totalChars: 0 }
|
|
94
|
+
|
|
95
|
+
const ignoreDirs = new Set(['node_modules', '.git', '.moa', '.worktrees', 'dist', 'build', '.dsh'])
|
|
96
|
+
const codeExts = new Set([
|
|
97
|
+
'.html', '.htm', '.css', '.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx',
|
|
98
|
+
'.vue', '.svelte', '.json', '.py', '.md', '.txt', '.sh', '.yaml', '.yml',
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
const collected = []
|
|
102
|
+
let totalChars = 0
|
|
103
|
+
|
|
104
|
+
async function scan(currentDir, relPrefix = '') {
|
|
105
|
+
if (totalChars >= maxCharBudget) return
|
|
106
|
+
try {
|
|
107
|
+
const list = await fs.readdir(currentDir, { withFileTypes: true })
|
|
108
|
+
for (const item of list) {
|
|
109
|
+
if (totalChars >= maxCharBudget) break
|
|
110
|
+
if (item.name.startsWith('.') && item.name !== '.env') continue
|
|
111
|
+
|
|
112
|
+
const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name
|
|
113
|
+
const full = path.join(currentDir, item.name)
|
|
114
|
+
|
|
115
|
+
if (item.isDirectory()) {
|
|
116
|
+
if (!ignoreDirs.has(item.name)) {
|
|
117
|
+
await scan(full, rel)
|
|
118
|
+
}
|
|
119
|
+
} else if (item.isFile()) {
|
|
120
|
+
const ext = path.extname(item.name).toLowerCase()
|
|
121
|
+
if (codeExts.has(ext)) {
|
|
122
|
+
try {
|
|
123
|
+
const stat = await fs.stat(full)
|
|
124
|
+
if (stat.size > 100000) continue // skip files > 100kb
|
|
125
|
+
const content = await fs.readFile(full, 'utf8')
|
|
126
|
+
const budgetRemaining = maxCharBudget - totalChars
|
|
127
|
+
const truncated = content.length > budgetRemaining ? content.slice(0, budgetRemaining) + '\n... [truncated]' : content
|
|
128
|
+
totalChars += truncated.length
|
|
129
|
+
collected.push({ relativePath: rel, content: truncated, size: stat.size })
|
|
130
|
+
} catch {}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await scan(baseDir)
|
|
138
|
+
return { files: collected, totalChars }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Checks if a task prompt is an iterative refinement / modification
|
|
143
|
+
* of an existing project rather than a greenfield project creation (#4).
|
|
144
|
+
*/
|
|
145
|
+
export function isRefinementTask(userPrompt = '', projectFiles = []) {
|
|
146
|
+
if (!Array.isArray(projectFiles) || projectFiles.length === 0) return false
|
|
147
|
+
const p = userPrompt.toLowerCase().trim()
|
|
148
|
+
if (!p) return false
|
|
149
|
+
|
|
150
|
+
// Greenfield creation phrases - ALWAYS fresh task
|
|
151
|
+
const freshPhrases = [
|
|
152
|
+
'с нуля', 'from scratch', 'новый проект', 'новое приложение', 'new project', 'new app',
|
|
153
|
+
'создай проект', 'создай приложение', 'создай игру', 'создай сервис',
|
|
154
|
+
'сделай проект', 'сделай приложение', 'сделай игру',
|
|
155
|
+
'create a new', 'build a new', 'generate a new',
|
|
156
|
+
]
|
|
157
|
+
if (freshPhrases.some((phrase) => p.includes(phrase))) {
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Modification keywords
|
|
162
|
+
const modKeywords = [
|
|
163
|
+
'добавь', 'измени', 'поменяй', 'исправь', 'обнови', 'переделай', 'доработай', 'удали',
|
|
164
|
+
'add', 'change', 'update', 'fix', 'modify', 'refactor', 'improve', 'enhance', 'adjust',
|
|
165
|
+
'style', 'css', 'темн', 'светл', 'color', 'цвет', 'кнопк', 'theme', 'bug', 'баг',
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
return modKeywords.some((kw) => p.includes(kw))
|
|
169
|
+
}
|
|
170
|
+
|
|
88
171
|
/**
|
|
89
172
|
* Writes candidate files into an isolated candidate workspace:
|
|
90
173
|
* <baseDir>/.moa/candidate-<index>/
|
|
@@ -114,7 +197,7 @@ export async function writeCandidateWorkspace(baseDir, candidateIndex, files = [
|
|
|
114
197
|
* Promotes winning candidate files to baseDir root,
|
|
115
198
|
* then cleans up the temporary .moa folder.
|
|
116
199
|
*/
|
|
117
|
-
export async function promoteCandidateWorkspace(baseDir, winningIndex) {
|
|
200
|
+
export async function promoteCandidateWorkspace(baseDir, winningIndex, options = { keepMoa: false }) {
|
|
118
201
|
if (!baseDir || winningIndex === null || winningIndex === undefined) return []
|
|
119
202
|
|
|
120
203
|
const winningDir = path.join(baseDir, '.moa', `candidate-${winningIndex}`)
|
|
@@ -123,7 +206,9 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex) {
|
|
|
123
206
|
try {
|
|
124
207
|
const entries = await getFilesRecursively(winningDir)
|
|
125
208
|
if (entries.length === 0) {
|
|
126
|
-
|
|
209
|
+
if (!options.keepMoa) {
|
|
210
|
+
try { await fs.rm(moaRoot, { recursive: true, force: true }) } catch {}
|
|
211
|
+
}
|
|
127
212
|
return []
|
|
128
213
|
}
|
|
129
214
|
|
|
@@ -137,12 +222,16 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex) {
|
|
|
137
222
|
promoted.push(rel)
|
|
138
223
|
}
|
|
139
224
|
|
|
140
|
-
|
|
141
|
-
|
|
225
|
+
if (!options.keepMoa) {
|
|
226
|
+
// Clean up temporary .moa folder
|
|
227
|
+
await fs.rm(moaRoot, { recursive: true, force: true })
|
|
228
|
+
}
|
|
142
229
|
|
|
143
230
|
return promoted
|
|
144
231
|
} catch (err) {
|
|
145
|
-
|
|
232
|
+
if (!options.keepMoa) {
|
|
233
|
+
try { await fs.rm(moaRoot, { recursive: true, force: true }) } catch {}
|
|
234
|
+
}
|
|
146
235
|
return []
|
|
147
236
|
}
|
|
148
237
|
}
|