@goodandready/dsh-moa 0.2.42 → 0.2.43
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 +14 -9
- package/lib/file-workspace.js +40 -43
- package/lib/history.js +7 -1
- package/lib/index.js +35 -2
- package/lib/moa-budget.js +28 -19
- package/lib/moa-candidates.js +3 -0
- package/lib/moa-multi-judge.js +2 -1
- package/lib/moa-prompts.js +5 -1
- package/lib/moa-runner.js +43 -44
- package/lib/moa-stream.js +6 -4
- package/lib/moa-test-gate.js +11 -0
- package/lib/routes.js +9 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -101,6 +101,8 @@ window.__ModuleLoader__.load({
|
|
|
101
101
|
'proposers.add_btn': '+ Add Candidate Model',
|
|
102
102
|
'proposers.temp_label': 'Candidates Temperature',
|
|
103
103
|
'proposers.temp_hint': 'Recommended: 0.6 – 0.8 for creative, independent solution exploration.',
|
|
104
|
+
'proposers.temperature_gradient': 'Temperature Gradient Exploration (0.2 → 0.9 across candidates)',
|
|
105
|
+
'proposers.candidate_temp_tooltip': 'Candidate Temperature (leave empty for default/gradient)',
|
|
104
106
|
'stats.title': '📈 MoA Analytics & Telemetry',
|
|
105
107
|
'stats.desc': 'Session telemetry, historical runs, and model win-rate tracking.',
|
|
106
108
|
'stats.total_runs': 'Total Runs',
|
|
@@ -277,6 +279,8 @@ window.__ModuleLoader__.load({
|
|
|
277
279
|
'proposers.add_btn': '+ 添加候选模型',
|
|
278
280
|
'proposers.temp_label': '候选模型采样温度 (Temperature)',
|
|
279
281
|
'proposers.temp_hint': '推荐:0.6 – 0.8,鼓励创造性、发散性的独立探索。',
|
|
282
|
+
'proposers.temperature_gradient': '温度梯度探索(候选模型采样温度从 0.2 到 0.9 递增)',
|
|
283
|
+
'proposers.candidate_temp_tooltip': '候选模型采样温度(留空使用默认/梯度)',
|
|
280
284
|
'stats.title': '📈 MoA 数据分析与遥测',
|
|
281
285
|
'stats.desc': '会话遥测、历史运行追踪及模型胜率统计。',
|
|
282
286
|
'stats.total_runs': '总运行次数',
|
|
@@ -1962,7 +1966,7 @@ window.__ModuleLoader__.load({
|
|
|
1962
1966
|
onChange: (e) => updateCurrentPreset((p) => ({ ...p, temperature_gradient_enabled: e.target.checked })),
|
|
1963
1967
|
style: { accentColor: 'var(--dsw-alias-state-brand-primary, var(--dsw-alias-label-primary))', cursor: 'pointer' },
|
|
1964
1968
|
}),
|
|
1965
|
-
'Temperature Gradient Exploration (0.2 → 0.9 across candidates)'
|
|
1969
|
+
t('proposers.temperature_gradient') || 'Temperature Gradient Exploration (0.2 → 0.9 across candidates)'
|
|
1966
1970
|
)
|
|
1967
1971
|
),
|
|
1968
1972
|
React.createElement(
|
|
@@ -1990,7 +1994,7 @@ window.__ModuleLoader__.load({
|
|
|
1990
1994
|
min: '0',
|
|
1991
1995
|
max: '2',
|
|
1992
1996
|
placeholder: 'Temp',
|
|
1993
|
-
title: 'Candidate Temperature (leave empty for default/gradient)',
|
|
1997
|
+
title: t('proposers.candidate_temp_tooltip') || 'Candidate Temperature (leave empty for default/gradient)',
|
|
1994
1998
|
className: 'moa-input',
|
|
1995
1999
|
style: { width: 50, height: 32, fontSize: 12, padding: '0 4px', textAlign: 'center' },
|
|
1996
2000
|
value: (typeof ref.temperature === 'number' && ref.temperature >= 0) ? ref.temperature : '',
|
|
@@ -2612,13 +2616,14 @@ window.__ModuleLoader__.load({
|
|
|
2612
2616
|
}
|
|
2613
2617
|
render() {
|
|
2614
2618
|
if (this.state.hasError) {
|
|
2619
|
+
const t = typeof this.props?.t === 'function' ? this.props.t : (typeof makeT === 'function' ? makeT(en, null) : ((k) => k))
|
|
2615
2620
|
return React.createElement(
|
|
2616
2621
|
'div',
|
|
2617
2622
|
{
|
|
2618
2623
|
className: 'moa-alert-err',
|
|
2619
2624
|
style: { margin: '12px 0', padding: '14px', borderRadius: '8px' },
|
|
2620
2625
|
},
|
|
2621
|
-
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ MoA UI Error:'),
|
|
2626
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, t('error_boundary.title') || '⚠️ MoA UI Error:'),
|
|
2622
2627
|
React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String(this.state.error?.message || this.state.error)),
|
|
2623
2628
|
React.createElement(
|
|
2624
2629
|
'button',
|
|
@@ -2628,7 +2633,7 @@ window.__ModuleLoader__.load({
|
|
|
2628
2633
|
style: { marginTop: '10px', fontSize: '12px', padding: '4px 10px' },
|
|
2629
2634
|
onClick: () => this.setState({ hasError: false, error: null }),
|
|
2630
2635
|
},
|
|
2631
|
-
'Retry'
|
|
2636
|
+
t('error_boundary.retry') || 'Retry'
|
|
2632
2637
|
)
|
|
2633
2638
|
)
|
|
2634
2639
|
}
|
|
@@ -2675,12 +2680,12 @@ window.__ModuleLoader__.load({
|
|
|
2675
2680
|
'div',
|
|
2676
2681
|
{ className: 'moa-body' },
|
|
2677
2682
|
state.status === 'loading'
|
|
2678
|
-
? React.createElement('div', { style: { padding: '16px 0', color: 'var(--dsw-alias-label-secondary)', fontSize: 13 } }, 'Loading MoA settings…')
|
|
2683
|
+
? React.createElement('div', { style: { padding: '16px 0', color: 'var(--dsw-alias-label-secondary)', fontSize: 13 } }, state.t('status.loading') || 'Loading MoA settings…')
|
|
2679
2684
|
: state.status === 'unavailable'
|
|
2680
2685
|
? React.createElement(
|
|
2681
2686
|
'div',
|
|
2682
2687
|
{ style: { padding: '16px 0', display: 'flex', flexDirection: 'column', gap: 10 } },
|
|
2683
|
-
React.createElement('div', { className: 'moa-alert-err' }, 'MoA service temporarily unavailable'),
|
|
2688
|
+
React.createElement('div', { className: 'moa-alert-err' }, state.t('status.unavailable') || 'MoA service temporarily unavailable'),
|
|
2684
2689
|
React.createElement(
|
|
2685
2690
|
'button',
|
|
2686
2691
|
{
|
|
@@ -2689,7 +2694,7 @@ window.__ModuleLoader__.load({
|
|
|
2689
2694
|
style: { width: 'fit-content' },
|
|
2690
2695
|
onClick: state.reload,
|
|
2691
2696
|
},
|
|
2692
|
-
state.t('actions.retry')
|
|
2697
|
+
state.t('status.retry') || state.t('actions.retry')
|
|
2693
2698
|
)
|
|
2694
2699
|
)
|
|
2695
2700
|
: React.createElement(MoAEditor, state)
|
|
@@ -2738,7 +2743,7 @@ window.__ModuleLoader__.load({
|
|
|
2738
2743
|
locale: NS,
|
|
2739
2744
|
inject: () => ({ ctx }),
|
|
2740
2745
|
},
|
|
2741
|
-
(props) => React.createElement(MoAErrorBoundary,
|
|
2746
|
+
(props) => React.createElement(MoAErrorBoundary, props, React.createElement(MoACard, props))
|
|
2742
2747
|
)
|
|
2743
2748
|
} catch (err) {
|
|
2744
2749
|
console.warn('[dsh-moa] Failed to register ' + slotName + ':', err)
|
|
@@ -2761,7 +2766,7 @@ window.__ModuleLoader__.load({
|
|
|
2761
2766
|
locale: NS,
|
|
2762
2767
|
inject: () => ({ ctx }),
|
|
2763
2768
|
},
|
|
2764
|
-
(props) => React.createElement(MoAErrorBoundary,
|
|
2769
|
+
(props) => React.createElement(MoAErrorBoundary, props, React.createElement(MoACard, props))
|
|
2765
2770
|
)
|
|
2766
2771
|
} catch (err) {
|
|
2767
2772
|
console.warn('[dsh-moa] Failed to register plugins.item:', err)
|
package/lib/file-workspace.js
CHANGED
|
@@ -78,7 +78,17 @@ export function resolveCandidateFolder(baseDir, candidateTarget, options = {}) {
|
|
|
78
78
|
const sub = /^\d+$/.test(sanitizedTarget) ? `candidate-${sanitizedTarget}` : (sanitizedTarget || 'candidate-1')
|
|
79
79
|
const rawRunId = typeof options === 'string' ? options : options?.runId
|
|
80
80
|
const safeRunId = rawRunId ? String(rawRunId).trim().replace(/[^a-zA-Z0-9_-]/g, '') : null
|
|
81
|
-
const
|
|
81
|
+
const moaPath = path.resolve(baseDir, '.moa')
|
|
82
|
+
try {
|
|
83
|
+
const lst = fsSync.lstatSync(moaPath)
|
|
84
|
+
if (lst.isSymbolicLink()) {
|
|
85
|
+
const realMoa = fsSync.realpathSync(moaPath), realBase = fsSync.realpathSync(path.resolve(baseDir))
|
|
86
|
+
if (realMoa !== realBase && !realMoa.startsWith(realBase + path.sep)) fsSync.unlinkSync(moaPath)
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (err?.code !== 'ENOENT') throw err
|
|
90
|
+
}
|
|
91
|
+
const moaRoot = assertPathContained(baseDir, '.moa')
|
|
82
92
|
const runRoot = safeRunId ? assertPathContained(moaRoot, safeRunId) : moaRoot
|
|
83
93
|
return { moaRoot, runRoot, safeRunId, sub, candidateDir: assertPathContained(runRoot, sub) }
|
|
84
94
|
}
|
|
@@ -97,18 +107,16 @@ export function extractFileBlocks(text) {
|
|
|
97
107
|
}
|
|
98
108
|
}
|
|
99
109
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const inlineRegex = /```[\w-]*[\r\n]+(?:\/\/|#|<!--)\s*(?:File|Path):\s*([^\s\n\r>]+)(?:\s*-->)?[\r\n]+([\s\S]*?)```/gi
|
|
111
|
-
while ((match = inlineRegex.exec(text)) !== null) addFile(match[1].trim(), match[2])
|
|
110
|
+
const patterns = [
|
|
111
|
+
/```[\w-]*\s+(?:file|path|filepath)=["']?([^\s"'\n\r]+)["']?[\r\n]+([\s\S]*?)```/gi,
|
|
112
|
+
/(?:\/\/|#|<!--)\s*(?:File|Path):\s*([^\s\n\r>]+)(?:\s*-->)?[\r\n]+```[\w-]*[\r\n]+([\s\S]*?)```/gi,
|
|
113
|
+
/#{1,4}\s+(?:File|Path):\s*([^\s\n\r]+)[\r\n]+```[\w-]*[\r\n]+([\s\S]*?)```/gi,
|
|
114
|
+
/```[\w-]*[\r\n]+(?:\/\/|#|<!--)\s*(?:File|Path):\s*([^\s\n\r>]+)(?:\s*-->)?[\r\n]+([\s\S]*?)```/gi,
|
|
115
|
+
]
|
|
116
|
+
for (const regex of patterns) {
|
|
117
|
+
let match
|
|
118
|
+
while ((match = regex.exec(text)) !== null) addFile(match[1].trim(), match[2])
|
|
119
|
+
}
|
|
112
120
|
|
|
113
121
|
if (files.length === 0) {
|
|
114
122
|
const htmlBlockRegex = /```(?:html)?[\r\n]+([\s\S]*?)```/gi
|
|
@@ -211,15 +219,12 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
|
|
|
211
219
|
* Checks if a task prompt is an iterative refinement / modification.
|
|
212
220
|
*/
|
|
213
221
|
export function isRefinementTask(userPrompt = '', projectFiles = null) {
|
|
214
|
-
if (Array.isArray(projectFiles) && projectFiles.length === 0) return false
|
|
222
|
+
if ((Array.isArray(projectFiles) && projectFiles.length === 0) || !userPrompt?.trim()) return false
|
|
215
223
|
const p = userPrompt.toLowerCase().trim()
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const modKeywords = ['add', 'change', 'update', 'fix', 'modify', 'refactor', 'improve', 'enhance', 'adjust', 'style', 'css', 'color', 'theme', 'bug', '添加', '修改', '更新', '修复', '重构', '改进', '调整', '样式', '颜色', '主题', 'добавь', 'измени', 'поменяй', 'исправь', 'обнови', 'переделай', 'доработай', 'удали', 'темн', 'светл', 'цвет', 'кнопк', 'баг']
|
|
222
|
-
return modKeywords.some((kw) => p.includes(kw))
|
|
224
|
+
const fresh = ['from scratch', 'new project', 'new app', 'create a new', 'build a new', 'generate a new', '从头开始', '新建项目', '创建项目', '新应用', '新建应用', 'с нуля', 'новый проект', 'новое приложение', 'создай проект', 'создай приложение', 'создай игру', 'создай сервис', 'сделай проект', 'сделай приложение', 'сделай игру']
|
|
225
|
+
if (fresh.some((phrase) => p.includes(phrase))) return false
|
|
226
|
+
const mod = ['add', 'change', 'update', 'fix', 'modify', 'refactor', 'improve', 'enhance', 'adjust', 'style', 'css', 'color', 'theme', 'bug', '添加', '修改', '更新', '修复', '重构', '改进', '调整', '样式', '颜色', '主题', 'добавь', 'измени', 'поменяй', 'исправь', 'обнови', 'переделай', 'доработай', 'удали', 'темн', 'светл', 'цвет', 'кнопк', 'баг']
|
|
227
|
+
return mod.some((kw) => p.includes(kw))
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
/**
|
|
@@ -344,6 +349,9 @@ async function doPromoteCandidateWorkspace(baseDir, winningIndex, options = { ke
|
|
|
344
349
|
logger.warn(errMsg)
|
|
345
350
|
throw new Error(errMsg)
|
|
346
351
|
}
|
|
352
|
+
if (options.signal?.aborted) {
|
|
353
|
+
throw (options.signal.reason instanceof Error ? options.signal.reason : new Error('Turn aborted'))
|
|
354
|
+
}
|
|
347
355
|
|
|
348
356
|
const promoted = []
|
|
349
357
|
const baseReal = await fs.realpath(baseDir).catch(() => path.resolve(baseDir))
|
|
@@ -392,11 +400,9 @@ export async function cleanMoaWorkspaces(baseDir, options = {}) {
|
|
|
392
400
|
const safeRunId = rawRunId ? String(rawRunId).trim().replace(/[^a-zA-Z0-9_-]/g, '') : null
|
|
393
401
|
const moaRoot = path.join(baseDir, '.moa')
|
|
394
402
|
if (safeRunId) {
|
|
395
|
-
|
|
396
|
-
await bestEffort('cleanMoaWorkspaces runId', () => fs.rm(runRoot, { recursive: true, force: true }))
|
|
403
|
+
await bestEffort('cleanMoaWorkspaces runId', () => fs.rm(path.join(moaRoot, safeRunId), { recursive: true, force: true }))
|
|
397
404
|
await bestEffort('clean empty moaRoot', async () => {
|
|
398
|
-
|
|
399
|
-
if (remaining.length === 0) await fs.rmdir(moaRoot)
|
|
405
|
+
if ((await fs.readdir(moaRoot)).length === 0) await fs.rmdir(moaRoot)
|
|
400
406
|
})
|
|
401
407
|
} else {
|
|
402
408
|
await bestEffort('cleanMoaWorkspaces', () => fs.rm(moaRoot, { recursive: true, force: true }))
|
|
@@ -563,33 +569,24 @@ export function computeLineDiff(oldText = '', newText = '') {
|
|
|
563
569
|
const subN = subOld.length, subM = subNew.length
|
|
564
570
|
const middleItems = []
|
|
565
571
|
|
|
566
|
-
if (subN === 0) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
for (let i = 0; i < subN; i++) middleItems.push({ type: 'removed', line: subOld[i], oldNum: start + i + 1 })
|
|
570
|
-
} else if (subN > 2000 || subM > 2000) {
|
|
572
|
+
if (subN === 0) for (let j = 0; j < subM; j++) middleItems.push({ type: 'added', line: subNew[j], newNum: start + j + 1 })
|
|
573
|
+
else if (subM === 0) for (let i = 0; i < subN; i++) middleItems.push({ type: 'removed', line: subOld[i], oldNum: start + i + 1 })
|
|
574
|
+
else if (subN > 2000 || subM > 2000) {
|
|
571
575
|
for (let i = 0; i < subN; i++) middleItems.push({ type: 'removed', line: subOld[i], oldNum: start + i + 1 })
|
|
572
576
|
for (let j = 0; j < subM; j++) middleItems.push({ type: 'added', line: subNew[j], newNum: start + j + 1 })
|
|
573
577
|
} else {
|
|
574
578
|
const dp = Array.from({ length: subN + 1 }, () => new Uint16Array(subM + 1))
|
|
575
579
|
for (let i = 0; i < subN; i++) {
|
|
576
|
-
for (let j = 0; j < subM; j++)
|
|
577
|
-
dp[i + 1][j + 1] = subOld[i] === subNew[j] ? dp[i][j] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1])
|
|
578
|
-
}
|
|
580
|
+
for (let j = 0; j < subM; j++) dp[i + 1][j + 1] = subOld[i] === subNew[j] ? dp[i][j] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1])
|
|
579
581
|
}
|
|
580
|
-
|
|
581
|
-
let i = subN, j = subM
|
|
582
|
-
const stack = []
|
|
582
|
+
let i = subN, j = subM; const stack = []
|
|
583
583
|
while (i > 0 || j > 0) {
|
|
584
584
|
if (i > 0 && j > 0 && subOld[i - 1] === subNew[j - 1]) {
|
|
585
|
-
stack.push({ type: 'same', line: subOld[i
|
|
586
|
-
i--; j--
|
|
585
|
+
stack.push({ type: 'same', line: subOld[--i], oldNum: start + i + 1, newNum: start + j }); j--
|
|
587
586
|
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
588
|
-
stack.push({ type: 'added', line: subNew[j
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
stack.push({ type: 'removed', line: subOld[i - 1], oldNum: start + i })
|
|
592
|
-
i--
|
|
587
|
+
stack.push({ type: 'added', line: subNew[--j], newNum: start + j + 1 })
|
|
588
|
+
} else if (i > 0) {
|
|
589
|
+
stack.push({ type: 'removed', line: subOld[--i], oldNum: start + i + 1 })
|
|
593
590
|
}
|
|
594
591
|
}
|
|
595
592
|
middleItems.push(...stack.reverse())
|
package/lib/history.js
CHANGED
|
@@ -358,7 +358,13 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = getDefaultHisto
|
|
|
358
358
|
})
|
|
359
359
|
.filter(Boolean)
|
|
360
360
|
|
|
361
|
-
let total = _historyCache
|
|
361
|
+
let total = (_historyCache &&
|
|
362
|
+
_historyCache.filePath === filePath &&
|
|
363
|
+
_historyCache.mtimeMs === stat.mtimeMs &&
|
|
364
|
+
_historyCache.size === stat.size &&
|
|
365
|
+
typeof _historyCache.totalCount === 'number')
|
|
366
|
+
? _historyCache.totalCount
|
|
367
|
+
: undefined
|
|
362
368
|
if (typeof total !== 'number') {
|
|
363
369
|
total = tailLines.length < needed ? tailLines.length : countFileLinesSync(filePath, stat)
|
|
364
370
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
1
2
|
import path from 'node:path'
|
|
2
3
|
import { setLogger, logger } from './logger.js'
|
|
3
4
|
import { refreshCatalogInBackground } from './pricing.js'
|
|
@@ -19,6 +20,7 @@ import { resolvePresetForPrompt } from './moa-router.js'
|
|
|
19
20
|
import { createLiveCanvasClient } from './live-canvas.js'
|
|
20
21
|
import { registerPluginUpdater } from './updater.js'
|
|
21
22
|
import { bestEffort } from './best-effort.js'
|
|
23
|
+
import { getMoaHistory } from './history.js'
|
|
22
24
|
|
|
23
25
|
export const name = '@goodandready/dsh-moa'
|
|
24
26
|
export { collectProjectContext, formatProjectContext } from './file-workspace.js'
|
|
@@ -39,12 +41,24 @@ export function apply(ctx, config) {
|
|
|
39
41
|
refreshCatalogInBackground()
|
|
40
42
|
|
|
41
43
|
let localOverrides = null
|
|
44
|
+
let baseAtOverride = {}
|
|
42
45
|
const getSourceConfig = () => {
|
|
43
46
|
let liveCandidate = ctx.fiber?.config
|
|
44
47
|
if (!liveCandidate && ('config' in ctx)) {
|
|
45
48
|
bestEffort('ctx.config probe', () => { liveCandidate = ctx.config }, ctx.logger)
|
|
46
49
|
}
|
|
47
50
|
const liveSource = plainConfig(liveCandidate ?? config ?? {})
|
|
51
|
+
if (localOverrides) {
|
|
52
|
+
for (const key of Object.keys(localOverrides)) {
|
|
53
|
+
if (baseAtOverride[key] !== undefined && JSON.stringify(liveSource[key]) !== baseAtOverride[key]) {
|
|
54
|
+
delete localOverrides[key]
|
|
55
|
+
delete baseAtOverride[key]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (Object.keys(localOverrides).length === 0) {
|
|
59
|
+
localOverrides = null
|
|
60
|
+
}
|
|
61
|
+
}
|
|
48
62
|
return localOverrides ? { ...liveSource, ...localOverrides } : liveSource
|
|
49
63
|
}
|
|
50
64
|
const live = () => {
|
|
@@ -86,6 +100,15 @@ export function apply(ctx, config) {
|
|
|
86
100
|
} else if (settingsService && typeof settingsService.update === 'function') {
|
|
87
101
|
await settingsService.update(NS, plain)
|
|
88
102
|
}
|
|
103
|
+
let liveCandidate = ctx.fiber?.config
|
|
104
|
+
if (!liveCandidate && ('config' in ctx)) {
|
|
105
|
+
bestEffort('ctx.config probe', () => { liveCandidate = ctx.config }, ctx.logger)
|
|
106
|
+
}
|
|
107
|
+
const currentLive = plainConfig(liveCandidate ?? config ?? {})
|
|
108
|
+
baseAtOverride = baseAtOverride || {}
|
|
109
|
+
for (const key of Object.keys(plain)) {
|
|
110
|
+
baseAtOverride[key] = JSON.stringify(currentLive[key])
|
|
111
|
+
}
|
|
89
112
|
localOverrides = { ...(localOverrides || {}), ...plain }
|
|
90
113
|
return live()
|
|
91
114
|
}
|
|
@@ -176,10 +199,15 @@ export function apply(ctx, config) {
|
|
|
176
199
|
const effectiveDefault = cfg.default_preset || 'default'
|
|
177
200
|
const parsed = parseMoACommand(userText, cfg.presets || [], effectiveDefault) || { prompt: userText, presetName: effectiveDefault, isExplicit: false }
|
|
178
201
|
if (parsed.isPromote) {
|
|
202
|
+
let promoteRunId = parsed.runId
|
|
203
|
+
if (!promoteRunId) {
|
|
204
|
+
const latestRun = getMoaHistory(1, 0)?.runs?.[0]
|
|
205
|
+
if (latestRun?.id) promoteRunId = latestRun.id
|
|
206
|
+
}
|
|
179
207
|
const sessCwd = event.cwd || event.session?.cwd || (event.session?.path ? path.dirname(event.session.path) : undefined) || process.cwd()
|
|
180
208
|
const turnContext = {
|
|
181
209
|
isPromoteTurn: true,
|
|
182
|
-
runId:
|
|
210
|
+
runId: promoteRunId,
|
|
183
211
|
candidateIndex: parsed.candidateIndex,
|
|
184
212
|
cwd: sessCwd,
|
|
185
213
|
webServerPort: ctx.webServer?.port,
|
|
@@ -192,11 +220,14 @@ export function apply(ctx, config) {
|
|
|
192
220
|
const isExplicitPreset = Boolean(parsed.isExplicit || userText.includes('--preset') || userText.includes('-p '))
|
|
193
221
|
if (!isExplicitPreset && (cfg.smart_routing_enabled || cfg.presets?.some((p) => p.smart_routing_enabled))) {
|
|
194
222
|
try {
|
|
223
|
+
const activePreset = (cfg.presets || []).find((p) => p.name === (parsed.presetName || effectiveDefault)) || cfg.presets?.[0]
|
|
224
|
+
const routingModel = (activePreset?.smart_routing_model?.model && activePreset.smart_routing_model)
|
|
225
|
+
|| (cfg.smart_routing_model?.model && cfg.smart_routing_model) || activePreset?.smart_routing_model || cfg.smart_routing_model
|
|
195
226
|
const routed = await resolvePresetForPrompt({
|
|
196
227
|
prompt: parsed.prompt || userText,
|
|
197
228
|
presets: cfg.presets || [],
|
|
198
229
|
defaultPreset: parsed.presetName || effectiveDefault,
|
|
199
|
-
routingModel
|
|
230
|
+
routingModel,
|
|
200
231
|
callLlm,
|
|
201
232
|
enabled: true,
|
|
202
233
|
})
|
|
@@ -216,8 +247,10 @@ export function apply(ctx, config) {
|
|
|
216
247
|
const aggProvider = targetPreset?.aggregator?.provider || ''
|
|
217
248
|
const aggModel = targetPreset?.aggregator?.model || ''
|
|
218
249
|
const sessCwd = event.cwd || event.session?.cwd || (event.session?.path ? path.dirname(event.session.path) : undefined) || process.cwd()
|
|
250
|
+
const runId = crypto.randomUUID()
|
|
219
251
|
|
|
220
252
|
const turnContext = {
|
|
253
|
+
runId,
|
|
221
254
|
targetPreset,
|
|
222
255
|
userPrompt: parsed.prompt || userText,
|
|
223
256
|
messages: messages.slice(0, -1),
|
package/lib/moa-budget.js
CHANGED
|
@@ -74,25 +74,29 @@ export function applyBudgetGuardrails({
|
|
|
74
74
|
// Base candidate cost per slot (Round 1)
|
|
75
75
|
const { totalEstimatedUsd, slotEstimates } = estimateCandidateRunCost(references, prices, promptTokens, expectedOutputTokens)
|
|
76
76
|
|
|
77
|
-
// Estimate
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
// Estimate cost for downstream phases (Aggregator / Judge panel)
|
|
78
|
+
function calcDownstreamUsd(numCandidates) {
|
|
79
|
+
let downstream = 0
|
|
80
|
+
if (aggregator?.provider && aggregator?.model) {
|
|
81
|
+
const aggInput = promptTokens + expectedOutputTokens * numCandidates
|
|
82
|
+
const aggCost = estimateTokenCost(aggregator, { inputTokens: aggInput, outputTokens: expectedOutputTokens }, prices)
|
|
83
|
+
downstream += aggCost?.costUsd || 0
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(judgeModels) && judgeModels.length > 0) {
|
|
86
|
+
for (const jm of judgeModels) {
|
|
87
|
+
if (jm?.provider && jm?.model) {
|
|
88
|
+
const jInput = promptTokens + expectedOutputTokens * numCandidates
|
|
89
|
+
const jCost = estimateTokenCost(jm, { inputTokens: jInput, outputTokens: expectedOutputTokens }, prices)
|
|
90
|
+
downstream += jCost?.costUsd || 0
|
|
91
|
+
}
|
|
90
92
|
}
|
|
91
93
|
}
|
|
94
|
+
return downstream
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
// Multiplier if peer critique (Round 2) is enabled: each candidate runs another turn with input from all peers
|
|
95
98
|
const roundMultiplier = peerCritiqueEnabled ? 2 : 1
|
|
99
|
+
const downstreamUsd = calcDownstreamUsd(references.length)
|
|
96
100
|
const fullEstimatedUsd = Number(((totalEstimatedUsd * roundMultiplier) + downstreamUsd).toFixed(6))
|
|
97
101
|
|
|
98
102
|
if (fullEstimatedUsd <= maxBudgetUsd) {
|
|
@@ -116,38 +120,43 @@ export function applyBudgetGuardrails({
|
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
// Action is 'trim': Sort by estimated cost ascending (cheapest first)
|
|
123
|
+
// Factor downstream synthesis cost into candidate selection (#158)
|
|
119
124
|
const sorted = [...slotEstimates].sort((a, b) => a.costUsd - b.costUsd)
|
|
120
125
|
const kept = []
|
|
121
|
-
let
|
|
126
|
+
let cumulativeCandidateCost = 0
|
|
122
127
|
|
|
123
128
|
for (const item of sorted) {
|
|
124
129
|
const candidateCost = item.costUsd * roundMultiplier
|
|
125
|
-
|
|
130
|
+
const nextDownstream = calcDownstreamUsd(kept.length + 1)
|
|
131
|
+
if (cumulativeCandidateCost + candidateCost + nextDownstream <= maxBudgetUsd) {
|
|
126
132
|
kept.push(item.slot)
|
|
127
|
-
|
|
133
|
+
cumulativeCandidateCost += candidateCost
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
if (kept.length === 0) {
|
|
138
|
+
const minCandidateCost = (sorted[0]?.costUsd || 0) * roundMultiplier
|
|
139
|
+
const minTotal = minCandidateCost + calcDownstreamUsd(1)
|
|
132
140
|
return {
|
|
133
141
|
allowed: false,
|
|
134
142
|
references: [],
|
|
135
143
|
action: 'trim',
|
|
136
144
|
originalCount: references.length,
|
|
137
145
|
trimmedCount: 0,
|
|
138
|
-
estimatedCostUsd: Number(
|
|
146
|
+
estimatedCostUsd: Number(minTotal.toFixed(6)),
|
|
139
147
|
maxBudgetUsd,
|
|
140
|
-
reason: `Cannot fit any candidate model within budget ($${maxBudgetUsd.toFixed(4)}). Cheapest candidate requires $${
|
|
148
|
+
reason: `Cannot fit any candidate model within budget ($${maxBudgetUsd.toFixed(4)}). Cheapest candidate requires $${minTotal.toFixed(4)}.`,
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
|
|
152
|
+
const finalEstimatedUsd = Number((cumulativeCandidateCost + calcDownstreamUsd(kept.length)).toFixed(6))
|
|
144
153
|
return {
|
|
145
154
|
allowed: true,
|
|
146
155
|
references: kept,
|
|
147
156
|
action: 'trim',
|
|
148
157
|
originalCount: references.length,
|
|
149
158
|
trimmedCount: kept.length,
|
|
150
|
-
estimatedCostUsd:
|
|
159
|
+
estimatedCostUsd: finalEstimatedUsd,
|
|
151
160
|
maxBudgetUsd,
|
|
152
161
|
reason: `Trimmed candidate pool from ${references.length} to ${kept.length} models to stay within budget ($${maxBudgetUsd.toFixed(4)}).`,
|
|
153
162
|
}
|
package/lib/moa-candidates.js
CHANGED
|
@@ -122,9 +122,11 @@ export async function runReferencesParallel(references, messages, options = {},
|
|
|
122
122
|
})
|
|
123
123
|
reject(new Error(`Timeout after ${Math.round(timeoutMs / 1000)}s`))
|
|
124
124
|
}, timeoutMs)
|
|
125
|
+
candidateTimer?.unref?.()
|
|
125
126
|
})
|
|
126
127
|
|
|
127
128
|
const res = await Promise.race([callPromise, timeoutPromise])
|
|
129
|
+
if (candidateTimer) { clearTimeout(candidateTimer); candidateTimer = null }
|
|
128
130
|
const text = typeof res === 'string' ? res : (res?.content || res?.text || '')
|
|
129
131
|
|
|
130
132
|
const fallbackUsage = (typeof res === 'object' && res?.usage) ? res.usage : {
|
|
@@ -275,6 +277,7 @@ export async function runReferencesParallel(references, messages, options = {},
|
|
|
275
277
|
}
|
|
276
278
|
}
|
|
277
279
|
} finally {
|
|
280
|
+
if (candidateTimer) clearTimeout(candidateTimer)
|
|
278
281
|
notifyFinished()
|
|
279
282
|
}
|
|
280
283
|
}
|
package/lib/moa-multi-judge.js
CHANGED
|
@@ -371,7 +371,8 @@ CONSENSUS_REASONING: [one concise sentence explaining your pick]
|
|
|
371
371
|
|
|
372
372
|
const winningCand = activeCandidates.find((c) => c.index === winner) || activeCandidates[0]
|
|
373
373
|
const winnerVotes = votes[winner] || 0
|
|
374
|
-
const
|
|
374
|
+
const winnerLabel = blind ? '' : ` (${winningCand.label})`
|
|
375
|
+
const consensusReport = `Multi-judge consensus (${strategy}): Candidate ${winner}${winnerLabel} won with ${winnerVotes}/${validResults.length} votes.`
|
|
375
376
|
|
|
376
377
|
if (typeof onProgress === 'function') {
|
|
377
378
|
onProgress(`⚖️ *${consensusReport}*\n`)
|
package/lib/moa-prompts.js
CHANGED
|
@@ -302,6 +302,10 @@ export function buildSynthesisPrompt(userPrompt, referenceOutputs = [], judgeCri
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
const isBlind = Boolean(options.blindEvaluation)
|
|
305
|
+
let consensusReportText = options.consensusReport
|
|
306
|
+
if (isBlind && consensusReportText) {
|
|
307
|
+
consensusReportText = consensusReportText.replace(/\s*\([^)]+\)/g, '')
|
|
308
|
+
}
|
|
305
309
|
|
|
306
310
|
const joined = referenceOutputs
|
|
307
311
|
.map((r, i) => {
|
|
@@ -344,7 +348,7 @@ ${LANGUAGE_MIRRORING_DIRECTIVE}
|
|
|
344
348
|
${referenceOutputs.some((r) => r.testResult) ? `\n${TEST_GATE_DIRECTIVE}\n` : ''}
|
|
345
349
|
${referenceOutputs.some((r) => r.syntaxWarning) ? `\n${SYNTAX_CORRECTION_DIRECTIVE}\n` : ''}
|
|
346
350
|
${options.priorTurnBaseline ? `\n### 🔄 Prior Turn Context Baseline:\n${options.priorTurnBaseline}\n` : ''}
|
|
347
|
-
${
|
|
351
|
+
${consensusReportText ? `\n### ⚖️ Multi-Judge Consensus Advisory:\n${consensusReportText}\n` : ''}
|
|
348
352
|
${options.compositeMergeDirective ? `\n${options.compositeMergeDirective}\n` : ''}
|
|
349
353
|
Instructions:
|
|
350
354
|
Your response MUST be structured into three clear parts:
|
package/lib/moa-runner.js
CHANGED
|
@@ -38,7 +38,8 @@ export async function runMoAPipeline({
|
|
|
38
38
|
webServerPort = null, timeMachineEngine = null, timeMachineUrl = null,
|
|
39
39
|
force = isTestEnvironment() ? true : false, checkpoint = true, checkpointFn = null, runId: customRunId = null,
|
|
40
40
|
}) {
|
|
41
|
-
const
|
|
41
|
+
const runId = (customRunId && String(customRunId).trim()) || crypto.randomUUID()
|
|
42
|
+
const promoteOpts = { webServerPort, timeMachineEngine, timeMachineUrl, force: Boolean(force), checkpoint, checkpointFn, signal }
|
|
42
43
|
if (signal?.aborted) return { error: new Error('Turn aborted') }
|
|
43
44
|
const startTime = Date.now()
|
|
44
45
|
// 1. Resolve configurations
|
|
@@ -58,7 +59,7 @@ export async function runMoAPipeline({
|
|
|
58
59
|
const candidateRetries = 1, refTimeoutSec = typeof preset?.reference_timeout_sec === 'number' ? preset.reference_timeout_sec : 60
|
|
59
60
|
const aggTimeoutSec = typeof preset?.aggregator_timeout_sec === 'number' ? preset.aggregator_timeout_sec : 180
|
|
60
61
|
const isBlindEvaluation = Boolean(preset?.blind_evaluation), isPeerCritiqueEnabled = Boolean(preset?.peer_critique_enabled)
|
|
61
|
-
const allowCandidateOverride = Boolean(preset?.allow_candidate_override)
|
|
62
|
+
const allowCandidateOverride = Boolean(preset?.allow_candidate_override)
|
|
62
63
|
// Feature 6: Budget Guardrail check
|
|
63
64
|
const budgetCheck = applyBudgetGuardrails({
|
|
64
65
|
references: referenceModels,
|
|
@@ -275,12 +276,18 @@ export async function runMoAPipeline({
|
|
|
275
276
|
return { error: new Error('Turn aborted') }
|
|
276
277
|
}
|
|
277
278
|
const single = successfulRefs[0]
|
|
278
|
-
let promotedFiles = []
|
|
279
|
+
let promotedFiles = [], promoteErr = null
|
|
279
280
|
if (single.files?.length > 0) {
|
|
280
|
-
|
|
281
|
-
|
|
281
|
+
try {
|
|
282
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, single.index, { ...promoteOpts, runId, keepMoa: allowCandidateOverride })
|
|
283
|
+
} catch (e) { promoteErr = e }
|
|
284
|
+
} else if (!allowCandidateOverride) {
|
|
282
285
|
await cleanMoaWorkspaces(cwd, { runId })
|
|
283
286
|
}
|
|
287
|
+
if (signal?.aborted || promoteErr?.message?.includes('aborted') || promoteErr?.name === 'AbortError') {
|
|
288
|
+
if (!allowCandidateOverride) await cleanMoaWorkspaces(cwd, { runId })
|
|
289
|
+
return { error: new Error('Turn aborted') }
|
|
290
|
+
}
|
|
284
291
|
|
|
285
292
|
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs, { winningIndex: single.index })
|
|
286
293
|
const totalTokens = single.usage?.totalTokens || 0
|
|
@@ -289,44 +296,27 @@ export async function runMoAPipeline({
|
|
|
289
296
|
|
|
290
297
|
try {
|
|
291
298
|
await recordMoaRunAsync({
|
|
292
|
-
cwd,
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
isFastMode: true,
|
|
297
|
-
candidates: candidatesForHistory(referenceOutputs),
|
|
298
|
-
aggregator: null,
|
|
299
|
-
winnerIndex: single.index,
|
|
300
|
-
winnerModel: single.label,
|
|
301
|
-
promotedFiles,
|
|
302
|
-
totalTokens,
|
|
303
|
-
totalCostUsd,
|
|
304
|
-
durationMs,
|
|
299
|
+
id: runId, cwd, prompt: userPrompt, preset: preset?.name || 'default',
|
|
300
|
+
isRefinement, isFastMode: true, candidates: candidatesForHistory(referenceOutputs),
|
|
301
|
+
aggregator: null, winnerIndex: single.index, winnerModel: single.label,
|
|
302
|
+
promotedFiles, totalTokens, totalCostUsd, durationMs,
|
|
305
303
|
}, historyFilePath || undefined)
|
|
306
304
|
} catch (histErr) {
|
|
307
305
|
logger.warn('[dsh-moa] Failed to record fast synthesis run in history:', histErr?.message || histErr)
|
|
308
306
|
}
|
|
309
307
|
|
|
310
308
|
return {
|
|
311
|
-
kind: 'synthesis',
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
presetName: preset?.name || 'default',
|
|
316
|
-
isRefinement,
|
|
317
|
-
isFastMode: true,
|
|
318
|
-
winningIndex: single.index,
|
|
319
|
-
winnerModel: single.label,
|
|
320
|
-
promotedFiles,
|
|
309
|
+
kind: 'synthesis', content: single.text, aggregator: 'Fast Mode (Direct)',
|
|
310
|
+
references: referenceOutputs, presetName: preset?.name || 'default',
|
|
311
|
+
isRefinement, isFastMode: true, winningIndex: single.index,
|
|
312
|
+
winnerModel: single.label, promotedFiles, runId,
|
|
321
313
|
...(livePreview ? { liveCanvas: livePreview } : {}),
|
|
322
314
|
usage: {
|
|
323
|
-
totalTokens,
|
|
324
|
-
totalCostUsd,
|
|
315
|
+
totalTokens, totalCostUsd,
|
|
325
316
|
candidates: referenceOutputs.map((r) => ({ label: r.label, usage: r.usage, costUsd: r.costUsd })),
|
|
326
317
|
aggregator: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 },
|
|
327
318
|
},
|
|
328
|
-
skippedFiles: collectedCtx?.skippedFiles || 0,
|
|
329
|
-
skippedList: collectedCtx?.skippedList || [],
|
|
319
|
+
skippedFiles: collectedCtx?.skippedFiles || 0, skippedList: collectedCtx?.skippedList || [],
|
|
330
320
|
durationMs,
|
|
331
321
|
}
|
|
332
322
|
}
|
|
@@ -502,16 +492,26 @@ export async function runMoAPipeline({
|
|
|
502
492
|
let promotedFiles = []
|
|
503
493
|
|
|
504
494
|
const keepWorkspaces = allowCandidateOverride
|
|
505
|
-
if (
|
|
506
|
-
await
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
495
|
+
if (signal?.aborted) {
|
|
496
|
+
if (!keepWorkspaces) await cleanMoaWorkspaces(cwd, { runId })
|
|
497
|
+
return { error: new Error('Turn aborted') }
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
if (synthesizedFiles.length > 0) {
|
|
501
|
+
await writeCandidateWorkspace(cwd, 'curator-synthesis', synthesizedFiles, { runId })
|
|
502
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, 'curator-synthesis', { ...promoteOpts, runId, keepMoa: keepWorkspaces })
|
|
503
|
+
} else if (referenceOutputs[winningIndex - 1]?.files?.length > 0) {
|
|
504
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, winningIndex, { ...promoteOpts, runId, keepMoa: keepWorkspaces })
|
|
505
|
+
} else if (successfulRefs[0]?.files?.length > 0) {
|
|
506
|
+
const fallbackIdx = successfulRefs[0].index
|
|
507
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, fallbackIdx, { ...promoteOpts, runId, keepMoa: keepWorkspaces })
|
|
508
|
+
} else if (!keepWorkspaces) {
|
|
509
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
510
|
+
}
|
|
511
|
+
} catch (promoteErr) {
|
|
512
|
+
if (!keepWorkspaces) await cleanMoaWorkspaces(cwd, { runId })
|
|
513
|
+
if (signal?.aborted || promoteErr?.message === 'Turn aborted') return { error: new Error('Turn aborted') }
|
|
514
|
+
throw promoteErr
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs, { winningIndex, synthesizedFiles })
|
|
@@ -582,8 +582,7 @@ export async function runMoAPipeline({
|
|
|
582
582
|
consensus: multiJudgeResult,
|
|
583
583
|
...(livePreview ? { liveCanvas: livePreview } : {}),
|
|
584
584
|
usage: usageSummary,
|
|
585
|
-
skippedFiles: collectedCtx?.skippedFiles || 0,
|
|
586
|
-
skippedList: collectedCtx?.skippedList || [],
|
|
585
|
+
skippedFiles: collectedCtx?.skippedFiles || 0, skippedList: collectedCtx?.skippedList || [],
|
|
587
586
|
durationMs,
|
|
588
587
|
}
|
|
589
588
|
}
|
package/lib/moa-stream.js
CHANGED
|
@@ -4,7 +4,7 @@ import crypto from 'node:crypto'
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { cleanMoaWorkspaces, promoteCandidateWorkspace } from './file-workspace.js'
|
|
7
|
-
import { isTestEnvironment } from './history.js'
|
|
7
|
+
import { isTestEnvironment, getMoaHistory } from './history.js'
|
|
8
8
|
import { formatMoAResponse } from './moa-parser.js'
|
|
9
9
|
import { runMoAPipeline } from './moa-runner.js'
|
|
10
10
|
|
|
@@ -57,6 +57,7 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
|
|
|
57
57
|
timeMachineEngine,
|
|
58
58
|
force,
|
|
59
59
|
signal: combinedSignal,
|
|
60
|
+
runId,
|
|
60
61
|
})
|
|
61
62
|
.catch((err) => ({ error: err }))
|
|
62
63
|
.finally(() => {
|
|
@@ -152,10 +153,11 @@ export async function* streamPromoteTurn({ runId, candidateIndex, cwd, webServer
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
try {
|
|
155
|
-
|
|
156
|
+
const effectiveRunId = runId || getMoaHistory(1, 0).runs[0]?.id || null
|
|
157
|
+
yield { type: 'text-delta', index: 0, text: `🔄 *Promoting candidate ${candidateIndex}${effectiveRunId ? ` for run \`${effectiveRunId}\`` : ''}...*\n\n` }
|
|
156
158
|
|
|
157
159
|
const promotedFiles = await promoteCandidateWorkspace(cwd, candidateIndex, {
|
|
158
|
-
runId,
|
|
160
|
+
runId: effectiveRunId,
|
|
159
161
|
keepMoa: true,
|
|
160
162
|
port: webServerPort,
|
|
161
163
|
timeMachineEngine,
|
|
@@ -163,7 +165,7 @@ export async function* streamPromoteTurn({ runId, candidateIndex, cwd, webServer
|
|
|
163
165
|
})
|
|
164
166
|
|
|
165
167
|
if (!promotedFiles || promotedFiles.length === 0) {
|
|
166
|
-
const noFilesText = `⚠️ **MoA Promote Notice**: No candidate files found for candidate ${candidateIndex}${
|
|
168
|
+
const noFilesText = `⚠️ **MoA Promote Notice**: No candidate files found for candidate ${candidateIndex}${effectiveRunId ? ` in run \`${effectiveRunId}\`` : ''}. Workspace may have expired or run does not exist.`
|
|
167
169
|
yield { type: 'text-delta', index: 0, text: noFilesText }
|
|
168
170
|
yield { type: 'block-end', index: 0, block: { type: 'text', text: noFilesText } }
|
|
169
171
|
yield { type: 'finish', reason: { kind: 'stop' } }
|
package/lib/moa-test-gate.js
CHANGED
|
@@ -115,12 +115,23 @@ export async function runCandidateTestGate({
|
|
|
115
115
|
stderr = customRes?.stderr || ''
|
|
116
116
|
} else {
|
|
117
117
|
const nodePath = path.join(cwd, 'node_modules')
|
|
118
|
+
const allowedReads = [
|
|
119
|
+
executionDir,
|
|
120
|
+
cwd,
|
|
121
|
+
...(candidateDir && candidateDir !== executionDir ? [candidateDir] : []),
|
|
122
|
+
...(process.platform === 'win32'
|
|
123
|
+
? [process.env.SYSTEMROOT || 'C:\\Windows', path.dirname(process.execPath)]
|
|
124
|
+
: ['/usr', '/lib', '/lib64', '/etc', path.resolve(path.dirname(process.execPath), '..'), process.env.HOME ? path.join(process.env.HOME, '.npmrc') : null]),
|
|
125
|
+
].filter(Boolean)
|
|
126
|
+
const permOpts = `--permission ${allowedReads.map((p) => `--allow-fs-read=${p}`).join(' ')} --allow-fs-write=${executionDir} --allow-child-process`
|
|
127
|
+
|
|
118
128
|
const env = getScrubbedEnv({
|
|
119
129
|
MOA_CANDIDATE_INDEX: String(candidateIndex),
|
|
120
130
|
MOA_CANDIDATE_DIR: candidateDir,
|
|
121
131
|
MOA_BASE_DIR: cwd,
|
|
122
132
|
...(runId ? { MOA_RUN_ID: String(runId) } : {}),
|
|
123
133
|
NODE_PATH: process.env.NODE_PATH ? `${nodePath}:${process.env.NODE_PATH}` : nodePath,
|
|
134
|
+
NODE_OPTIONS: process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ${permOpts}` : permOpts,
|
|
124
135
|
})
|
|
125
136
|
|
|
126
137
|
const runPromise = new Promise((resolve) => {
|
package/lib/routes.js
CHANGED
|
@@ -356,9 +356,11 @@ ctx.effect(() => {
|
|
|
356
356
|
}
|
|
357
357
|
try {
|
|
358
358
|
const u = new URL(req.url, 'http://localhost')
|
|
359
|
-
const
|
|
359
|
+
const rawRunId = u.searchParams.get('runId')
|
|
360
|
+
const latestRun = (!rawRunId || rawRunId === 'latest' || rawRunId === 'active') ? getMoaHistory(1, 0).runs[0] : null
|
|
361
|
+
const runId = (rawRunId && rawRunId !== 'latest' && rawRunId !== 'active') ? rawRunId : latestRun?.id
|
|
360
362
|
const run = runId ? getMoaRunById(runId) : null
|
|
361
|
-
const cwd = run?.cwd || u.searchParams.get('cwd') || process.cwd()
|
|
363
|
+
const cwd = run?.cwd || latestRun?.cwd || u.searchParams.get('cwd') || process.cwd()
|
|
362
364
|
const sanitizeTarget = (val, def) => {
|
|
363
365
|
const clean = String(val || '').trim().replace(/[^a-zA-Z0-9_-]/g, '')
|
|
364
366
|
return clean || def
|
|
@@ -511,11 +513,14 @@ ctx.effect(() => {
|
|
|
511
513
|
const raw = await readBody(req)
|
|
512
514
|
const body = JSON.parse(raw || '{}')
|
|
513
515
|
const prompt = body?.prompt || ''
|
|
516
|
+
const activePreset = (cfg.presets || []).find((p) => p.name === (cfg.default_preset || 'default')) || cfg.presets?.[0]
|
|
517
|
+
const routingModel = (activePreset?.smart_routing_model?.model && activePreset.smart_routing_model)
|
|
518
|
+
|| (cfg.smart_routing_model?.model && cfg.smart_routing_model) || activePreset?.smart_routing_model || cfg.smart_routing_model
|
|
514
519
|
const result = await resolvePresetForPrompt({
|
|
515
520
|
prompt,
|
|
516
521
|
presets: cfg.presets || [],
|
|
517
522
|
defaultPreset: cfg.default_preset || 'default',
|
|
518
|
-
routingModel
|
|
523
|
+
routingModel,
|
|
519
524
|
callLlm,
|
|
520
525
|
enabled: cfg.enabled !== false,
|
|
521
526
|
})
|
|
@@ -575,6 +580,7 @@ ctx.effect(() => {
|
|
|
575
580
|
timeMachineEngine: timeMachineEngine || ctx.reflect?.get?.('timeMachineEngine', false),
|
|
576
581
|
timeMachineUrl: payload.timeMachineUrl, force: payload.force,
|
|
577
582
|
checkpoint: payload.checkpoint, checkpointFn: payload.checkpointFn,
|
|
583
|
+
runId: payload.runId,
|
|
578
584
|
})
|
|
579
585
|
|
|
580
586
|
if (result?.skippedFiles > 0) {
|