@goodandready/dsh-moa 0.2.37 → 0.2.39
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 +1 -1
- package/README.ru.md +1 -1
- package/README.zh.md +1 -1
- package/lib/file-workspace.js +195 -143
- package/lib/index.js +26 -19
- package/lib/moa-budget.js +65 -8
- package/lib/moa-parser.js +16 -3
- package/lib/moa-runner.js +30 -58
- package/lib/moa-stream.js +5 -3
- package/lib/moa-test-gate.js +94 -41
- package/lib/routes.js +18 -11
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -36,15 +36,18 @@ export function apply(ctx, config) {
|
|
|
36
36
|
if (ctx.logger) setLogger(ctx.logger)
|
|
37
37
|
refreshCatalogInBackground()
|
|
38
38
|
|
|
39
|
-
let
|
|
40
|
-
const
|
|
39
|
+
let localOverrides = null
|
|
40
|
+
const getSourceConfig = () => {
|
|
41
|
+
const liveSource = plainConfig(ctx.config ?? config ?? {})
|
|
42
|
+
return localOverrides ? { ...liveSource, ...localOverrides } : liveSource
|
|
43
|
+
}
|
|
41
44
|
const live = () => {
|
|
42
45
|
try {
|
|
43
|
-
const raw =
|
|
46
|
+
const raw = getSourceConfig() ?? {}
|
|
44
47
|
const validated = plainConfig(Config(plainConfig(raw)))
|
|
45
|
-
return plainConfig(validated) ??
|
|
48
|
+
return plainConfig(validated) ?? raw
|
|
46
49
|
} catch {
|
|
47
|
-
return
|
|
50
|
+
return getSourceConfig()
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
|
|
@@ -72,17 +75,13 @@ export function apply(ctx, config) {
|
|
|
72
75
|
|
|
73
76
|
const saveConfig = async (newFields) => {
|
|
74
77
|
const plain = plainConfig(newFields)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
} else if (settingsService && typeof settingsService.update === 'function') {
|
|
80
|
-
await settingsService.update(NS, plain)
|
|
81
|
-
}
|
|
82
|
-
} catch (err) {
|
|
83
|
-
logger.warn('[dsh-moa] Settings persistence warning:', err?.message || err)
|
|
78
|
+
if (settingsService && typeof settingsService.replace === 'function') {
|
|
79
|
+
await settingsService.replace(NS, plain)
|
|
80
|
+
} else if (settingsService && typeof settingsService.update === 'function') {
|
|
81
|
+
await settingsService.update(NS, plain)
|
|
84
82
|
}
|
|
85
|
-
|
|
83
|
+
localOverrides = { ...(localOverrides || {}), ...plain }
|
|
84
|
+
return live()
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
const callLlm = createLlmCaller(ctx)
|
|
@@ -110,6 +109,8 @@ export function apply(ctx, config) {
|
|
|
110
109
|
saveConfig,
|
|
111
110
|
Config,
|
|
112
111
|
plainConfig,
|
|
112
|
+
webServerPort: ctx.webServer?.port,
|
|
113
|
+
timeMachineEngine: ctx.get?.('timeMachineEngine') || ctx.timeMachineEngine,
|
|
113
114
|
})
|
|
114
115
|
|
|
115
116
|
// Session turn interceptor: route turn to real aggregator model & stream MoA synthesis
|
|
@@ -166,14 +167,15 @@ export function apply(ctx, config) {
|
|
|
166
167
|
if (userText.startsWith('/moa')) {
|
|
167
168
|
const cfg = live()
|
|
168
169
|
if (cfg.enabled !== false) {
|
|
169
|
-
const
|
|
170
|
-
const
|
|
170
|
+
const effectiveDefault = cfg.default_preset || 'default'
|
|
171
|
+
const parsed = parseMoACommand(userText, cfg.presets || [], effectiveDefault) || { prompt: userText, presetName: effectiveDefault, isExplicit: false }
|
|
172
|
+
const isExplicitPreset = Boolean(parsed.isExplicit || userText.includes('--preset') || userText.includes('-p '))
|
|
171
173
|
if (!isExplicitPreset && (cfg.smart_routing_enabled || cfg.presets?.some((p) => p.smart_routing_enabled))) {
|
|
172
174
|
try {
|
|
173
175
|
const routed = await resolvePresetForPrompt({
|
|
174
176
|
prompt: parsed.prompt || userText,
|
|
175
177
|
presets: cfg.presets || [],
|
|
176
|
-
defaultPreset: parsed.presetName ||
|
|
178
|
+
defaultPreset: parsed.presetName || effectiveDefault,
|
|
177
179
|
routingModel: cfg.smart_routing_model,
|
|
178
180
|
callLlm,
|
|
179
181
|
enabled: true,
|
|
@@ -185,7 +187,12 @@ export function apply(ctx, config) {
|
|
|
185
187
|
// fallback silently
|
|
186
188
|
}
|
|
187
189
|
}
|
|
188
|
-
const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName
|
|
190
|
+
const targetPreset = (cfg.presets || []).find((p) => p.name === (parsed.presetName || effectiveDefault))
|
|
191
|
+
|| (!isExplicitPreset ? (cfg.presets || []).find((p) => p.name === effectiveDefault) || cfg.presets?.[0] : null)
|
|
192
|
+
if (!targetPreset) {
|
|
193
|
+
logger.warn(`[dsh-moa] Unknown explicit preset "${parsed.presetName}" in /moa command.`)
|
|
194
|
+
return next()
|
|
195
|
+
}
|
|
189
196
|
const aggProvider = targetPreset?.aggregator?.provider || ''
|
|
190
197
|
const aggModel = targetPreset?.aggregator?.model || ''
|
|
191
198
|
const sessCwd = event.cwd || event.session?.cwd || (event.session?.path ? path.dirname(event.session.path) : undefined) || process.cwd()
|
package/lib/moa-budget.js
CHANGED
|
@@ -31,6 +31,7 @@ export function estimateCandidateRunCost(slots = [], prices = {}, promptTokens =
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Applies budget guardrails before initiating parallel candidate execution.
|
|
34
|
+
* Evaluates candidate runs, peer critique (Round 2) and downstream synthesis phases.
|
|
34
35
|
*/
|
|
35
36
|
export function applyBudgetGuardrails({
|
|
36
37
|
references = [],
|
|
@@ -39,6 +40,11 @@ export function applyBudgetGuardrails({
|
|
|
39
40
|
action = 'trim', // 'trim' | 'abort'
|
|
40
41
|
prices = {},
|
|
41
42
|
promptLength = 1000,
|
|
43
|
+
messages = [],
|
|
44
|
+
maxTokens = 2048,
|
|
45
|
+
peerCritiqueEnabled = false,
|
|
46
|
+
aggregator = null,
|
|
47
|
+
judgeModels = [],
|
|
42
48
|
}) {
|
|
43
49
|
if (!enabled || typeof maxBudgetUsd !== 'number' || maxBudgetUsd <= 0 || references.length === 0) {
|
|
44
50
|
return {
|
|
@@ -49,15 +55,52 @@ export function applyBudgetGuardrails({
|
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
// Calculate full input tokens from userPrompt length + messages context
|
|
59
|
+
let contextChars = Number(promptLength) || 1000
|
|
60
|
+
if (Array.isArray(messages) && messages.length > 0) {
|
|
61
|
+
for (const m of messages) {
|
|
62
|
+
if (typeof m?.content === 'string') {
|
|
63
|
+
contextChars += m.content.length
|
|
64
|
+
} else if (Array.isArray(m?.content)) {
|
|
65
|
+
for (const part of m.content) {
|
|
66
|
+
if (part?.text) contextChars += part.text.length
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const promptTokens = Math.max(50, Math.round(contextChars / 4))
|
|
72
|
+
const expectedOutputTokens = typeof maxTokens === 'number' && maxTokens > 0 ? maxTokens : 2048
|
|
73
|
+
|
|
74
|
+
// Base candidate cost per slot (Round 1)
|
|
75
|
+
const { totalEstimatedUsd, slotEstimates } = estimateCandidateRunCost(references, prices, promptTokens, expectedOutputTokens)
|
|
76
|
+
|
|
77
|
+
// Estimate fixed cost for downstream phases (Aggregator / Judge panel)
|
|
78
|
+
let downstreamUsd = 0
|
|
79
|
+
if (aggregator?.provider && aggregator?.model) {
|
|
80
|
+
const aggInput = promptTokens + expectedOutputTokens * references.length
|
|
81
|
+
const aggCost = estimateTokenCost(aggregator, { inputTokens: aggInput, outputTokens: expectedOutputTokens }, prices)
|
|
82
|
+
downstreamUsd += aggCost?.costUsd || 0
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(judgeModels) && judgeModels.length > 0) {
|
|
85
|
+
for (const jm of judgeModels) {
|
|
86
|
+
if (jm?.provider && jm?.model) {
|
|
87
|
+
const jInput = promptTokens + expectedOutputTokens * references.length
|
|
88
|
+
const jCost = estimateTokenCost(jm, { inputTokens: jInput, outputTokens: expectedOutputTokens }, prices)
|
|
89
|
+
downstreamUsd += jCost?.costUsd || 0
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Multiplier if peer critique (Round 2) is enabled: each candidate runs another turn with input from all peers
|
|
95
|
+
const roundMultiplier = peerCritiqueEnabled ? 2 : 1
|
|
96
|
+
const fullEstimatedUsd = Number(((totalEstimatedUsd * roundMultiplier) + downstreamUsd).toFixed(6))
|
|
54
97
|
|
|
55
|
-
if (
|
|
98
|
+
if (fullEstimatedUsd <= maxBudgetUsd) {
|
|
56
99
|
return {
|
|
57
100
|
allowed: true,
|
|
58
101
|
references,
|
|
59
102
|
action: 'pass',
|
|
60
|
-
estimatedCostUsd:
|
|
103
|
+
estimatedCostUsd: fullEstimatedUsd,
|
|
61
104
|
}
|
|
62
105
|
}
|
|
63
106
|
|
|
@@ -66,9 +109,9 @@ export function applyBudgetGuardrails({
|
|
|
66
109
|
allowed: false,
|
|
67
110
|
references: [],
|
|
68
111
|
action: 'abort',
|
|
69
|
-
estimatedCostUsd:
|
|
112
|
+
estimatedCostUsd: fullEstimatedUsd,
|
|
70
113
|
maxBudgetUsd,
|
|
71
|
-
reason: `Estimated MoA run cost ($${
|
|
114
|
+
reason: `Estimated MoA run cost ($${fullEstimatedUsd.toFixed(4)}) exceeds configured max budget ($${maxBudgetUsd.toFixed(4)}).`,
|
|
72
115
|
}
|
|
73
116
|
}
|
|
74
117
|
|
|
@@ -78,9 +121,23 @@ export function applyBudgetGuardrails({
|
|
|
78
121
|
let cumulative = 0
|
|
79
122
|
|
|
80
123
|
for (const item of sorted) {
|
|
81
|
-
|
|
124
|
+
const candidateCost = item.costUsd * roundMultiplier
|
|
125
|
+
if (cumulative + candidateCost <= maxBudgetUsd) {
|
|
82
126
|
kept.push(item.slot)
|
|
83
|
-
cumulative +=
|
|
127
|
+
cumulative += candidateCost
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (kept.length === 0) {
|
|
132
|
+
return {
|
|
133
|
+
allowed: false,
|
|
134
|
+
references: [],
|
|
135
|
+
action: 'trim',
|
|
136
|
+
originalCount: references.length,
|
|
137
|
+
trimmedCount: 0,
|
|
138
|
+
estimatedCostUsd: Number((sorted[0]?.costUsd * roundMultiplier || 0).toFixed(6)),
|
|
139
|
+
maxBudgetUsd,
|
|
140
|
+
reason: `Cannot fit any candidate model within budget ($${maxBudgetUsd.toFixed(4)}). Cheapest candidate requires $${((sorted[0]?.costUsd || 0) * roundMultiplier).toFixed(4)}.`,
|
|
84
141
|
}
|
|
85
142
|
}
|
|
86
143
|
|
package/lib/moa-parser.js
CHANGED
|
@@ -40,7 +40,7 @@ export function parseRecommendedAssembler(judgeText, defaultIndex = 1, candidate
|
|
|
40
40
|
/**
|
|
41
41
|
* Parses a `/moa [preset] <prompt>` command invocation.
|
|
42
42
|
*/
|
|
43
|
-
export function parseMoACommand(text, presets = []) {
|
|
43
|
+
export function parseMoACommand(text, presets = [], defaultPreset = undefined) {
|
|
44
44
|
if (typeof text !== 'string' || !text.startsWith('/moa')) {
|
|
45
45
|
return null
|
|
46
46
|
}
|
|
@@ -59,8 +59,9 @@ export function parseMoACommand(text, presets = []) {
|
|
|
59
59
|
const remainder = text.slice(4).trim()
|
|
60
60
|
if (!remainder) {
|
|
61
61
|
return {
|
|
62
|
-
presetName:
|
|
62
|
+
presetName: defaultPreset,
|
|
63
63
|
prompt: '',
|
|
64
|
+
isExplicit: false,
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -69,6 +70,16 @@ export function parseMoACommand(text, presets = []) {
|
|
|
69
70
|
return {
|
|
70
71
|
presetName: matchPreset[1],
|
|
71
72
|
prompt: matchPreset[2] || '',
|
|
73
|
+
isExplicit: true,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const matchShortPreset = /^-p\s+([a-zA-Z0-9_-]+)\s*(.*)/s.exec(remainder)
|
|
78
|
+
if (matchShortPreset) {
|
|
79
|
+
return {
|
|
80
|
+
presetName: matchShortPreset[1],
|
|
81
|
+
prompt: matchShortPreset[2] || '',
|
|
82
|
+
isExplicit: true,
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
|
|
@@ -80,13 +91,15 @@ export function parseMoACommand(text, presets = []) {
|
|
|
80
91
|
return {
|
|
81
92
|
presetName: matched.name,
|
|
82
93
|
prompt: parts.slice(1).join(' ').trim(),
|
|
94
|
+
isExplicit: true,
|
|
83
95
|
}
|
|
84
96
|
}
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
return {
|
|
88
|
-
presetName:
|
|
100
|
+
presetName: defaultPreset,
|
|
89
101
|
prompt: remainder,
|
|
102
|
+
isExplicit: false,
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
|
package/lib/moa-runner.js
CHANGED
|
@@ -12,7 +12,6 @@ import { executeMultiJudgePanel, buildCompositeBlockDirectives } from './moa-mul
|
|
|
12
12
|
import { applyBudgetGuardrails } from './moa-budget.js'
|
|
13
13
|
import { extractPriorTurnBaseline, pruneMultiTurnMessages } from './moa-context.js'
|
|
14
14
|
import { generateMoABenchmarkReport } from './moa-report.js'
|
|
15
|
-
|
|
16
15
|
// Re-exports for consumers & backward compatibility
|
|
17
16
|
export { slotLabel, cleanAdvisoryMessages, isBroadPromptRequiringQuestions, buildQuestionSynthesisPrompt, buildCuratorSynthesisPrompt, buildSynthesisPrompt, ANTIPATTERNS_RUBRIC } from './moa-prompts.js'
|
|
18
17
|
export { parseWinnerIndex, parseRecommendedAssembler, parseMoACommand, stripOrSummarizeCode, formatMoAResponse } from './moa-parser.js'
|
|
@@ -20,7 +19,6 @@ export { estimateTokenCost, summarizeMoAUsage } from './pricing.js'
|
|
|
20
19
|
export { REFERENCE_SYSTEM_PROMPT, callWithTransientRetry, runReferencesParallel } from './moa-candidates.js'
|
|
21
20
|
export { executeTestGateForCandidates, runCandidateTestGate } from './moa-test-gate.js'
|
|
22
21
|
export { streamMoATurn } from './moa-stream.js'
|
|
23
|
-
|
|
24
22
|
/**
|
|
25
23
|
* Executes the full Mixture of Agents pipeline.
|
|
26
24
|
*/
|
|
@@ -36,36 +34,20 @@ export async function runMoAPipeline({
|
|
|
36
34
|
liveCanvas = null,
|
|
37
35
|
onStreamDelta = null,
|
|
38
36
|
signal = null,
|
|
39
|
-
testGateEnabled = null,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
webServerPort = null,
|
|
43
|
-
timeMachineEngine = null,
|
|
44
|
-
timeMachineUrl = null,
|
|
45
|
-
force = isTestEnvironment() ? true : false,
|
|
46
|
-
checkpoint = true,
|
|
47
|
-
checkpointFn = null,
|
|
37
|
+
testGateEnabled = null, testCommand = null, execFn = null,
|
|
38
|
+
webServerPort = null, timeMachineEngine = null, timeMachineUrl = null,
|
|
39
|
+
force = isTestEnvironment() ? true : false, checkpoint = true, checkpointFn = null, runId: customRunId = null,
|
|
48
40
|
}) {
|
|
49
|
-
const promoteOpts = {
|
|
50
|
-
webServerPort,
|
|
51
|
-
timeMachineEngine,
|
|
52
|
-
timeMachineUrl,
|
|
53
|
-
force: Boolean(force),
|
|
54
|
-
checkpoint,
|
|
55
|
-
checkpointFn,
|
|
56
|
-
}
|
|
41
|
+
const promoteOpts = { webServerPort, timeMachineEngine, timeMachineUrl, force: Boolean(force), checkpoint, checkpointFn }
|
|
57
42
|
if (signal?.aborted) return { error: new Error('Turn aborted') }
|
|
58
43
|
const startTime = Date.now()
|
|
59
|
-
|
|
60
44
|
// 1. Resolve configurations
|
|
61
45
|
const referenceModels = Array.isArray(preset?.reference_models) && preset.reference_models.length > 0
|
|
62
46
|
? preset.reference_models : [{ provider: '', model: '' }]
|
|
63
47
|
const primaryJudge = preset?.aggregator?.provider && preset?.aggregator?.model
|
|
64
48
|
? preset.aggregator : { provider: '', model: '' }
|
|
65
|
-
|
|
66
49
|
const fallbackJudges = Array.isArray(preset?.aggregator_fallbacks) ? preset.aggregator_fallbacks : []
|
|
67
50
|
const judgesChain = [primaryJudge, ...fallbackJudges]
|
|
68
|
-
|
|
69
51
|
const refTemp = typeof preset?.reference_temperature === 'number' ? preset.reference_temperature : 0.6
|
|
70
52
|
const aggTemp = typeof preset?.aggregator_temperature === 'number' ? preset.aggregator_temperature : 0.4
|
|
71
53
|
const maxTokens = typeof preset?.max_tokens === 'number' ? preset.max_tokens : 4096
|
|
@@ -76,8 +58,7 @@ export async function runMoAPipeline({
|
|
|
76
58
|
const candidateRetries = 1, refTimeoutSec = typeof preset?.reference_timeout_sec === 'number' ? preset.reference_timeout_sec : 60
|
|
77
59
|
const aggTimeoutSec = typeof preset?.aggregator_timeout_sec === 'number' ? preset.aggregator_timeout_sec : 180
|
|
78
60
|
const isBlindEvaluation = Boolean(preset?.blind_evaluation), isPeerCritiqueEnabled = Boolean(preset?.peer_critique_enabled)
|
|
79
|
-
const allowCandidateOverride = Boolean(preset?.allow_candidate_override), runId =
|
|
80
|
-
|
|
61
|
+
const allowCandidateOverride = Boolean(preset?.allow_candidate_override), runId = customRunId || null
|
|
81
62
|
// Feature 6: Budget Guardrail check
|
|
82
63
|
const budgetCheck = applyBudgetGuardrails({
|
|
83
64
|
references: referenceModels,
|
|
@@ -86,6 +67,11 @@ export async function runMoAPipeline({
|
|
|
86
67
|
action: preset?.budget_action || 'trim',
|
|
87
68
|
prices,
|
|
88
69
|
promptLength: userPrompt?.length || 1000,
|
|
70
|
+
messages,
|
|
71
|
+
maxTokens,
|
|
72
|
+
peerCritiqueEnabled: isPeerCritiqueEnabled,
|
|
73
|
+
aggregator: primaryJudge,
|
|
74
|
+
judgeModels: preset?.multi_judge_enabled ? preset?.judge_models : [],
|
|
89
75
|
})
|
|
90
76
|
if (!budgetCheck.allowed) {
|
|
91
77
|
return {
|
|
@@ -109,7 +95,6 @@ export async function runMoAPipeline({
|
|
|
109
95
|
if (budgetCheck.action === 'trim' && typeof onProgress === 'function') {
|
|
110
96
|
onProgress(`✂️ *${budgetCheck.reason}*\n`)
|
|
111
97
|
}
|
|
112
|
-
|
|
113
98
|
// 2. Collect project context for refinement tasks
|
|
114
99
|
const collectedCtx = await collectProjectContext(cwd, 16000)
|
|
115
100
|
const isRefinement = Boolean(collectedCtx?.files?.length > 0 && isRefinementTask(userPrompt, collectedCtx.files))
|
|
@@ -128,19 +113,15 @@ export async function runMoAPipeline({
|
|
|
128
113
|
skippedList: collectedCtx.skippedList,
|
|
129
114
|
})
|
|
130
115
|
}
|
|
131
|
-
|
|
132
116
|
// 3. Build prompts & Feature 8 multi-turn context
|
|
133
117
|
const priorTurnBaseline = preset?.multi_turn_enabled !== false ? extractPriorTurnBaseline(messages) : null
|
|
134
118
|
const prunedHistory = preset?.multi_turn_enabled !== false ? pruneMultiTurnMessages(messages) : messages
|
|
135
|
-
|
|
136
119
|
const candidateSystemPrompt = projectContext
|
|
137
120
|
? `${REFERENCE_SYSTEM_PROMPT}\n\n### Current Project Files & Context:\n${projectContext}`
|
|
138
121
|
: REFERENCE_SYSTEM_PROMPT
|
|
139
|
-
|
|
140
122
|
const askClarifyingQuestions = preset?.ask_clarifying_questions !== false
|
|
141
123
|
const needsQuestions = askClarifyingQuestions && isBroadPromptRequiringQuestions(userPrompt, messages)
|
|
142
124
|
const enrichedMessages = [...prunedHistory, { role: 'user', content: userPrompt }]
|
|
143
|
-
|
|
144
125
|
// 4. Parallel fan-out to candidate models with temperature gradient & local fallback
|
|
145
126
|
const referenceOutputs = await runReferencesParallel(
|
|
146
127
|
effectiveReferences,
|
|
@@ -162,12 +143,10 @@ export async function runMoAPipeline({
|
|
|
162
143
|
callLlm,
|
|
163
144
|
onProgress
|
|
164
145
|
)
|
|
165
|
-
|
|
166
146
|
if (signal?.aborted) {
|
|
167
|
-
await cleanMoaWorkspaces(cwd)
|
|
147
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
168
148
|
return { error: new Error('Turn aborted') }
|
|
169
149
|
}
|
|
170
|
-
|
|
171
150
|
// Fail fast if all candidates failed
|
|
172
151
|
const successfulRefs = referenceOutputs.filter((r) => r.ok)
|
|
173
152
|
if (successfulRefs.length === 0) {
|
|
@@ -194,7 +173,6 @@ export async function runMoAPipeline({
|
|
|
194
173
|
durationMs: Date.now() - startTime,
|
|
195
174
|
}
|
|
196
175
|
}
|
|
197
|
-
|
|
198
176
|
// 5. Extract file blocks & write candidate workspaces
|
|
199
177
|
for (let i = 0; i < referenceOutputs.length; i++) {
|
|
200
178
|
const ref = referenceOutputs[i]
|
|
@@ -203,24 +181,21 @@ export async function runMoAPipeline({
|
|
|
203
181
|
ref.files = files
|
|
204
182
|
if (files.length > 0) {
|
|
205
183
|
ref.syntaxWarning = (verifyFileSyntax(files) || []).map((w) => `${w.file}: ${w.error}`).join('; ')
|
|
206
|
-
await writeCandidateWorkspace(cwd, i + 1, files)
|
|
184
|
+
await writeCandidateWorkspace(cwd, i + 1, files, { runId })
|
|
207
185
|
}
|
|
208
186
|
}
|
|
209
|
-
|
|
210
187
|
if (signal?.aborted) {
|
|
211
|
-
await cleanMoaWorkspaces(cwd)
|
|
188
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
212
189
|
return { error: new Error('Turn aborted') }
|
|
213
190
|
}
|
|
214
|
-
|
|
215
191
|
// 5b. Test Execution Gate
|
|
216
192
|
await executeTestGateForCandidates({
|
|
217
193
|
cwd,
|
|
218
194
|
referenceOutputs,
|
|
219
195
|
preset,
|
|
220
|
-
options: { testGateEnabled, testCommand, execFn },
|
|
196
|
+
options: { testGateEnabled, testCommand, execFn, runId },
|
|
221
197
|
onProgress,
|
|
222
198
|
})
|
|
223
|
-
|
|
224
199
|
// 6. Questionnaire synthesis branch
|
|
225
200
|
if (needsQuestions) {
|
|
226
201
|
if (typeof onProgress === 'function') {
|
|
@@ -248,12 +223,10 @@ export async function runMoAPipeline({
|
|
|
248
223
|
logger.warn('[dsh-moa] Questionnaire synthesis failed, proceeding with fallback questions:', err)
|
|
249
224
|
questionsContent = `### Clarification of Requirements: "${userPrompt}"\n\n1. **Architecture & Scope**: Single-file deliverable or multi-module project structure?\n2. **Design & Style**: Minimalist, dark mode, or clean neutral theme?\n3. **Functional Priorities**: Core MVP or comprehensive extended implementation?\n\n*Reply with your preferences (e.g. "1, 2") or proceed with defaults.*`
|
|
250
225
|
}
|
|
251
|
-
|
|
252
|
-
await cleanMoaWorkspaces(cwd)
|
|
226
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
253
227
|
const totalTokens = referenceOutputs.reduce((acc, r) => acc + (r.usage?.totalTokens || 0), 0) + qUsage.totalTokens
|
|
254
228
|
const totalCostUsd = Number((referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0) + qUsage.costUsd).toFixed(5))
|
|
255
229
|
const durationMs = Date.now() - startTime
|
|
256
|
-
|
|
257
230
|
try {
|
|
258
231
|
await recordMoaRunAsync({
|
|
259
232
|
prompt: userPrompt,
|
|
@@ -271,7 +244,6 @@ export async function runMoAPipeline({
|
|
|
271
244
|
} catch (histErr) {
|
|
272
245
|
logger.warn('[dsh-moa] Failed to record questionnaire run in history:', histErr?.message || histErr)
|
|
273
246
|
}
|
|
274
|
-
|
|
275
247
|
return {
|
|
276
248
|
kind: 'questions',
|
|
277
249
|
content: questionsContent,
|
|
@@ -298,15 +270,15 @@ export async function runMoAPipeline({
|
|
|
298
270
|
// 7. Fast Mode (1 candidate, bypass judge)
|
|
299
271
|
if (isFastMode) {
|
|
300
272
|
if (signal?.aborted) {
|
|
301
|
-
await cleanMoaWorkspaces(cwd)
|
|
273
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
302
274
|
return { error: new Error('Turn aborted') }
|
|
303
275
|
}
|
|
304
276
|
const single = successfulRefs[0]
|
|
305
277
|
let promotedFiles = []
|
|
306
278
|
if (single.files?.length > 0) {
|
|
307
|
-
promotedFiles = await promoteCandidateWorkspace(cwd, single.index, promoteOpts)
|
|
279
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, single.index, { ...promoteOpts, runId })
|
|
308
280
|
} else {
|
|
309
|
-
await cleanMoaWorkspaces(cwd)
|
|
281
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
310
282
|
}
|
|
311
283
|
|
|
312
284
|
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs)
|
|
@@ -358,7 +330,7 @@ export async function runMoAPipeline({
|
|
|
358
330
|
}
|
|
359
331
|
|
|
360
332
|
if (signal?.aborted) {
|
|
361
|
-
await cleanMoaWorkspaces(cwd)
|
|
333
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
362
334
|
return { error: new Error('Turn aborted') }
|
|
363
335
|
}
|
|
364
336
|
|
|
@@ -377,7 +349,7 @@ export async function runMoAPipeline({
|
|
|
377
349
|
}))
|
|
378
350
|
const r2Prompt = buildPeerCritiquePrompt(userPrompt, cand.text, opponents, isBlindEvaluation)
|
|
379
351
|
try {
|
|
380
|
-
const slot =
|
|
352
|
+
const slot = cand.slot || { provider: cand.provider, model: cand.model }
|
|
381
353
|
const res = await callWithTransientRetry(callLlm, {
|
|
382
354
|
provider: slot.provider,
|
|
383
355
|
model: slot.model,
|
|
@@ -393,7 +365,7 @@ export async function runMoAPipeline({
|
|
|
393
365
|
if (r2Files.length > 0) {
|
|
394
366
|
cand.files = r2Files
|
|
395
367
|
cand.syntaxWarning = (verifyFileSyntax(r2Files) || []).map((w) => `${w.file}: ${w.error}`).join('; ')
|
|
396
|
-
await writeCandidateWorkspace(cwd, cand.index, r2Files)
|
|
368
|
+
await writeCandidateWorkspace(cwd, cand.index, r2Files, { runId })
|
|
397
369
|
}
|
|
398
370
|
if (typeof res === 'object' && res?.usage) {
|
|
399
371
|
cand.usage.inputTokens = (cand.usage.inputTokens || 0) + (res.usage.inputTokens || 0)
|
|
@@ -411,7 +383,7 @@ export async function runMoAPipeline({
|
|
|
411
383
|
cwd,
|
|
412
384
|
referenceOutputs: successfulRefs,
|
|
413
385
|
preset,
|
|
414
|
-
options: { testGateEnabled, testCommand, execFn },
|
|
386
|
+
options: { testGateEnabled, testCommand, execFn, runId },
|
|
415
387
|
onProgress,
|
|
416
388
|
})
|
|
417
389
|
}
|
|
@@ -419,7 +391,7 @@ export async function runMoAPipeline({
|
|
|
419
391
|
// 7c. Feature 2: Multi-Judge Panel & Consensus Voting
|
|
420
392
|
let multiJudgeResult = null
|
|
421
393
|
if (signal?.aborted) {
|
|
422
|
-
await cleanMoaWorkspaces(cwd)
|
|
394
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
423
395
|
return { error: new Error('Turn aborted') }
|
|
424
396
|
}
|
|
425
397
|
|
|
@@ -508,7 +480,7 @@ export async function runMoAPipeline({
|
|
|
508
480
|
}
|
|
509
481
|
|
|
510
482
|
if (signal?.aborted) {
|
|
511
|
-
await cleanMoaWorkspaces(cwd)
|
|
483
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
512
484
|
return { error: new Error('Turn aborted') }
|
|
513
485
|
}
|
|
514
486
|
|
|
@@ -527,22 +499,22 @@ export async function runMoAPipeline({
|
|
|
527
499
|
|
|
528
500
|
const keepWorkspaces = allowCandidateOverride
|
|
529
501
|
if (synthesizedFiles.length > 0) {
|
|
530
|
-
await writeCandidateWorkspace(cwd, 'curator-synthesis', synthesizedFiles)
|
|
531
|
-
promotedFiles = await promoteCandidateWorkspace(cwd, 'curator-synthesis', { ...promoteOpts, keepMoa: keepWorkspaces })
|
|
502
|
+
await writeCandidateWorkspace(cwd, 'curator-synthesis', synthesizedFiles, { runId })
|
|
503
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, 'curator-synthesis', { ...promoteOpts, runId, keepMoa: keepWorkspaces })
|
|
532
504
|
} else if (referenceOutputs[winningIndex - 1]?.files?.length > 0) {
|
|
533
|
-
promotedFiles = await promoteCandidateWorkspace(cwd, winningIndex, { ...promoteOpts, keepMoa: keepWorkspaces })
|
|
505
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, winningIndex, { ...promoteOpts, runId, keepMoa: keepWorkspaces })
|
|
534
506
|
} else if (successfulRefs[0]?.files?.length > 0) {
|
|
535
507
|
const fallbackIdx = successfulRefs[0].index
|
|
536
|
-
promotedFiles = await promoteCandidateWorkspace(cwd, fallbackIdx, { ...promoteOpts, keepMoa: keepWorkspaces })
|
|
508
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, fallbackIdx, { ...promoteOpts, runId, keepMoa: keepWorkspaces })
|
|
537
509
|
} else if (!keepWorkspaces) {
|
|
538
|
-
await cleanMoaWorkspaces(cwd)
|
|
510
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
539
511
|
}
|
|
540
512
|
|
|
541
513
|
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs)
|
|
542
514
|
const durationMs = Date.now() - startTime
|
|
543
515
|
const usageSummary = summarizeMoAUsage(referenceOutputs, aggUsage)
|
|
544
516
|
const winningRef = referenceOutputs[winningIndex - 1]
|
|
545
|
-
const winnerModel = winningRef?.label || slotLabel(referenceModels[0])
|
|
517
|
+
const winnerModel = winningRef?.label || slotLabel(effectiveReferences[0] || referenceModels[0])
|
|
546
518
|
const finalAggLabel = slotLabel(chosenJudge)
|
|
547
519
|
|
|
548
520
|
// Feature 9: Benchmark & Post-Mortem PR report
|
package/lib/moa-stream.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
1
2
|
/**
|
|
2
3
|
* Zero-latency Live Streaming Generator for MoA Turns
|
|
3
4
|
*/
|
|
@@ -11,6 +12,7 @@ import { runMoAPipeline } from './moa-runner.js'
|
|
|
11
12
|
*/
|
|
12
13
|
export async function* streamMoATurn({ targetPreset, userPrompt, messages, callLlm, cwd, prices, historyFilePath, liveCanvas, webServerPort, timeMachineEngine, force }, options = {}) {
|
|
13
14
|
const signal = options?.signal
|
|
15
|
+
const runId = options?.runId || null
|
|
14
16
|
if (signal?.aborted) return
|
|
15
17
|
|
|
16
18
|
const internalAbortCtrl = new AbortController()
|
|
@@ -69,7 +71,7 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
|
|
|
69
71
|
|
|
70
72
|
while (!done || queue.length > 0) {
|
|
71
73
|
if (combinedSignal.aborted) {
|
|
72
|
-
await cleanMoaWorkspaces(cwd)
|
|
74
|
+
await cleanMoaWorkspaces(cwd, runId ? { runId } : {})
|
|
73
75
|
return
|
|
74
76
|
}
|
|
75
77
|
|
|
@@ -96,7 +98,7 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
|
|
|
96
98
|
|
|
97
99
|
const result = await pipelinePromise
|
|
98
100
|
if (combinedSignal.aborted) {
|
|
99
|
-
await cleanMoaWorkspaces(cwd)
|
|
101
|
+
await cleanMoaWorkspaces(cwd, runId ? { runId } : {})
|
|
100
102
|
return
|
|
101
103
|
}
|
|
102
104
|
|
|
@@ -126,7 +128,7 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
|
|
|
126
128
|
} finally {
|
|
127
129
|
internalAbortCtrl.abort()
|
|
128
130
|
if (!done) {
|
|
129
|
-
await cleanMoaWorkspaces(cwd).catch(() => {})
|
|
131
|
+
await cleanMoaWorkspaces(cwd, runId ? { runId } : {}).catch(() => {})
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
}
|