@goodandready/dsh-moa 0.2.7 → 0.2.8
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/lib/client.js +21 -1
- package/lib/file-workspace.js +1 -1
- package/lib/index.js +37 -5
- package/lib/moa-runner.js +89 -42
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -650,7 +650,27 @@ window.__ModuleLoader__.load({
|
|
|
650
650
|
placeholder: t('judgeCriteriaPlaceholder') || 'например: Приоритет — производительность, чистый код, минимум зависимостей...',
|
|
651
651
|
value: judgeCriteria,
|
|
652
652
|
onChange: (e) => setJudgeCriteria(e.target.value),
|
|
653
|
-
})
|
|
653
|
+
}),
|
|
654
|
+
React.createElement(
|
|
655
|
+
'div',
|
|
656
|
+
{ style: { marginTop: 8, display: 'flex', flexDirection: 'column', gap: 4 } },
|
|
657
|
+
React.createElement(
|
|
658
|
+
'label',
|
|
659
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500 } },
|
|
660
|
+
React.createElement('input', {
|
|
661
|
+
type: 'checkbox',
|
|
662
|
+
checked: currentPreset.ask_clarifying_questions !== false,
|
|
663
|
+
onChange: (e) => updateCurrentPreset((p) => ({ ...p, ask_clarifying_questions: e.target.checked })),
|
|
664
|
+
style: { accentColor: 'var(--dsw-alias-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
665
|
+
}),
|
|
666
|
+
t('askQuestionsLabel') || 'Спрашивать уточняющие вопросы при неясных задачах (Опросник)'
|
|
667
|
+
),
|
|
668
|
+
React.createElement(
|
|
669
|
+
'div',
|
|
670
|
+
{ className: 'moa-hint', style: { marginLeft: 22 } },
|
|
671
|
+
t('askQuestionsHint') || 'Если включено: при коротких задачах судья сформирует опросник. Если выключено: MoA сразу переходит к генерации решения.'
|
|
672
|
+
)
|
|
673
|
+
)
|
|
654
674
|
)
|
|
655
675
|
),
|
|
656
676
|
/* Proposers / Advisors Box with Temperature Slider */
|
package/lib/file-workspace.js
CHANGED
|
@@ -92,7 +92,7 @@ function sanitizePath(filePath) {
|
|
|
92
92
|
export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
|
|
93
93
|
if (!baseDir) return { files: [], totalChars: 0 }
|
|
94
94
|
|
|
95
|
-
const ignoreDirs = new Set(['node_modules', '.git', '.moa', '.worktrees', 'dist', 'build', '.dsh'])
|
|
95
|
+
const ignoreDirs = new Set(['node_modules', '.git', '.moa', '.worktrees', 'dist', 'build', '.dsh', '.next', '.nuxt', '.cache', 'coverage', '.turbo', '.idea', '.vscode'])
|
|
96
96
|
const codeExts = new Set([
|
|
97
97
|
'.html', '.htm', '.css', '.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx',
|
|
98
98
|
'.vue', '.svelte', '.json', '.py', '.md', '.txt', '.sh', '.yaml', '.yml',
|
package/lib/index.js
CHANGED
|
@@ -44,6 +44,7 @@ export const ModelSlotSchema = z.object({
|
|
|
44
44
|
export const PresetSchema = z.object({
|
|
45
45
|
name: z.string(),
|
|
46
46
|
enabled: z.boolean().default(true),
|
|
47
|
+
ask_clarifying_questions: z.boolean().default(true),
|
|
47
48
|
reference_models: z.array(ModelSlotSchema).default([]),
|
|
48
49
|
aggregator: ModelSlotSchema.default({ provider: 'codex', model: 'gpt-5.6-sol' }),
|
|
49
50
|
reference_temperature: z.number().default(0.6),
|
|
@@ -61,6 +62,7 @@ export const Config = z.object({
|
|
|
61
62
|
{
|
|
62
63
|
name: 'default',
|
|
63
64
|
enabled: true,
|
|
65
|
+
ask_clarifying_questions: true,
|
|
64
66
|
reference_models: [
|
|
65
67
|
{ provider: 'opencode-go', model: 'deepseek-v4-flash' },
|
|
66
68
|
{ provider: 'grok', model: 'grok-build-0.1' },
|
|
@@ -75,6 +77,7 @@ export const Config = z.object({
|
|
|
75
77
|
{
|
|
76
78
|
name: 'fast',
|
|
77
79
|
enabled: true,
|
|
80
|
+
ask_clarifying_questions: false,
|
|
78
81
|
reference_models: [
|
|
79
82
|
{ provider: 'opencode-go', model: 'deepseek-v4-flash' },
|
|
80
83
|
],
|
|
@@ -88,6 +91,7 @@ export const Config = z.object({
|
|
|
88
91
|
{
|
|
89
92
|
name: 'deep-reasoning',
|
|
90
93
|
enabled: true,
|
|
94
|
+
ask_clarifying_questions: true,
|
|
91
95
|
reference_models: [
|
|
92
96
|
{ provider: 'grok', model: 'grok-build-0.1' },
|
|
93
97
|
{ provider: 'commandcode', model: 'deepseek/deepseek-v4-flash' },
|
|
@@ -449,11 +453,39 @@ export function apply(ctx, config) {
|
|
|
449
453
|
},
|
|
450
454
|
})
|
|
451
455
|
}, 'dsh-moa: run pipeline route')
|
|
452
|
-
|
|
453
456
|
// Session turn interceptor: route turn to real aggregator model & stream MoA synthesis
|
|
454
457
|
const moaPendingTurns = new WeakMap()
|
|
455
458
|
const moaSessionTurns = new Map()
|
|
456
459
|
|
|
460
|
+
function setSessionTurn(sessionId, turnContext) {
|
|
461
|
+
if (!sessionId) return
|
|
462
|
+
const existing = moaSessionTurns.get(sessionId)
|
|
463
|
+
if (existing?.ttlTimer) clearTimeout(existing.ttlTimer)
|
|
464
|
+
|
|
465
|
+
// TTL (3 minutes) to prevent memory leaks if turn is aborted or skipped before llm/stream
|
|
466
|
+
const ttlTimer = setTimeout(() => {
|
|
467
|
+
moaSessionTurns.delete(sessionId)
|
|
468
|
+
}, 180000)
|
|
469
|
+
ttlTimer.unref?.()
|
|
470
|
+
|
|
471
|
+
moaSessionTurns.set(sessionId, { ...turnContext, ttlTimer })
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function getSessionTurn(sessionId) {
|
|
475
|
+
if (!sessionId) return null
|
|
476
|
+
return moaSessionTurns.get(sessionId) || null
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function consumeSessionTurn(sessionId) {
|
|
480
|
+
if (!sessionId) return null
|
|
481
|
+
const entry = moaSessionTurns.get(sessionId)
|
|
482
|
+
if (entry) {
|
|
483
|
+
if (entry.ttlTimer) clearTimeout(entry.ttlTimer)
|
|
484
|
+
moaSessionTurns.delete(sessionId)
|
|
485
|
+
}
|
|
486
|
+
return entry
|
|
487
|
+
}
|
|
488
|
+
|
|
457
489
|
ctx.on('agent/pre-step', async (event, next) => {
|
|
458
490
|
const messages = event.messages || []
|
|
459
491
|
const lastUserMsg = [...messages].reverse().find((m) => m && m.role === 'user')
|
|
@@ -485,7 +517,7 @@ export function apply(ctx, config) {
|
|
|
485
517
|
moaPendingTurns.set(event.signal, turnContext)
|
|
486
518
|
}
|
|
487
519
|
if (event.session?.id) {
|
|
488
|
-
|
|
520
|
+
setSessionTurn(event.session.id, turnContext)
|
|
489
521
|
}
|
|
490
522
|
}
|
|
491
523
|
}
|
|
@@ -496,7 +528,7 @@ export function apply(ctx, config) {
|
|
|
496
528
|
ctx.on('agent/request', async (payload, next) => {
|
|
497
529
|
const resolved = await next()
|
|
498
530
|
const turnContext = (payload.signal ? moaPendingTurns.get(payload.signal) : null)
|
|
499
|
-
|| (payload.session?.id ?
|
|
531
|
+
|| (payload.session?.id ? getSessionTurn(payload.session.id) : null)
|
|
500
532
|
|
|
501
533
|
if (turnContext) {
|
|
502
534
|
return {
|
|
@@ -511,11 +543,11 @@ export function apply(ctx, config) {
|
|
|
511
543
|
ctx.effect(() => {
|
|
512
544
|
return ctx.on('llm/stream', (options, next) => {
|
|
513
545
|
const turnContext = (options.signal ? moaPendingTurns.get(options.signal) : null)
|
|
514
|
-
|| (options.session?.id ?
|
|
546
|
+
|| (options.session?.id ? consumeSessionTurn(options.session.id) : null)
|
|
515
547
|
|
|
516
548
|
if (turnContext) {
|
|
517
549
|
if (options.signal) moaPendingTurns.delete(options.signal)
|
|
518
|
-
if (options.session?.id)
|
|
550
|
+
if (options.session?.id) consumeSessionTurn(options.session.id)
|
|
519
551
|
|
|
520
552
|
return streamMoATurn(turnContext, options)
|
|
521
553
|
}
|
package/lib/moa-runner.js
CHANGED
|
@@ -383,7 +383,8 @@ export async function runMoAPipeline({
|
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
// Phase 1: Check if clarifying questions should be asked
|
|
386
|
-
const
|
|
386
|
+
const allowQuestions = preset?.ask_clarifying_questions !== false && preset?.askClarifyingQuestions !== false
|
|
387
|
+
const needsQuestions = allowQuestions && !skipQuestions && !isRefinement && isBroadPromptRequiringQuestions(userPrompt, messages)
|
|
387
388
|
if (needsQuestions) {
|
|
388
389
|
if (typeof onProgress === 'function') {
|
|
389
390
|
onProgress('🔍 *Задача общего характера. Советники формируют ключевые развилки...*\n\n')
|
|
@@ -446,6 +447,32 @@ export async function runMoAPipeline({
|
|
|
446
447
|
onProgress,
|
|
447
448
|
)
|
|
448
449
|
|
|
450
|
+
// Fast-fail: If all candidates failed, do not call judge to save tokens and avoid synthesized hallucinations
|
|
451
|
+
const allFailed = referenceOutputs.length > 0 && referenceOutputs.every((r) => !r.ok)
|
|
452
|
+
if (allFailed) {
|
|
453
|
+
const errorDetails = referenceOutputs
|
|
454
|
+
.map((r, i) => `• Кандидат ${i + 1} (${r.label}): ${r.text}`)
|
|
455
|
+
.join('\n')
|
|
456
|
+
const failMessage = `⚠️ **Все модели-советники (${referenceOutputs.length}) завершились с ошибкой**.\n\nСудья не вызывался для предотвращения бессмысленного расхода токенов.\n\n### Детали ошибок:\n${errorDetails}`
|
|
457
|
+
|
|
458
|
+
await cleanMoaWorkspaces(workDir)
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
kind: 'failure',
|
|
462
|
+
content: failMessage,
|
|
463
|
+
aggregator: slotLabel(aggregator),
|
|
464
|
+
references: referenceOutputs,
|
|
465
|
+
presetName: preset?.name || 'default',
|
|
466
|
+
promotedFiles: [],
|
|
467
|
+
usage: {
|
|
468
|
+
totalTokens: 0,
|
|
469
|
+
totalCostUsd: 0,
|
|
470
|
+
candidates: referenceOutputs.map((r) => ({ label: r.label, usage: r.usage, costUsd: 0 })),
|
|
471
|
+
aggregator: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 },
|
|
472
|
+
},
|
|
473
|
+
durationMs: Date.now() - startTime,
|
|
474
|
+
}
|
|
475
|
+
}
|
|
449
476
|
// Write files for each candidate into .moa/candidate-X/
|
|
450
477
|
for (let i = 0; i < referenceOutputs.length; i++) {
|
|
451
478
|
const ref = referenceOutputs[i]
|
|
@@ -691,73 +718,93 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
|
|
|
691
718
|
yield { type: 'block-start', index: 0, blockType: 'text' }
|
|
692
719
|
yield { type: 'text-delta', index: 0, text: '🧠 *Mixture of Agents запущен...*\n\n' }
|
|
693
720
|
|
|
694
|
-
//
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
721
|
+
// Async push-queue for zero-latency live delta streaming
|
|
722
|
+
const queue = []
|
|
723
|
+
let notify = null
|
|
724
|
+
|
|
725
|
+
const pushUpdate = (text) => {
|
|
726
|
+
queue.push({ type: 'text-delta', index: 0, text })
|
|
727
|
+
if (notify) {
|
|
728
|
+
notify()
|
|
729
|
+
notify = null
|
|
730
|
+
}
|
|
698
731
|
}
|
|
699
732
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
733
|
+
let done = false
|
|
734
|
+
const pipelinePromise = runMoAPipeline({
|
|
735
|
+
userPrompt,
|
|
736
|
+
messages,
|
|
737
|
+
preset: targetPreset,
|
|
738
|
+
callLlm,
|
|
739
|
+
cwd,
|
|
740
|
+
onProgress: pushUpdate,
|
|
741
|
+
})
|
|
742
|
+
.catch((err) => ({ error: err }))
|
|
743
|
+
.finally(() => {
|
|
744
|
+
done = true
|
|
745
|
+
if (notify) {
|
|
746
|
+
notify()
|
|
747
|
+
notify = null
|
|
748
|
+
}
|
|
708
749
|
})
|
|
709
750
|
|
|
710
|
-
|
|
711
|
-
|
|
751
|
+
const startTime = Date.now()
|
|
752
|
+
let lastYieldTime = Date.now()
|
|
712
753
|
|
|
713
|
-
|
|
714
|
-
while (
|
|
715
|
-
if (signal?.aborted)
|
|
754
|
+
try {
|
|
755
|
+
while (!done || queue.length > 0) {
|
|
756
|
+
if (signal?.aborted) {
|
|
757
|
+
await cleanMoaWorkspaces(cwd)
|
|
758
|
+
return
|
|
759
|
+
}
|
|
716
760
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
yield { type: 'text-delta', index: 0, text: chunk }
|
|
721
|
-
emittedAny = true
|
|
761
|
+
while (queue.length > 0) {
|
|
762
|
+
const item = queue.shift()
|
|
763
|
+
yield item
|
|
722
764
|
lastYieldTime = Date.now()
|
|
723
765
|
}
|
|
724
766
|
|
|
725
|
-
const done = await Promise.race([
|
|
726
|
-
pipelinePromise.then(() => true),
|
|
727
|
-
new Promise((r) => { const t = setTimeout(() => r(false), 500); t.unref?.(); }),
|
|
728
|
-
])
|
|
729
767
|
if (done) break
|
|
730
768
|
|
|
769
|
+
// Wait for next push item or max 2.5s heartbeat
|
|
770
|
+
await Promise.race([
|
|
771
|
+
new Promise((resolve) => { notify = resolve }),
|
|
772
|
+
new Promise((resolve) => { const t = setTimeout(resolve, 2500); t.unref?.(); }),
|
|
773
|
+
])
|
|
774
|
+
|
|
731
775
|
const elapsedSec = Math.floor((Date.now() - startTime) / 1000)
|
|
732
|
-
|
|
733
|
-
if (!emittedAny && Date.now() - lastYieldTime >= 3000) {
|
|
776
|
+
if (!done && Date.now() - lastYieldTime >= 3000) {
|
|
734
777
|
yield { type: 'text-delta', index: 0, text: `⏳ *[${elapsedSec}с] Идёт обработка...*\n` }
|
|
735
778
|
lastYieldTime = Date.now()
|
|
736
779
|
}
|
|
737
780
|
}
|
|
738
781
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
782
|
+
const result = await pipelinePromise
|
|
783
|
+
if (signal?.aborted) {
|
|
784
|
+
await cleanMoaWorkspaces(cwd)
|
|
785
|
+
return
|
|
742
786
|
}
|
|
743
787
|
|
|
744
|
-
|
|
745
|
-
|
|
788
|
+
if (result?.error) {
|
|
789
|
+
const errText = '\n\n⚠️ **Ошибка Mixture of Agents**: ' + (result.error?.message || String(result.error))
|
|
790
|
+
yield { type: 'text-delta', index: 0, text: errText }
|
|
791
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
792
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
793
|
+
return
|
|
794
|
+
}
|
|
746
795
|
|
|
747
796
|
const formatted = formatMoAResponse({
|
|
748
|
-
moaResult,
|
|
797
|
+
moaResult: result,
|
|
749
798
|
presetName: targetPreset?.name || 'default',
|
|
750
799
|
})
|
|
751
800
|
|
|
752
801
|
yield { type: 'text-delta', index: 0, text: '\n---\n\n' + formatted }
|
|
753
802
|
yield { type: 'block-end', index: 0, block: { type: 'text', text: formatted } }
|
|
754
|
-
yield { type: 'usage', usage: { inputTokens:
|
|
755
|
-
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
756
|
-
} catch (err) {
|
|
757
|
-
if (signal?.aborted) return
|
|
758
|
-
const errText = '\n\n⚠️ **Ошибка Mixture of Agents**: ' + (err?.message || String(err))
|
|
759
|
-
yield { type: 'text-delta', index: 0, text: errText }
|
|
760
|
-
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
803
|
+
yield { type: 'usage', usage: { inputTokens: result?.usage?.totalTokens || 100, outputTokens: formatted.length } }
|
|
761
804
|
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
805
|
+
} finally {
|
|
806
|
+
if (signal?.aborted) {
|
|
807
|
+
await cleanMoaWorkspaces(cwd)
|
|
808
|
+
}
|
|
762
809
|
}
|
|
763
810
|
}
|