@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/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 localConfig = plainConfig(config ?? {})
40
- const getConfig = () => localConfig
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 = getConfig() ?? {}
46
+ const raw = getSourceConfig() ?? {}
44
47
  const validated = plainConfig(Config(plainConfig(raw)))
45
- return plainConfig(validated) ?? localConfig
48
+ return plainConfig(validated) ?? raw
46
49
  } catch {
47
- return localConfig
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
- localConfig = { ...localConfig, ...plain }
76
- try {
77
- if (settingsService && typeof settingsService.replace === 'function') {
78
- await settingsService.replace(NS, plain)
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
- return localConfig
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 parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: cfg.default_preset || 'default' }
170
- const isExplicitPreset = userText.includes('--preset') || userText.includes('-p ')
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 || cfg.default_preset || 'default',
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) || cfg.presets?.[0]
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
- const promptTokens = Math.max(50, Math.round(promptLength / 4))
53
- const { totalEstimatedUsd, slotEstimates } = estimateCandidateRunCost(references, prices, promptTokens)
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 (totalEstimatedUsd <= maxBudgetUsd) {
98
+ if (fullEstimatedUsd <= maxBudgetUsd) {
56
99
  return {
57
100
  allowed: true,
58
101
  references,
59
102
  action: 'pass',
60
- estimatedCostUsd: totalEstimatedUsd,
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: totalEstimatedUsd,
112
+ estimatedCostUsd: fullEstimatedUsd,
70
113
  maxBudgetUsd,
71
- reason: `Estimated MoA run cost ($${totalEstimatedUsd.toFixed(4)}) exceeds configured max budget ($${maxBudgetUsd.toFixed(4)}).`,
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
- if (cumulative + item.costUsd <= maxBudgetUsd || kept.length === 0) {
124
+ const candidateCost = item.costUsd * roundMultiplier
125
+ if (cumulative + candidateCost <= maxBudgetUsd) {
82
126
  kept.push(item.slot)
83
- cumulative += item.costUsd
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: 'default',
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: 'default',
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
- testCommand = null,
41
- execFn = null,
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 = crypto.randomUUID()
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 = referenceModels[cand.index - 1] || { provider: cand.provider, model: cand.model }
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
  }