@goodandready/dsh-moa 0.2.51 → 0.2.53
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 +2 -2
- package/lib/moa-budget.js +55 -15
- package/lib/moa-multi-judge.js +5 -1
- package/lib/routes.js +36 -6
- package/lib/updater.js +1 -2
- package/package.json +1 -1
package/lib/file-workspace.js
CHANGED
|
@@ -403,9 +403,9 @@ export async function cleanMoaWorkspaces(baseDir, options = {}) {
|
|
|
403
403
|
if (!baseDir) return
|
|
404
404
|
const rawRunId = typeof options === 'string' ? options : options?.runId
|
|
405
405
|
const safeRunId = rawRunId ? String(rawRunId).trim().replace(/[^a-zA-Z0-9_-]/g, '') : null
|
|
406
|
-
const moaRoot =
|
|
406
|
+
const moaRoot = assertPathContained(baseDir, '.moa')
|
|
407
407
|
if (safeRunId) {
|
|
408
|
-
await bestEffort('cleanMoaWorkspaces runId', () => fs.rm(
|
|
408
|
+
await bestEffort('cleanMoaWorkspaces runId', () => fs.rm(assertPathContained(moaRoot, safeRunId), { recursive: true, force: true }))
|
|
409
409
|
await bestEffort('clean empty moaRoot', async () => {
|
|
410
410
|
if ((await fs.readdir(moaRoot)).length === 0) await fs.rmdir(moaRoot)
|
|
411
411
|
})
|
package/lib/moa-budget.js
CHANGED
|
@@ -87,15 +87,38 @@ export function estimatePanelCost(judges = [], numCandidates = 1, prices = {}, m
|
|
|
87
87
|
* Calculates accurate synthesis prompt tokens accounting for full user prompt,
|
|
88
88
|
* judge criteria, and reference candidate texts (#158).
|
|
89
89
|
*/
|
|
90
|
-
export function calcSynthesisPromptTokens(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
export function calcSynthesisPromptTokens(firstArg = '', maybeRefs = [], maybeCriteria = '', maybeOverhead = 2600, maybeOptions = {}) {
|
|
91
|
+
let userPrompt, referenceOutputs, judgeCriteria, projectContext, priorTurnBaseline, consensusReport, compositeMergeDirective, baseOverhead
|
|
92
|
+
|
|
93
|
+
if (typeof firstArg === 'object' && firstArg !== null) {
|
|
94
|
+
userPrompt = firstArg.userPrompt || ''
|
|
95
|
+
referenceOutputs = firstArg.referenceOutputs || []
|
|
96
|
+
judgeCriteria = firstArg.judgeCriteria || ''
|
|
97
|
+
projectContext = firstArg.projectContext || ''
|
|
98
|
+
priorTurnBaseline = firstArg.priorTurnBaseline
|
|
99
|
+
consensusReport = firstArg.consensusReport
|
|
100
|
+
compositeMergeDirective = firstArg.compositeMergeDirective
|
|
101
|
+
baseOverhead = typeof firstArg.overhead === 'number' ? firstArg.overhead : 2600
|
|
102
|
+
} else {
|
|
103
|
+
userPrompt = typeof firstArg === 'string' ? firstArg : ''
|
|
104
|
+
referenceOutputs = Array.isArray(maybeRefs) ? maybeRefs : []
|
|
105
|
+
judgeCriteria = typeof maybeCriteria === 'string' ? maybeCriteria : ''
|
|
106
|
+
const opts = (typeof maybeOverhead === 'object' && maybeOverhead !== null) ? maybeOverhead : (maybeOptions || {})
|
|
107
|
+
projectContext = opts.projectContext || ''
|
|
108
|
+
priorTurnBaseline = opts.priorTurnBaseline
|
|
109
|
+
consensusReport = opts.consensusReport
|
|
110
|
+
compositeMergeDirective = opts.compositeMergeDirective
|
|
111
|
+
baseOverhead = typeof maybeOverhead === 'number' ? maybeOverhead : 2600
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let promptChars = userPrompt.length + judgeCriteria.length + (projectContext?.length || 0) + baseOverhead
|
|
96
115
|
|
|
97
116
|
if (Array.isArray(referenceOutputs)) {
|
|
117
|
+
if (referenceOutputs.some((r) => r && r.testResult)) {
|
|
118
|
+
promptChars += 550 // TEST_GATE_DIRECTIVE overhead
|
|
119
|
+
}
|
|
98
120
|
for (const r of referenceOutputs) {
|
|
121
|
+
if (!r) continue
|
|
99
122
|
promptChars += (r.text?.length || 0)
|
|
100
123
|
if (r.testResult) {
|
|
101
124
|
promptChars += 150 + (r.testResult.output?.length || 0)
|
|
@@ -105,12 +128,12 @@ export function calcSynthesisPromptTokens(userPrompt = '', referenceOutputs = []
|
|
|
105
128
|
}
|
|
106
129
|
}
|
|
107
130
|
}
|
|
108
|
-
if (
|
|
109
|
-
if (
|
|
110
|
-
const crText = typeof
|
|
131
|
+
if (priorTurnBaseline) promptChars += priorTurnBaseline.length + 50
|
|
132
|
+
if (consensusReport) {
|
|
133
|
+
const crText = typeof consensusReport === 'string' ? consensusReport : JSON.stringify(consensusReport)
|
|
111
134
|
promptChars += crText.length + 50
|
|
112
135
|
}
|
|
113
|
-
if (
|
|
136
|
+
if (compositeMergeDirective) promptChars += compositeMergeDirective.length + 50
|
|
114
137
|
|
|
115
138
|
return Math.max(50, Math.ceil(promptChars / 4))
|
|
116
139
|
}
|
|
@@ -301,8 +324,23 @@ export function createBudgetAbortPayload({
|
|
|
301
324
|
multiJudgeResult = null,
|
|
302
325
|
}) {
|
|
303
326
|
const aggLabel = (primaryJudge?.provider && primaryJudge?.model) ? `${primaryJudge.provider}:${primaryJudge.model}` : "none";
|
|
304
|
-
const refTokens = referenceOutputs.reduce((acc, r) => acc + (r.usage?.totalTokens || 0), 0);
|
|
305
|
-
const
|
|
327
|
+
const refTokens = referenceOutputs.reduce((acc, r) => acc + (r.usage?.totalTokens ?? ((r.usage?.inputTokens || 0) + (r.usage?.outputTokens || 0))), 0);
|
|
328
|
+
const refInTokens = referenceOutputs.reduce((acc, r) => acc + (r.usage?.inputTokens || r.usage?.promptTokens || 0), 0);
|
|
329
|
+
const refOutTokens = referenceOutputs.reduce((acc, r) => acc + (r.usage?.outputTokens || r.usage?.completionTokens || 0), 0);
|
|
330
|
+
|
|
331
|
+
const mjUsage = multiJudgeResult?.totalUsage;
|
|
332
|
+
const panelTokens = mjUsage?.totalTokens ?? ((mjUsage?.inputTokens || 0) + (mjUsage?.outputTokens || 0));
|
|
333
|
+
const panelInTokens = mjUsage?.inputTokens || mjUsage?.promptTokens || 0;
|
|
334
|
+
const panelOutTokens = mjUsage?.outputTokens || mjUsage?.completionTokens || 0;
|
|
335
|
+
const panelCostUsd = mjUsage?.costUsd || multiJudgeResult?.totalCostUsd || 0;
|
|
336
|
+
|
|
337
|
+
const refCost = referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0);
|
|
338
|
+
const totalCostUsd = Number(Math.max(currentSpentUsd, refCost + panelCostUsd).toFixed(5));
|
|
339
|
+
|
|
340
|
+
const totalTokens = refTokens + panelTokens;
|
|
341
|
+
const totalInputTokens = refInTokens + panelInTokens;
|
|
342
|
+
const totalOutputTokens = refOutTokens + panelOutTokens;
|
|
343
|
+
|
|
306
344
|
return {
|
|
307
345
|
kind: "failure",
|
|
308
346
|
content: `🛑 Budget Guardrail Abort: ${reason}`,
|
|
@@ -315,11 +353,13 @@ export function createBudgetAbortPayload({
|
|
|
315
353
|
winnerModel: "none",
|
|
316
354
|
promotedFiles: [],
|
|
317
355
|
usage: {
|
|
318
|
-
totalTokens
|
|
319
|
-
|
|
356
|
+
totalTokens,
|
|
357
|
+
totalInputTokens,
|
|
358
|
+
totalOutputTokens,
|
|
359
|
+
totalCostUsd,
|
|
320
360
|
candidates: referenceOutputs.map((r) => ({ label: r.label, usage: r.usage, costUsd: r.costUsd })),
|
|
321
361
|
aggregator: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 },
|
|
322
|
-
...(multiJudgeResult ? { multiJudge:
|
|
362
|
+
...(multiJudgeResult ? { multiJudge: mjUsage || { inputTokens: panelInTokens, outputTokens: panelOutTokens, totalTokens: panelTokens, costUsd: panelCostUsd } } : {}),
|
|
323
363
|
},
|
|
324
364
|
multiJudgeResult: multiJudgeResult || undefined,
|
|
325
365
|
skippedFiles: collectedCtx?.skippedFiles || 0,
|
package/lib/moa-multi-judge.js
CHANGED
|
@@ -420,7 +420,11 @@ export async function runMultiJudgePhase({
|
|
|
420
420
|
const panelMaxTokens = Math.min(typeof preset.max_tokens === 'number' && preset.max_tokens > 0 ? preset.max_tokens : 2048, 2048)
|
|
421
421
|
|
|
422
422
|
if (preset?.budget_guard_enabled && preset?.max_budget_usd > 0) {
|
|
423
|
-
const promptTokens = calcSynthesisPromptTokens(
|
|
423
|
+
const promptTokens = calcSynthesisPromptTokens({
|
|
424
|
+
userPrompt,
|
|
425
|
+
referenceOutputs: successfulRefs,
|
|
426
|
+
judgeCriteria,
|
|
427
|
+
})
|
|
424
428
|
const panelEst = estimatePanelCost(panelJudges, successfulRefs.length, prices, panelMaxTokens, promptTokens)
|
|
425
429
|
const downEst = estimateDownstreamCost(primaryJudge, [], successfulRefs.length, prices, maxTokens, promptTokens)
|
|
426
430
|
const phaseCheck = checkPhaseBudget({
|
package/lib/routes.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import path from 'node:path'
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import os from 'node:os'
|
|
7
|
+
import { getMoaHistory, getMoaLeaderboard, getMoaRunById, exportMoaHistory, isTestEnvironment } from './history.js'
|
|
8
|
+
import { promoteCandidateWorkspace, readCandidateFiles, computeLineDiff, isSafeRelativePath, isLoopbackUrl, assertPathContained } from './file-workspace.js'
|
|
8
9
|
import { isSafeWriteRequest } from './updater.js'
|
|
9
10
|
import { runMoAPipeline } from './moa-runner.js'
|
|
10
11
|
import { generateMoABenchmarkReport } from './moa-report.js'
|
|
@@ -30,7 +31,21 @@ function readBody(req) {
|
|
|
30
31
|
})
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
function isAllowedWorkspace(targetCwd, allowedRoots) {
|
|
35
|
+
if (!targetCwd || typeof targetCwd !== 'string') return false
|
|
36
|
+
for (const root of allowedRoots) {
|
|
37
|
+
if (!root) continue
|
|
38
|
+
try {
|
|
39
|
+
assertPathContained(root, targetCwd)
|
|
40
|
+
return true
|
|
41
|
+
} catch {
|
|
42
|
+
// not within root
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function registerMoaRoutes(ctx, { live, callLlm, liveCanvas, saveConfig, Config, plainConfig, webServerPort, timeMachineEngine, workspaceRoot }) {
|
|
34
49
|
|
|
35
50
|
ctx.effect(() => {
|
|
36
51
|
return ctx.webServer.register({
|
|
@@ -535,13 +550,28 @@ ctx.effect(() => {
|
|
|
535
550
|
const presetName = payload.preset || cfg.default_preset || 'default'
|
|
536
551
|
const preset = (cfg.presets || []).find((p) => p.name === presetName) || cfg.presets?.[0]
|
|
537
552
|
|
|
553
|
+
const baseWorkspaceRoot = workspaceRoot || ctx.baseDir || ctx.workspaceDir || process.cwd()
|
|
554
|
+
const allowedRoots = [
|
|
555
|
+
baseWorkspaceRoot,
|
|
556
|
+
process.cwd(),
|
|
557
|
+
cfg.workspace_root,
|
|
558
|
+
...(Array.isArray(cfg.allowed_workspaces) ? cfg.allowed_workspaces : (cfg.allowed_workspaces ? [cfg.allowed_workspaces] : [])),
|
|
559
|
+
...(isTestEnvironment() ? [os.tmpdir()] : []),
|
|
560
|
+
].filter(Boolean)
|
|
561
|
+
|
|
562
|
+
const targetCwd = payload.cwd ? path.resolve(payload.cwd) : baseWorkspaceRoot
|
|
563
|
+
if (payload.cwd && !isAllowedWorkspace(targetCwd, allowedRoots)) {
|
|
564
|
+
writeJson(res, 403, { ok: false, error: 'Forbidden: cwd escapes allowed workspace boundary' })
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
|
|
538
568
|
const result = await runMoAPipeline({
|
|
539
569
|
userPrompt: prompt, messages: payload.messages || [], preset, callLlm,
|
|
540
|
-
cwd:
|
|
570
|
+
cwd: targetCwd, prices: cfg.prices || {}, liveCanvas,
|
|
541
571
|
webServerPort: webServerPort || ctx.webServer?.port,
|
|
542
572
|
timeMachineEngine: timeMachineEngine || ctx.reflect?.get?.('timeMachineEngine', false),
|
|
543
|
-
timeMachineUrl: payload.timeMachineUrl, force:
|
|
544
|
-
checkpoint:
|
|
573
|
+
timeMachineUrl: payload.timeMachineUrl, force: false,
|
|
574
|
+
checkpoint: true,
|
|
545
575
|
runId: payload.runId,
|
|
546
576
|
})
|
|
547
577
|
|
package/lib/updater.js
CHANGED
|
@@ -44,8 +44,7 @@ export function isTrustedUpdateRequest(request) {
|
|
|
44
44
|
/** General safe write check for plugin write endpoints (CSRF / Cross-Site protection, #234) */
|
|
45
45
|
export function isSafeWriteRequest(request) {
|
|
46
46
|
const remote = request?.socket?.remoteAddress || request?.connection?.remoteAddress;
|
|
47
|
-
|
|
48
|
-
if (!isLoop) return false;
|
|
47
|
+
if (!remote || !isLoopback(remote)) return false;
|
|
49
48
|
|
|
50
49
|
const site = header(request, 'sec-fetch-site');
|
|
51
50
|
if (site === 'cross-site') return false;
|