@goodandready/dsh-moa 0.2.45 → 0.2.46
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/file-workspace.js +12 -12
- package/lib/index.js +12 -1
- package/lib/moa-runner.js +22 -18
- package/lib/moa-test-gate.js +29 -8
- package/lib/routes.js +47 -79
- package/package.json +1 -1
package/lib/file-workspace.js
CHANGED
|
@@ -302,6 +302,7 @@ export async function createPrePromotionCheckpoint(baseDir, candidateTarget, opt
|
|
|
302
302
|
headers: { 'Content-Type': 'application/json' },
|
|
303
303
|
body: JSON.stringify({ label, cwd: baseDir }),
|
|
304
304
|
signal: AbortSignal.timeout(options.timeoutMs ?? 1500),
|
|
305
|
+
redirect: 'error',
|
|
305
306
|
})
|
|
306
307
|
if (res.ok) {
|
|
307
308
|
const data = await res.json()
|
|
@@ -563,15 +564,15 @@ export function computeLineDiff(oldText = '', newText = '') {
|
|
|
563
564
|
const subOld = oldLines.slice(start, oldEnd + 1)
|
|
564
565
|
const subNew = newLines.slice(start, newEnd + 1)
|
|
565
566
|
const subN = subOld.length, subM = subNew.length
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
else if (subM === 0)
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
)
|
|
567
|
+
let middleItems
|
|
568
|
+
if (subN === 0) {
|
|
569
|
+
middleItems = subNew.map((line, j) => ({ type: 'added', line, newNum: start + j + 1 }))
|
|
570
|
+
} else if (subM === 0) {
|
|
571
|
+
middleItems = subOld.map((line, i) => ({ type: 'removed', line, oldNum: start + i + 1 }))
|
|
572
|
+
} else if (subN > 2000 || subM > 2000) {
|
|
573
|
+
middleItems = new Array(subN + subM)
|
|
574
|
+
for (let i = 0; i < subN; i++) middleItems[i] = { type: 'removed', line: subOld[i], oldNum: start + i + 1 }
|
|
575
|
+
for (let j = 0; j < subM; j++) middleItems[subN + j] = { type: 'added', line: subNew[j], newNum: start + j + 1 }
|
|
575
576
|
} else {
|
|
576
577
|
const dp = Array.from({ length: subN + 1 }, () => new Uint16Array(subM + 1))
|
|
577
578
|
for (let i = 0; i < subN; i++) {
|
|
@@ -587,8 +588,7 @@ export function computeLineDiff(oldText = '', newText = '') {
|
|
|
587
588
|
stack.push({ type: 'removed', line: subOld[--i], oldNum: start + i + 1 })
|
|
588
589
|
}
|
|
589
590
|
}
|
|
590
|
-
middleItems
|
|
591
|
+
middleItems = stack.reverse()
|
|
591
592
|
}
|
|
592
|
-
|
|
593
|
-
return [...prefixItems, ...middleItems, ...suffixItems]
|
|
593
|
+
return prefixItems.concat(middleItems, suffixItems)
|
|
594
594
|
}
|
package/lib/index.js
CHANGED
|
@@ -95,10 +95,21 @@ export function apply(ctx, config) {
|
|
|
95
95
|
|
|
96
96
|
const saveConfig = async (newFields) => {
|
|
97
97
|
const plain = plainConfig(newFields)
|
|
98
|
+
let persisted = false
|
|
98
99
|
if (settingsService && typeof settingsService.replace === 'function') {
|
|
99
100
|
await settingsService.replace(NS, plain)
|
|
101
|
+
persisted = true
|
|
100
102
|
} else if (settingsService && typeof settingsService.update === 'function') {
|
|
101
103
|
await settingsService.update(NS, plain)
|
|
104
|
+
persisted = true
|
|
105
|
+
}
|
|
106
|
+
if (config && typeof config === 'object') {
|
|
107
|
+
for (const [k, v] of Object.entries(plain)) {
|
|
108
|
+
if (config[k] && typeof config[k] === 'object' && typeof config[k].get === 'function') continue
|
|
109
|
+
const desc = Object.getOwnPropertyDescriptor(config, k)
|
|
110
|
+
if (desc && desc.get && !desc.set) continue
|
|
111
|
+
try { config[k] = v } catch {}
|
|
112
|
+
}
|
|
102
113
|
}
|
|
103
114
|
let liveCandidate = ctx.fiber?.config
|
|
104
115
|
if (!liveCandidate && ('config' in ctx)) {
|
|
@@ -110,7 +121,7 @@ export function apply(ctx, config) {
|
|
|
110
121
|
baseAtOverride[key] = JSON.stringify(currentLive[key])
|
|
111
122
|
}
|
|
112
123
|
localOverrides = { ...(localOverrides || {}), ...plain }
|
|
113
|
-
return live()
|
|
124
|
+
return { ok: true, persisted, ...live() }
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
const callLlm = createLlmCaller(ctx)
|
package/lib/moa-runner.js
CHANGED
|
@@ -27,13 +27,8 @@ export async function runMoAPipeline({
|
|
|
27
27
|
messages = [],
|
|
28
28
|
preset,
|
|
29
29
|
callLlm,
|
|
30
|
-
cwd = process.cwd(),
|
|
31
|
-
|
|
32
|
-
onProgress = null,
|
|
33
|
-
historyFilePath = null,
|
|
34
|
-
liveCanvas = null,
|
|
35
|
-
onStreamDelta = null,
|
|
36
|
-
signal = null,
|
|
30
|
+
cwd = process.cwd(), prices = {}, onProgress = null,
|
|
31
|
+
historyFilePath = null, liveCanvas = null, onStreamDelta = null, signal = null,
|
|
37
32
|
testGateEnabled = null, testCommand = null, execFn = null,
|
|
38
33
|
webServerPort = null, timeMachineEngine = null, timeMachineUrl = null,
|
|
39
34
|
force = isTestEnvironment() ? true : false, checkpoint = true, checkpointFn = null, runId: customRunId = null,
|
|
@@ -316,11 +311,7 @@ export async function runMoAPipeline({
|
|
|
316
311
|
const currentSpent = referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0)
|
|
317
312
|
const r2Next = estimateRound2Cost(successfulRefs, prices, maxTokens)
|
|
318
313
|
const downNext = estimateDownstreamCost(primaryJudge, preset?.judge_models, successfulRefs.length, prices, maxTokens)
|
|
319
|
-
const phaseCheck = checkPhaseBudget({
|
|
320
|
-
phaseName: 'Consilium Round 2', currentSpentUsd: currentSpent,
|
|
321
|
-
nextPhaseCostUsd: r2Next + downNext, maxBudgetUsd: preset.max_budget_usd,
|
|
322
|
-
action: preset.budget_action || 'trim', enabled: true,
|
|
323
|
-
})
|
|
314
|
+
const phaseCheck = checkPhaseBudget({ phaseName: 'Consilium Round 2', currentSpentUsd: currentSpent, nextPhaseCostUsd: r2Next + downNext, maxBudgetUsd: preset.max_budget_usd, action: preset.budget_action || 'trim', enabled: true })
|
|
324
315
|
if (!phaseCheck.allowed) {
|
|
325
316
|
if (phaseCheck.action === 'abort') {
|
|
326
317
|
await cleanMoaWorkspaces(cwd, { runId })
|
|
@@ -416,12 +407,8 @@ export async function runMoAPipeline({
|
|
|
416
407
|
if (preset?.budget_guard_enabled && preset?.max_budget_usd > 0 && !isFastMode) {
|
|
417
408
|
const currentSpent = referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0) + (multiJudgeResult?.totalCostUsd || 0)
|
|
418
409
|
const downNext = estimateDownstreamCost(primaryJudge, [], successfulRefs.length, prices, maxTokens)
|
|
419
|
-
const phaseCheck = checkPhaseBudget({
|
|
420
|
-
|
|
421
|
-
nextPhaseCostUsd: downNext, maxBudgetUsd: preset.max_budget_usd,
|
|
422
|
-
action: preset.budget_action || 'trim', enabled: true,
|
|
423
|
-
})
|
|
424
|
-
if (!phaseCheck.allowed) {
|
|
410
|
+
const phaseCheck = checkPhaseBudget({ phaseName: 'Judge Synthesis', currentSpentUsd: currentSpent, nextPhaseCostUsd: downNext, maxBudgetUsd: preset.max_budget_usd, action: preset.budget_action || 'trim', enabled: true })
|
|
411
|
+
if (!phaseCheck.allowed && fallbackJudges.length === 0) {
|
|
425
412
|
await cleanMoaWorkspaces(cwd, { runId })
|
|
426
413
|
return createBudgetAbortPayload({ reason: phaseCheck.reason, primaryJudge, referenceOutputs, currentSpentUsd: currentSpent, preset, isRefinement, isFastMode, collectedCtx, startTime })
|
|
427
414
|
}
|
|
@@ -443,6 +430,18 @@ export async function runMoAPipeline({
|
|
|
443
430
|
const currentJudge = judgesChain[jIdx]
|
|
444
431
|
const currentLabel = slotLabel(currentJudge)
|
|
445
432
|
|
|
433
|
+
if (preset?.budget_guard_enabled && preset?.max_budget_usd > 0 && !isFastMode) {
|
|
434
|
+
const currentSpent = referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0) + (multiJudgeResult?.totalCostUsd || 0)
|
|
435
|
+
const judgeCost = estimateDownstreamCost(currentJudge, [], successfulRefs.length, prices, maxTokens)
|
|
436
|
+
const phaseCheck = checkPhaseBudget({ phaseName: `Judge Synthesis (${currentLabel})`, currentSpentUsd: currentSpent, nextPhaseCostUsd: judgeCost, maxBudgetUsd: preset.max_budget_usd, action: preset.budget_action || 'trim', enabled: true })
|
|
437
|
+
if (!phaseCheck.allowed) {
|
|
438
|
+
lastJudgeError = new Error(phaseCheck.reason)
|
|
439
|
+
logger.warn(`[dsh-moa] Judge ${currentLabel} budget check failed: ${phaseCheck.reason}`)
|
|
440
|
+
if (typeof onProgress === 'function') onProgress(`⚠️ *Judge ${currentLabel} skipped: exceeds budget limit.*\n`)
|
|
441
|
+
continue
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
446
445
|
if (typeof onProgress === 'function') {
|
|
447
446
|
const modeTitle = isCuratorSynthesis ? 'Curator' : 'Judge'
|
|
448
447
|
const fallbackBadge = jIdx > 0 ? ` (Fallback #${jIdx})` : ''
|
|
@@ -487,6 +486,11 @@ export async function runMoAPipeline({
|
|
|
487
486
|
}
|
|
488
487
|
|
|
489
488
|
if (!judgeSuccess) {
|
|
489
|
+
if (preset?.budget_guard_enabled && preset?.max_budget_usd > 0 && !isFastMode && (lastJudgeError?.message?.includes('exceeds max budget') || lastJudgeError?.message?.includes('Budget Guardrail') || preset?.budget_action === 'abort')) {
|
|
490
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
491
|
+
const currentSpent = referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0) + (multiJudgeResult?.totalCostUsd || 0)
|
|
492
|
+
return createBudgetAbortPayload({ reason: lastJudgeError?.message || 'Budget exceeded before judge synthesis', primaryJudge, referenceOutputs, currentSpentUsd: currentSpent, preset, isRefinement, isFastMode, collectedCtx, startTime })
|
|
493
|
+
}
|
|
490
494
|
const firstErr = lastJudgeError ? (lastJudgeError.message || String(lastJudgeError)) : 'unknown error'
|
|
491
495
|
synthesizedText = `⚠️ *[Aggregator error: ${firstErr}. Fallback candidate outputs:]*\n\n` +
|
|
492
496
|
successfulRefs.map((r, i) => `### Candidate ${i + 1} (${r.label})\n${r.text}`).join('\n\n')
|
package/lib/moa-test-gate.js
CHANGED
|
@@ -43,8 +43,10 @@ export function resolveCandidateDir(baseDir, candidateIndex, options = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const UNIX_ROOT_PREFIX = /^\/(?:home|root|etc|var|tmp|opt|mnt|srv|media|proc|sys|dev|usr|bin|lib|lib64|run|sbin|boot|Users|Applications|Library|System|Volumes|private)(?:\/|$)/
|
|
47
|
+
|
|
46
48
|
/**
|
|
47
|
-
* Validates that an executed script or command does not reference files outside allowed roots (#161).
|
|
49
|
+
* Validates that an executed script or command does not reference files outside allowed roots (#161, #225).
|
|
48
50
|
*/
|
|
49
51
|
function scanPathTokensForLeak(text, allowedRoots, executionDir) {
|
|
50
52
|
if (!text || typeof text !== 'string') return null
|
|
@@ -56,7 +58,10 @@ function scanPathTokensForLeak(text, allowedRoots, executionDir) {
|
|
|
56
58
|
if (/^\/(usr\/(bin|lib|include)|bin|lib|lib64|etc|dev)(\/|$)/.test(raw) && !/\.(txt|json|env|npmrc|key|ssh|md)/i.test(raw)) {
|
|
57
59
|
continue
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
const isAbs = path.isAbsolute(raw)
|
|
62
|
+
const isUp = raw.startsWith('..')
|
|
63
|
+
if (!isAbs && !isUp) continue
|
|
64
|
+
if (isAbs && process.platform !== 'win32' && !UNIX_ROOT_PREFIX.test(raw)) continue
|
|
60
65
|
const resolved = path.resolve(executionDir, raw)
|
|
61
66
|
const allowed = allowedRoots.some((r) => r && (resolved === r || resolved.startsWith(r + path.sep)))
|
|
62
67
|
if (!allowed) {
|
|
@@ -173,10 +178,10 @@ export async function runCandidateTestGate({
|
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
// 2. Scan script files referenced by command or present in candidate workspace (#161)
|
|
176
|
-
const candFiles = fsSync.existsSync(executionDir) ? fsSync.readdirSync(executionDir) : []
|
|
181
|
+
const candFiles = fsSync.existsSync(executionDir) ? fsSync.readdirSync(executionDir, { recursive: true }) : []
|
|
177
182
|
const scriptTokens = new Set([
|
|
178
183
|
...cmdTokens.map((t) => t.replace(/^["']|["']$/g, '')),
|
|
179
|
-
...candFiles,
|
|
184
|
+
...candFiles.map((f) => String(f)),
|
|
180
185
|
])
|
|
181
186
|
|
|
182
187
|
for (const scriptName of scriptTokens) {
|
|
@@ -251,19 +256,35 @@ export async function runCandidateTestGate({
|
|
|
251
256
|
const cp = require('node:child_process');
|
|
252
257
|
const path = require('node:path');
|
|
253
258
|
const allowed = ${JSON.stringify(allowedReads)};
|
|
254
|
-
|
|
259
|
+
const UNIX_ROOT_PREFIX = /^\\/(?:home|root|etc|var|tmp|opt|mnt|srv|media|proc|sys|dev|usr|bin|lib|lib64|run|sbin|boot|Users|Applications|Library|System|Volumes|private)(?:\\/|$)/;
|
|
260
|
+
function checkSinglePath(p) {
|
|
255
261
|
if (!p || typeof p !== 'string') return true;
|
|
256
262
|
if (/^\\/(usr\\/(bin|lib|include)|bin|lib|lib64|etc|dev)(\\/|$)/.test(p) && !/\\.(txt|json|env|npmrc|key|ssh|md)/i.test(p)) return true;
|
|
263
|
+
const isAbs = path.isAbsolute(p);
|
|
264
|
+
const isUp = p.startsWith('..');
|
|
265
|
+
if (!isAbs && !isUp) return true;
|
|
266
|
+
if (isAbs && process.platform !== 'win32' && !UNIX_ROOT_PREFIX.test(p)) return true;
|
|
257
267
|
const res = path.resolve(process.cwd(), p);
|
|
258
268
|
return allowed.some(root => root && (res === root || res.startsWith(root + path.sep)));
|
|
259
269
|
}
|
|
270
|
+
function scanStringForPaths(str) {
|
|
271
|
+
if (!str || typeof str !== 'string') return true;
|
|
272
|
+
if (!checkSinglePath(str)) return false;
|
|
273
|
+
const matches = str.match(/(?:[a-zA-Z]:[\\\\/][^\\s"'<>]+|(?<![\\w.@/\\\\])\\/(?:[a-zA-Z0-9_\\-.]+\\/)*[a-zA-Z0-9_\\-.]+|(?<![\\w.@/\\\\])\\.\\.[\\\\/][^\\s"'<>]*)/g) || [];
|
|
274
|
+
for (const m of matches) {
|
|
275
|
+
if (!checkSinglePath(m)) return false;
|
|
276
|
+
}
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
260
279
|
function guardArgs(cmd, args) {
|
|
261
|
-
if (cmd &&
|
|
262
|
-
throw new Error('ERR_ACCESS_DENIED: child process
|
|
280
|
+
if (cmd && !scanStringForPaths(String(cmd))) {
|
|
281
|
+
throw new Error('ERR_ACCESS_DENIED: child process command outside sandbox: ' + cmd);
|
|
263
282
|
}
|
|
264
283
|
if (Array.isArray(args)) {
|
|
265
284
|
for (const a of args) {
|
|
266
|
-
if (!
|
|
285
|
+
if (a && !scanStringForPaths(String(a))) {
|
|
286
|
+
throw new Error('ERR_ACCESS_DENIED: child process argument outside sandbox: ' + a);
|
|
287
|
+
}
|
|
267
288
|
}
|
|
268
289
|
}
|
|
269
290
|
}
|
package/lib/routes.js
CHANGED
|
@@ -37,10 +37,7 @@ ctx.effect(() => {
|
|
|
37
37
|
kind: 'exact',
|
|
38
38
|
path: '/dsh-moa/status',
|
|
39
39
|
handler: (req, res) => {
|
|
40
|
-
if (req.method !== 'GET') {
|
|
41
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
42
|
-
return
|
|
43
|
-
}
|
|
40
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
44
41
|
try {
|
|
45
42
|
const cfg = live()
|
|
46
43
|
writeJson(res, 200, {
|
|
@@ -49,9 +46,7 @@ ctx.effect(() => {
|
|
|
49
46
|
defaultPreset: cfg.default_preset || 'default',
|
|
50
47
|
presetsCount: (cfg.presets || []).length,
|
|
51
48
|
})
|
|
52
|
-
} catch (err) {
|
|
53
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
54
|
-
}
|
|
49
|
+
} catch (err) { writeJson(res, 500, { ok: false, error: err?.message || String(err) }) }
|
|
55
50
|
},
|
|
56
51
|
})
|
|
57
52
|
}, 'dsh-moa: status route')
|
|
@@ -62,10 +57,7 @@ ctx.effect(() => {
|
|
|
62
57
|
kind: 'exact',
|
|
63
58
|
path: '/dsh-moa/models',
|
|
64
59
|
handler: async (req, res) => {
|
|
65
|
-
if (req.method !== 'GET') {
|
|
66
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
67
|
-
return
|
|
68
|
-
}
|
|
60
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
69
61
|
if (_modelsCache.models.length > 0 && Date.now() - _modelsCache.timestamp < MODELS_CACHE_TTL) {
|
|
70
62
|
writeJson(res, 200, { ok: true, models: _modelsCache.models, cached: true })
|
|
71
63
|
return
|
|
@@ -201,21 +193,21 @@ ctx.effect(() => {
|
|
|
201
193
|
}
|
|
202
194
|
|
|
203
195
|
// Persist via saveConfig (in-memory and DSH profile settings)
|
|
196
|
+
let persistResult
|
|
204
197
|
if (typeof saveConfig === 'function') {
|
|
205
|
-
try { await saveConfig(validated) }
|
|
198
|
+
try { persistResult = await saveConfig(validated) }
|
|
206
199
|
catch (perr) { return writeJson(res, 500, { ok: false, error: 'Settings persistence failed: ' + (perr?.message || String(perr)) }) }
|
|
207
200
|
}
|
|
208
201
|
|
|
209
202
|
const unwrapped = typeof plainConfig === 'function' ? plainConfig(validated) : validated
|
|
210
203
|
writeJson(res, 200, {
|
|
211
204
|
ok: true,
|
|
205
|
+
persisted: persistResult?.persisted ?? false,
|
|
212
206
|
enabled: unwrapped.enabled !== false,
|
|
213
207
|
defaultPreset: unwrapped.default_preset,
|
|
214
208
|
presets: unwrapped.presets,
|
|
215
209
|
})
|
|
216
|
-
} catch (err) {
|
|
217
|
-
writeJson(res, 400, { ok: false, error: err?.message || String(err) })
|
|
218
|
-
}
|
|
210
|
+
} catch (err) { writeJson(res, 400, { ok: false, error: err?.message || String(err) }) }
|
|
219
211
|
return
|
|
220
212
|
}
|
|
221
213
|
|
|
@@ -230,19 +222,14 @@ ctx.effect(() => {
|
|
|
230
222
|
kind: 'exact',
|
|
231
223
|
path: '/dsh-moa/history',
|
|
232
224
|
handler: async (req, res) => {
|
|
233
|
-
if (req.method !== 'GET') {
|
|
234
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
235
|
-
return
|
|
236
|
-
}
|
|
225
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
237
226
|
try {
|
|
238
227
|
const u = new URL(req.url, 'http://localhost')
|
|
239
228
|
const limit = Math.max(1, Math.min(100, parseInt(u.searchParams.get('limit') || '20', 10) || 20))
|
|
240
229
|
const offset = Math.max(0, parseInt(u.searchParams.get('offset') || '0', 10) || 0)
|
|
241
230
|
const data = getMoaHistory(limit, offset)
|
|
242
231
|
writeJson(res, 200, { ok: true, ...data })
|
|
243
|
-
} catch (err) {
|
|
244
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
245
|
-
}
|
|
232
|
+
} catch (err) { writeJson(res, 500, { ok: false, error: err?.message || String(err) }) }
|
|
246
233
|
},
|
|
247
234
|
})
|
|
248
235
|
}, 'dsh-moa: history route')
|
|
@@ -253,10 +240,7 @@ ctx.effect(() => {
|
|
|
253
240
|
kind: 'exact',
|
|
254
241
|
path: '/dsh-moa/leaderboard',
|
|
255
242
|
handler: async (req, res) => {
|
|
256
|
-
if (req.method !== 'GET') {
|
|
257
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
258
|
-
return
|
|
259
|
-
}
|
|
243
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
260
244
|
try {
|
|
261
245
|
const url = new URL(req.url || '', 'http://127.0.0.1')
|
|
262
246
|
const preset = url.searchParams.get('preset') || null
|
|
@@ -274,9 +258,7 @@ ctx.effect(() => {
|
|
|
274
258
|
|
|
275
259
|
const data = getMoaLeaderboard(undefined, { preset })
|
|
276
260
|
writeJson(res, 200, { ok: true, ...data })
|
|
277
|
-
} catch (err) {
|
|
278
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
279
|
-
}
|
|
261
|
+
} catch (err) { writeJson(res, 500, { ok: false, error: err?.message || String(err) }) }
|
|
280
262
|
},
|
|
281
263
|
})
|
|
282
264
|
}, 'dsh-moa: leaderboard route')
|
|
@@ -287,10 +269,7 @@ ctx.effect(() => {
|
|
|
287
269
|
kind: 'exact',
|
|
288
270
|
path: '/dsh-moa/promote',
|
|
289
271
|
handler: async (req, res) => {
|
|
290
|
-
if (req.method !== 'POST') {
|
|
291
|
-
writeJson(res, 405, { ok: false, error: 'POST required' })
|
|
292
|
-
return
|
|
293
|
-
}
|
|
272
|
+
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST required' })
|
|
294
273
|
if (!isSafeWriteRequest(req)) {
|
|
295
274
|
writeJson(res, 403, { ok: false, error: 'Forbidden: cross-site request rejected' })
|
|
296
275
|
return
|
|
@@ -303,26 +282,35 @@ ctx.effect(() => {
|
|
|
303
282
|
writeJson(res, 400, { ok: false, error: 'Valid candidateIndex required (>= 1)' })
|
|
304
283
|
return
|
|
305
284
|
}
|
|
306
|
-
const cwd = body?.cwd || process.cwd()
|
|
307
285
|
const runId = body?.runId
|
|
286
|
+
let recordedRun = null
|
|
308
287
|
if (runId) {
|
|
309
|
-
|
|
310
|
-
if (recordedRun
|
|
311
|
-
writeJson(res,
|
|
288
|
+
recordedRun = getMoaRunById(runId)
|
|
289
|
+
if (!recordedRun) {
|
|
290
|
+
writeJson(res, 404, { ok: false, error: `Run '${runId}' not found` })
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
recordedRun = getMoaHistory(1, 0)?.runs?.[0] || null
|
|
295
|
+
if (!recordedRun) {
|
|
296
|
+
writeJson(res, 400, { ok: false, error: 'No recorded MoA run found' })
|
|
312
297
|
return
|
|
313
298
|
}
|
|
314
299
|
}
|
|
315
|
-
|
|
316
|
-
|
|
300
|
+
if (body?.cwd && path.resolve(body.cwd) !== path.resolve(recordedRun.cwd)) {
|
|
301
|
+
writeJson(res, 403, { ok: false, error: 'Forbidden: cwd does not match recorded run cwd' })
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
const effectiveCwd = recordedRun.cwd
|
|
305
|
+
const promotedFiles = await promoteCandidateWorkspace(effectiveCwd, candidateIndex, {
|
|
306
|
+
runId: runId ? recordedRun.id : undefined,
|
|
317
307
|
keepMoa: true,
|
|
318
308
|
port: ctx.webServer?.port,
|
|
319
309
|
timeMachineEngine: timeMachineEngine || ctx.reflect?.get?.('timeMachineEngine', false),
|
|
320
310
|
force: Boolean(body?.force),
|
|
321
311
|
})
|
|
322
312
|
writeJson(res, 200, { ok: true, candidateIndex, promotedFiles, checkpoint: promotedFiles?.checkpoint || null })
|
|
323
|
-
} catch (err) {
|
|
324
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
325
|
-
}
|
|
313
|
+
} catch (err) { writeJson(res, 500, { ok: false, error: err?.message || String(err) }) }
|
|
326
314
|
},
|
|
327
315
|
})
|
|
328
316
|
}, 'dsh-moa: promote candidate route')
|
|
@@ -333,10 +321,7 @@ ctx.effect(() => {
|
|
|
333
321
|
kind: 'exact',
|
|
334
322
|
path: '/dsh-moa/diff',
|
|
335
323
|
handler: async (req, res) => {
|
|
336
|
-
if (req.method !== 'GET') {
|
|
337
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
338
|
-
return
|
|
339
|
-
}
|
|
324
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
340
325
|
try {
|
|
341
326
|
const u = new URL(req.url, 'http://localhost')
|
|
342
327
|
const rawRunId = u.searchParams.get('runId')
|
|
@@ -349,14 +334,18 @@ ctx.effect(() => {
|
|
|
349
334
|
return
|
|
350
335
|
}
|
|
351
336
|
} else {
|
|
352
|
-
run = getMoaHistory(1, 0)
|
|
337
|
+
run = getMoaHistory(1, 0)?.runs?.[0] || null
|
|
338
|
+
if (!run) {
|
|
339
|
+
writeJson(res, 404, { ok: false, error: 'No recorded MoA run found' })
|
|
340
|
+
return
|
|
341
|
+
}
|
|
353
342
|
}
|
|
354
|
-
if (
|
|
343
|
+
if (queryCwd && path.resolve(queryCwd) !== path.resolve(run.cwd)) {
|
|
355
344
|
writeJson(res, 403, { ok: false, error: 'Forbidden: cwd does not match recorded run cwd' })
|
|
356
345
|
return
|
|
357
346
|
}
|
|
358
|
-
const runId = run
|
|
359
|
-
const cwd = run
|
|
347
|
+
const runId = run.id
|
|
348
|
+
const cwd = run.cwd
|
|
360
349
|
const sanitizeTarget = (val, def) => {
|
|
361
350
|
const clean = String(val || '').trim().replace(/[^a-zA-Z0-9_-]/g, '')
|
|
362
351
|
return clean || def
|
|
@@ -393,24 +382,18 @@ ctx.effect(() => {
|
|
|
393
382
|
toSize: toItem?.size || 0,
|
|
394
383
|
diff,
|
|
395
384
|
})
|
|
396
|
-
} catch (err) {
|
|
397
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
398
|
-
}
|
|
385
|
+
} catch (err) { writeJson(res, 500, { ok: false, error: err?.message || String(err) }) }
|
|
399
386
|
},
|
|
400
387
|
})
|
|
401
388
|
}, 'dsh-moa: candidate diff route')
|
|
402
389
|
|
|
403
|
-
|
|
404
390
|
// Route: /dsh-moa/history/export
|
|
405
391
|
ctx.effect(() => {
|
|
406
392
|
return ctx.webServer.register({
|
|
407
393
|
kind: 'exact',
|
|
408
394
|
path: '/dsh-moa/history/export',
|
|
409
395
|
handler: async (req, res) => {
|
|
410
|
-
if (req.method !== 'GET') {
|
|
411
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
412
|
-
return
|
|
413
|
-
}
|
|
396
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
414
397
|
try {
|
|
415
398
|
const url = new URL(req.url || '', 'http://127.0.0.1')
|
|
416
399
|
const preset = url.searchParams.get('preset') || null
|
|
@@ -423,9 +406,7 @@ ctx.effect(() => {
|
|
|
423
406
|
'Content-Disposition': `attachment; filename="moa-history.${isCsv ? 'csv' : 'json'}"`,
|
|
424
407
|
})
|
|
425
408
|
res.end(data)
|
|
426
|
-
} catch (err) {
|
|
427
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
428
|
-
}
|
|
409
|
+
} catch (err) { writeJson(res, 500, { ok: false, error: err?.message || String(err) }) }
|
|
429
410
|
},
|
|
430
411
|
})
|
|
431
412
|
}, 'dsh-moa: history export route')
|
|
@@ -436,10 +417,7 @@ ctx.effect(() => {
|
|
|
436
417
|
kind: 'prefix',
|
|
437
418
|
path: '/dsh-moa/runs/',
|
|
438
419
|
handler: (req, res) => {
|
|
439
|
-
if (req.method !== 'GET') {
|
|
440
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
441
|
-
return
|
|
442
|
-
}
|
|
420
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
443
421
|
const urlPath = req.url.split('?')[0]
|
|
444
422
|
const parts = urlPath.split('/').filter(Boolean)
|
|
445
423
|
const runId = parts[2] || ''
|
|
@@ -484,10 +462,7 @@ ctx.effect(() => {
|
|
|
484
462
|
kind: 'exact',
|
|
485
463
|
path: '/dsh-moa/route-preset',
|
|
486
464
|
handler: async (req, res) => {
|
|
487
|
-
if (req.method !== 'POST') {
|
|
488
|
-
writeJson(res, 405, { ok: false, error: 'POST required' })
|
|
489
|
-
return
|
|
490
|
-
}
|
|
465
|
+
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST required' })
|
|
491
466
|
if (!isSafeWriteRequest(req)) {
|
|
492
467
|
writeJson(res, 403, { ok: false, error: 'Forbidden: cross-site request rejected' })
|
|
493
468
|
return
|
|
@@ -513,9 +488,7 @@ ctx.effect(() => {
|
|
|
513
488
|
enabled: cfg.enabled !== false,
|
|
514
489
|
})
|
|
515
490
|
writeJson(res, 200, { ok: true, ...result })
|
|
516
|
-
} catch (err) {
|
|
517
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
518
|
-
}
|
|
491
|
+
} catch (err) { writeJson(res, 500, { ok: false, error: err?.message || String(err) }) }
|
|
519
492
|
},
|
|
520
493
|
})
|
|
521
494
|
}, 'dsh-moa: route preset endpoint')
|
|
@@ -529,10 +502,7 @@ ctx.effect(() => {
|
|
|
529
502
|
kind: 'exact',
|
|
530
503
|
path: '/dsh-moa/run',
|
|
531
504
|
handler: async (req, res) => {
|
|
532
|
-
if (req.method !== 'POST') {
|
|
533
|
-
writeJson(res, 405, { ok: false, error: 'POST required' })
|
|
534
|
-
return
|
|
535
|
-
}
|
|
505
|
+
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST required' })
|
|
536
506
|
if (!isSafeWriteRequest(req)) {
|
|
537
507
|
writeJson(res, 403, { ok: false, error: 'Forbidden: cross-site request rejected' })
|
|
538
508
|
return
|
|
@@ -584,9 +554,7 @@ ctx.effect(() => {
|
|
|
584
554
|
}
|
|
585
555
|
|
|
586
556
|
writeJson(res, 200, { ok: true, ...result })
|
|
587
|
-
} catch (err) {
|
|
588
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
589
|
-
}
|
|
557
|
+
} catch (err) { writeJson(res, 500, { ok: false, error: err?.message || String(err) }) }
|
|
590
558
|
},
|
|
591
559
|
})
|
|
592
560
|
}, 'dsh-moa: run pipeline route')
|