@goodandready/dsh-moa 0.2.6 → 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 +75 -44
- package/lib/moa-runner.js +105 -96
- 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
|
@@ -16,7 +16,7 @@ import os from 'node:os'
|
|
|
16
16
|
import {
|
|
17
17
|
parseMoACommand,
|
|
18
18
|
runMoAPipeline,
|
|
19
|
-
|
|
19
|
+
streamMoATurn,
|
|
20
20
|
} from './moa-runner.js'
|
|
21
21
|
|
|
22
22
|
import {
|
|
@@ -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,45 +453,38 @@ export function apply(ctx, config) {
|
|
|
449
453
|
},
|
|
450
454
|
})
|
|
451
455
|
}, 'dsh-moa: run pipeline route')
|
|
456
|
+
// Session turn interceptor: route turn to real aggregator model & stream MoA synthesis
|
|
457
|
+
const moaPendingTurns = new WeakMap()
|
|
458
|
+
const moaSessionTurns = new Map()
|
|
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
|
+
}
|
|
452
473
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const adapter = new MoaRunnerAdapter((options) => {
|
|
457
|
-
const msgs = options.messages || []
|
|
458
|
-
const lastUser = [...msgs].reverse().find((m) => m && m.role === 'user')
|
|
459
|
-
const userText = typeof lastUser?.content === 'string'
|
|
460
|
-
? lastUser.content
|
|
461
|
-
: (Array.isArray(lastUser?.content)
|
|
462
|
-
? lastUser.content.filter((p) => p.type === 'text').map((p) => p.text).join('\n')
|
|
463
|
-
: '')
|
|
464
|
-
|
|
465
|
-
const cfg = live()
|
|
466
|
-
const parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: 'default' }
|
|
467
|
-
const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName) || cfg.presets?.[0]
|
|
468
|
-
|
|
469
|
-
const extraCtx = options.signal ? moaPendingContexts.get(options.signal) : null
|
|
470
|
-
const cwd = options.cwd || extraCtx?.cwd || process.cwd()
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
targetPreset,
|
|
474
|
-
userPrompt: parsed.prompt || userText,
|
|
475
|
-
messages: msgs.slice(0, -1),
|
|
476
|
-
callLlm,
|
|
477
|
-
cwd,
|
|
478
|
-
}
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
const dispose = ctx.llm.registerAdapter(['moa-runner'], adapter)
|
|
482
|
-
return () => {
|
|
483
|
-
if (typeof dispose === 'function') dispose()
|
|
484
|
-
}
|
|
485
|
-
}, 'dsh-moa: llm adapter')
|
|
474
|
+
function getSessionTurn(sessionId) {
|
|
475
|
+
if (!sessionId) return null
|
|
476
|
+
return moaSessionTurns.get(sessionId) || null
|
|
486
477
|
}
|
|
487
478
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
+
}
|
|
491
488
|
|
|
492
489
|
ctx.on('agent/pre-step', async (event, next) => {
|
|
493
490
|
const messages = event.messages || []
|
|
@@ -501,10 +498,26 @@ export function apply(ctx, config) {
|
|
|
501
498
|
if (userText.startsWith('/moa')) {
|
|
502
499
|
const cfg = live()
|
|
503
500
|
if (cfg.enabled !== false) {
|
|
501
|
+
const parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: cfg.default_preset || 'default' }
|
|
502
|
+
const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName) || cfg.presets?.[0]
|
|
503
|
+
const aggProvider = targetPreset?.aggregator?.provider || 'codex'
|
|
504
|
+
const aggModel = targetPreset?.aggregator?.model || 'gpt-5.6-sol'
|
|
505
|
+
const sessCwd = event.cwd || event.session?.cwd || (event.session?.path ? path.dirname(event.session.path) : undefined) || process.cwd()
|
|
506
|
+
|
|
507
|
+
const turnContext = {
|
|
508
|
+
targetPreset,
|
|
509
|
+
userPrompt: parsed.prompt || userText,
|
|
510
|
+
messages: messages.slice(0, -1),
|
|
511
|
+
cwd: sessCwd,
|
|
512
|
+
aggregator: { provider: aggProvider, model: aggModel },
|
|
513
|
+
callLlm,
|
|
514
|
+
}
|
|
515
|
+
|
|
504
516
|
if (event.signal) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
517
|
+
moaPendingTurns.set(event.signal, turnContext)
|
|
518
|
+
}
|
|
519
|
+
if (event.session?.id) {
|
|
520
|
+
setSessionTurn(event.session.id, turnContext)
|
|
508
521
|
}
|
|
509
522
|
}
|
|
510
523
|
}
|
|
@@ -514,14 +527,32 @@ export function apply(ctx, config) {
|
|
|
514
527
|
|
|
515
528
|
ctx.on('agent/request', async (payload, next) => {
|
|
516
529
|
const resolved = await next()
|
|
517
|
-
|
|
518
|
-
|
|
530
|
+
const turnContext = (payload.signal ? moaPendingTurns.get(payload.signal) : null)
|
|
531
|
+
|| (payload.session?.id ? getSessionTurn(payload.session.id) : null)
|
|
532
|
+
|
|
533
|
+
if (turnContext) {
|
|
519
534
|
return {
|
|
520
535
|
...resolved,
|
|
521
|
-
provider:
|
|
522
|
-
model:
|
|
536
|
+
provider: turnContext.aggregator.provider,
|
|
537
|
+
model: turnContext.aggregator.model,
|
|
523
538
|
}
|
|
524
539
|
}
|
|
525
540
|
return resolved
|
|
526
541
|
})
|
|
542
|
+
|
|
543
|
+
ctx.effect(() => {
|
|
544
|
+
return ctx.on('llm/stream', (options, next) => {
|
|
545
|
+
const turnContext = (options.signal ? moaPendingTurns.get(options.signal) : null)
|
|
546
|
+
|| (options.session?.id ? consumeSessionTurn(options.session.id) : null)
|
|
547
|
+
|
|
548
|
+
if (turnContext) {
|
|
549
|
+
if (options.signal) moaPendingTurns.delete(options.signal)
|
|
550
|
+
if (options.session?.id) consumeSessionTurn(options.session.id)
|
|
551
|
+
|
|
552
|
+
return streamMoATurn(turnContext, options)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return next(options)
|
|
556
|
+
})
|
|
557
|
+
}, 'dsh-moa: llm stream interceptor')
|
|
527
558
|
}
|
package/lib/moa-runner.js
CHANGED
|
@@ -231,7 +231,7 @@ export function parseMoACommand(text, presets = []) {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
const matchPreset = /^--preset
|
|
234
|
+
const matchPreset = /^--preset(?:=|\s+)([a-zA-Z0-9_-]+)\s*(.*)/s.exec(remainder)
|
|
235
235
|
if (matchPreset) {
|
|
236
236
|
return {
|
|
237
237
|
presetName: matchPreset[1],
|
|
@@ -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]
|
|
@@ -684,118 +711,100 @@ export function formatMoAResponse({ moaResult, presetName }) {
|
|
|
684
711
|
return parts.join('\n')
|
|
685
712
|
}
|
|
686
713
|
|
|
687
|
-
export
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
providerInfo(provider) {
|
|
693
|
-
return { id: provider, name: 'Mixture of Agents Runner' }
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
providerRetryPolicy(_provider) {
|
|
697
|
-
return undefined
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
imageRequestPricing(_provider, _model) {
|
|
701
|
-
return undefined
|
|
702
|
-
}
|
|
714
|
+
export async function* streamMoATurn({ targetPreset, userPrompt, messages, callLlm, cwd }, options = {}) {
|
|
715
|
+
const signal = options?.signal
|
|
716
|
+
if (signal?.aborted) return
|
|
703
717
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
return [{ provider: prov, id: 'ensemble', name: 'MoA Ensemble' }]
|
|
707
|
-
}
|
|
718
|
+
yield { type: 'block-start', index: 0, blockType: 'text' }
|
|
719
|
+
yield { type: 'text-delta', index: 0, text: '🧠 *Mixture of Agents запущен...*\n\n' }
|
|
708
720
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
721
|
+
// Async push-queue for zero-latency live delta streaming
|
|
722
|
+
const queue = []
|
|
723
|
+
let notify = null
|
|
712
724
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
stream: (options) => this.stream(options),
|
|
725
|
+
const pushUpdate = (text) => {
|
|
726
|
+
queue.push({ type: 'text-delta', index: 0, text })
|
|
727
|
+
if (notify) {
|
|
728
|
+
notify()
|
|
729
|
+
notify = null
|
|
719
730
|
}
|
|
720
731
|
}
|
|
721
732
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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
|
+
}
|
|
749
|
+
})
|
|
736
750
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
userPrompt,
|
|
740
|
-
messages,
|
|
741
|
-
preset: targetPreset,
|
|
742
|
-
callLlm,
|
|
743
|
-
cwd,
|
|
744
|
-
onProgress,
|
|
745
|
-
})
|
|
751
|
+
const startTime = Date.now()
|
|
752
|
+
let lastYieldTime = Date.now()
|
|
746
753
|
|
|
747
|
-
|
|
748
|
-
|
|
754
|
+
try {
|
|
755
|
+
while (!done || queue.length > 0) {
|
|
756
|
+
if (signal?.aborted) {
|
|
757
|
+
await cleanMoaWorkspaces(cwd)
|
|
758
|
+
return
|
|
759
|
+
}
|
|
749
760
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
761
|
+
while (queue.length > 0) {
|
|
762
|
+
const item = queue.shift()
|
|
763
|
+
yield item
|
|
764
|
+
lastYieldTime = Date.now()
|
|
765
|
+
}
|
|
753
766
|
|
|
754
|
-
|
|
755
|
-
while (progressChunks.length > 0) {
|
|
756
|
-
const chunk = progressChunks.shift()
|
|
757
|
-
yield { type: 'text-delta', index: 0, text: chunk }
|
|
758
|
-
emittedAny = true
|
|
759
|
-
lastYieldTime = Date.now()
|
|
760
|
-
}
|
|
767
|
+
if (done) break
|
|
761
768
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
const elapsedSec = Math.floor((Date.now() - startTime) / 1000)
|
|
769
|
-
// If 3 seconds passed without any progress message, send a live tick
|
|
770
|
-
if (!emittedAny && Date.now() - lastYieldTime >= 3000) {
|
|
771
|
-
yield { type: 'text-delta', index: 0, text: `⏳ *[${elapsedSec}с] Идёт обработка...*\n` }
|
|
772
|
-
lastYieldTime = Date.now()
|
|
773
|
-
}
|
|
774
|
-
}
|
|
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
|
+
])
|
|
775
774
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
yield { type: 'text-delta', index: 0, text:
|
|
775
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1000)
|
|
776
|
+
if (!done && Date.now() - lastYieldTime >= 3000) {
|
|
777
|
+
yield { type: 'text-delta', index: 0, text: `⏳ *[${elapsedSec}с] Идёт обработка...*\n` }
|
|
778
|
+
lastYieldTime = Date.now()
|
|
779
779
|
}
|
|
780
|
+
}
|
|
780
781
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
presetName: targetPreset?.name || 'default',
|
|
787
|
-
})
|
|
782
|
+
const result = await pipelinePromise
|
|
783
|
+
if (signal?.aborted) {
|
|
784
|
+
await cleanMoaWorkspaces(cwd)
|
|
785
|
+
return
|
|
786
|
+
}
|
|
788
787
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
yield { type: 'usage', usage: { inputTokens: moaResult?.usage?.totalTokens || 100, outputTokens: formatted.length } }
|
|
792
|
-
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
793
|
-
} catch (err) {
|
|
794
|
-
if (signal?.aborted) return
|
|
795
|
-
const errText = '\n\n⚠️ **Ошибка Mixture of Agents**: ' + (err?.message || String(err))
|
|
788
|
+
if (result?.error) {
|
|
789
|
+
const errText = '\n\n⚠️ **Ошибка Mixture of Agents**: ' + (result.error?.message || String(result.error))
|
|
796
790
|
yield { type: 'text-delta', index: 0, text: errText }
|
|
797
791
|
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
798
792
|
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
793
|
+
return
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const formatted = formatMoAResponse({
|
|
797
|
+
moaResult: result,
|
|
798
|
+
presetName: targetPreset?.name || 'default',
|
|
799
|
+
})
|
|
800
|
+
|
|
801
|
+
yield { type: 'text-delta', index: 0, text: '\n---\n\n' + formatted }
|
|
802
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: formatted } }
|
|
803
|
+
yield { type: 'usage', usage: { inputTokens: result?.usage?.totalTokens || 100, outputTokens: formatted.length } }
|
|
804
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
805
|
+
} finally {
|
|
806
|
+
if (signal?.aborted) {
|
|
807
|
+
await cleanMoaWorkspaces(cwd)
|
|
799
808
|
}
|
|
800
809
|
}
|
|
801
810
|
}
|