@goodandready/dsh-moa 0.2.47 → 0.2.48
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/moa-budget.js +24 -4
- package/lib/moa-runner.js +9 -8
- package/lib/moa-test-gate.js +51 -21
- package/package.json +1 -1
package/lib/moa-budget.js
CHANGED
|
@@ -87,11 +87,31 @@ 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(userPrompt = '', referenceOutputs = [], judgeCriteria = '', overhead = 2600) {
|
|
91
|
-
const
|
|
90
|
+
export function calcSynthesisPromptTokens(userPrompt = '', referenceOutputs = [], judgeCriteria = '', overhead = 2600, options = {}) {
|
|
91
|
+
const opts = (typeof overhead === 'object' && overhead !== null) ? overhead : (options || {})
|
|
92
|
+
const baseOverhead = typeof overhead === 'number' ? overhead : 2600
|
|
93
|
+
let promptChars = (typeof userPrompt === 'string' ? userPrompt.length : 0) +
|
|
92
94
|
(typeof judgeCriteria === 'string' ? judgeCriteria.length : 0) +
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
baseOverhead
|
|
96
|
+
|
|
97
|
+
if (Array.isArray(referenceOutputs)) {
|
|
98
|
+
for (const r of referenceOutputs) {
|
|
99
|
+
promptChars += (r.text?.length || 0)
|
|
100
|
+
if (r.testResult) {
|
|
101
|
+
promptChars += 150 + (r.testResult.output?.length || 0)
|
|
102
|
+
}
|
|
103
|
+
if (r.files && r.files.length > 0) {
|
|
104
|
+
promptChars += 50 + r.files.reduce((acc, f) => acc + (f.relativePath?.length || 0), 0)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (opts.priorTurnBaseline) promptChars += opts.priorTurnBaseline.length + 50
|
|
109
|
+
if (opts.consensusReport) {
|
|
110
|
+
const crText = typeof opts.consensusReport === 'string' ? opts.consensusReport : JSON.stringify(opts.consensusReport)
|
|
111
|
+
promptChars += crText.length + 50
|
|
112
|
+
}
|
|
113
|
+
if (opts.compositeMergeDirective) promptChars += opts.compositeMergeDirective.length + 50
|
|
114
|
+
|
|
95
115
|
return Math.max(50, Math.ceil(promptChars / 4))
|
|
96
116
|
}
|
|
97
117
|
|
package/lib/moa-runner.js
CHANGED
|
@@ -401,7 +401,15 @@ export async function runMoAPipeline({
|
|
|
401
401
|
: null
|
|
402
402
|
|
|
403
403
|
// 8. Synthesis phase via primary judge or fallback chain
|
|
404
|
-
const
|
|
404
|
+
const synthesisPrompt = buildSynthesisPrompt(userPrompt, referenceOutputs, judgeCriteria, {
|
|
405
|
+
curatorSynthesis: isCuratorSynthesis,
|
|
406
|
+
blindEvaluation: isBlindEvaluation,
|
|
407
|
+
priorTurnBaseline,
|
|
408
|
+
compositeMergeDirective,
|
|
409
|
+
consensusReport: multiJudgeResult?.consensusReport,
|
|
410
|
+
})
|
|
411
|
+
const synthPromptTokens = Math.max(50, Math.ceil(synthesisPrompt.length / 4))
|
|
412
|
+
|
|
405
413
|
if (preset?.budget_guard_enabled && preset?.max_budget_usd > 0 && !isFastMode) {
|
|
406
414
|
const currentSpent = referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0) + (multiJudgeResult?.totalCostUsd || 0)
|
|
407
415
|
const downNext = estimateDownstreamCost(primaryJudge, [], successfulRefs.length, prices, maxTokens, synthPromptTokens)
|
|
@@ -411,13 +419,6 @@ export async function runMoAPipeline({
|
|
|
411
419
|
return createBudgetAbortPayload({ reason: phaseCheck.reason, primaryJudge, referenceOutputs, currentSpentUsd: currentSpent, preset, isRefinement, isFastMode, collectedCtx, startTime, multiJudgeResult })
|
|
412
420
|
}
|
|
413
421
|
}
|
|
414
|
-
const synthesisPrompt = buildSynthesisPrompt(userPrompt, referenceOutputs, judgeCriteria, {
|
|
415
|
-
curatorSynthesis: isCuratorSynthesis,
|
|
416
|
-
blindEvaluation: isBlindEvaluation,
|
|
417
|
-
priorTurnBaseline,
|
|
418
|
-
compositeMergeDirective,
|
|
419
|
-
consensusReport: multiJudgeResult?.consensusReport,
|
|
420
|
-
})
|
|
421
422
|
let synthesizedText = ''
|
|
422
423
|
let aggUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 }
|
|
423
424
|
let chosenJudge = primaryJudge
|
package/lib/moa-test-gate.js
CHANGED
|
@@ -43,34 +43,47 @@ export function resolveCandidateDir(baseDir, candidateIndex, options = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
export function decodeEscapedStrings(str) {
|
|
47
|
+
if (!str || typeof str !== 'string') return ''
|
|
48
|
+
let res = str.replace(/\\+/g, '\\').replace(/\\(?:([0-7]{1,3})|x([0-9a-fA-F]{2}))/g, (m, oct, hex) =>
|
|
49
|
+
oct ? String.fromCharCode(parseInt(oct, 8)) : hex ? String.fromCharCode(parseInt(hex, 16)) : m
|
|
50
|
+
)
|
|
51
|
+
return res.replace(/(?<![A-Za-z0-9+/=])(?:(?:[A-Za-z0-9+/]{4}){2,}|(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=))(?![A-Za-z0-9+/=])/g, (m) => {
|
|
52
|
+
try {
|
|
53
|
+
const dec = Buffer.from(m, 'base64').toString('utf8')
|
|
54
|
+
return /^[\x20-\x7E\r\n\t]+$/.test(dec) ? dec : m
|
|
55
|
+
} catch (err) {
|
|
56
|
+
logger.debug('[dsh-moa:testGate] base64 decode skip:', err?.message || err)
|
|
57
|
+
return m
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const COMPUTED_SHELL_EVASION = /(?:base64\s+(?:-d|--decode|-D)[^|\n;]*\|\s*(?:ba|z|da)?sh|\beval\s+["\']?\$|\bxxd\s+-r[^|\n;]*\|\s*(?:ba|z|da)?sh|\bprintf\s+[^|\n]+\|\s*(?:ba|z|da)?sh|\bopenssl\s+enc\s+-d[^|\n;]*\|\s*(?:ba|z|da)?sh)/i
|
|
47
63
|
|
|
48
|
-
const UNIX_ROOT_PREFIX = /^\/(?:home|root|etc|var|tmp|opt|mnt|srv|media|proc|sys|dev|usr|bin|lib|lib64|run\/(?:user
|
|
64
|
+
const UNIX_ROOT_PREFIX = /^\/(?:home|root|etc|var|tmp|opt|mnt|srv|media|proc|sys|dev|usr|bin|lib|lib64|run\/(?:user\/\d+|systemd|lock|credentials|secrets|udev|initramfs|mount|shm|dbus|sshd|motd)|sbin|boot|Users\/(?:[a-zA-Z0-9_\-.]+)|Applications|Library|System|Volumes|private)(?:\/|$)/
|
|
49
65
|
|
|
50
66
|
/**
|
|
51
67
|
* Validates that an executed script or command does not reference files outside allowed roots (#161, #225).
|
|
52
68
|
*/
|
|
53
69
|
function scanPathTokensForLeak(text, allowedRoots, executionDir) {
|
|
54
70
|
if (!text || typeof text !== 'string') return null
|
|
55
|
-
const
|
|
56
|
-
.replace(/https?:\/\/[^\s"'<>]+/gi, '')
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
continue
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const isUp = raw.startsWith('..')
|
|
65
|
-
if (!isAbs && !isUp) continue
|
|
66
|
-
if (isAbs && process.platform !== 'win32' && !UNIX_ROOT_PREFIX.test(raw)) continue
|
|
67
|
-
const resolved = path.resolve(executionDir, raw)
|
|
68
|
-
const allowed = allowedRoots.some((r) => r && (resolved === r || resolved.startsWith(r + path.sep)))
|
|
69
|
-
if (!allowed) {
|
|
70
|
-
return raw
|
|
71
|
+
const checkText = (t) => {
|
|
72
|
+
const sanitized = t.replace(/https?:\/\/[^\s"'<>]+/gi, '').replace(/\b(?:node|file|npm|data):[^\s"'<>]+/gi, '')
|
|
73
|
+
const matches = sanitized.match(/(?:[a-zA-Z]:[\\/][^\s"'<>]+|(?<![\w.@\/\\])\/(?:[a-zA-Z0-9_\-.]+\/)*[a-zA-Z0-9_\-.]+|(?<![\w.@\/\\])\.\.[\\/][^\s"'<>]*)/g) || []
|
|
74
|
+
for (const raw of matches) {
|
|
75
|
+
if (/^\/(usr\/(bin|lib|include)|bin|lib|lib64|etc|dev)(\/|$)/.test(raw) && !/\.(txt|json|env|npmrc|key|ssh|md)/i.test(raw)) continue
|
|
76
|
+
const isAbs = path.isAbsolute(raw)
|
|
77
|
+
if ((!isAbs && !raw.startsWith('..')) || (isAbs && process.platform !== 'win32' && !UNIX_ROOT_PREFIX.test(raw))) continue
|
|
78
|
+
const resolved = path.resolve(executionDir, raw)
|
|
79
|
+
if (!allowedRoots.some((r) => r && (resolved === r || resolved.startsWith(r + path.sep)))) return raw
|
|
71
80
|
}
|
|
81
|
+
return null
|
|
72
82
|
}
|
|
73
|
-
|
|
83
|
+
const rawLeak = checkText(text)
|
|
84
|
+
if (rawLeak) return rawLeak
|
|
85
|
+
const decoded = decodeEscapedStrings(text)
|
|
86
|
+
return decoded !== text ? checkText(decoded) : null
|
|
74
87
|
}
|
|
75
88
|
|
|
76
89
|
/**
|
|
@@ -288,8 +301,17 @@ export async function runCandidateTestGate({
|
|
|
288
301
|
const cp = require('node:child_process');
|
|
289
302
|
const path = require('node:path');
|
|
290
303
|
const allowed = ${JSON.stringify(allowedReads)};
|
|
291
|
-
|
|
292
|
-
|
|
304
|
+
function decodeEscaped(str) {
|
|
305
|
+
if (!str || typeof str !== 'string') return '';
|
|
306
|
+
const norm = str.split(/\\+/).join(String.fromCharCode(92));
|
|
307
|
+
return norm.replace(new RegExp(String.fromCharCode(92, 92) + '(?:([0-7]{1,3})|x([0-9a-fA-F]{2}))', 'g'), (m, oct, hex) => {
|
|
308
|
+
if (oct) return String.fromCharCode(parseInt(oct, 8));
|
|
309
|
+
if (hex) return String.fromCharCode(parseInt(hex, 16));
|
|
310
|
+
return m;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
const COMPUTED_SHELL_EVASION = /(?:base64\\s+(?:-d|--decode|-D)[^|\\n;]*\\|\\s*(?:ba|z|da)?sh|\\beval\\s+["\']?\\$|\\bxxd\\s+-r[^|\\n;]*\\|\\s*(?:ba|z|da)?sh|\\bprintf\\s+[^|\\n]+\\|\\s*(?:ba|z|da)?sh|\\bopenssl\\s+enc\\s+-d[^|\\n;]*\\|\\s*(?:ba|z|da)?sh)/i;
|
|
314
|
+
const UNIX_ROOT_PREFIX = /^\\/(?:home|root|etc|var|tmp|opt|mnt|srv|media|proc|sys|dev|usr|bin|lib|lib64|run\\/(?:user\\/\\d+|systemd|lock|credentials|secrets|udev|initramfs|mount|shm|dbus|sshd|motd)|sbin|boot|Users\\/(?:[a-zA-Z0-9_\\-.]+)|Applications|Library|System|Volumes|private)(?:\\/|$)/;
|
|
293
315
|
function checkSinglePath(p) {
|
|
294
316
|
if (!p || typeof p !== 'string') return true;
|
|
295
317
|
if (/^\\/(usr\\/(bin|lib|include)|bin|lib|lib64|etc|dev)(\\/|$)/.test(p) && !/\\.(txt|json|env|npmrc|key|ssh|md)/i.test(p)) return true;
|
|
@@ -303,10 +325,18 @@ function checkSinglePath(p) {
|
|
|
303
325
|
function scanStringForPaths(str) {
|
|
304
326
|
if (!str || typeof str !== 'string') return true;
|
|
305
327
|
if (!checkSinglePath(str)) return false;
|
|
328
|
+
const dec = decodeEscaped(str);
|
|
329
|
+
if (dec !== str && !checkSinglePath(dec)) return false;
|
|
306
330
|
const matches = str.match(/(?:[a-zA-Z]:[\\\\/][^\\s"'<>]+|(?<![\\w.@/\\\\])\\/(?:[a-zA-Z0-9_\\-.]+\\/)*[a-zA-Z0-9_\\-.]+|(?<![\\w.@/\\\\])\\.\\.[\\\\/][^\\s"'<>]*)/g) || [];
|
|
307
331
|
for (const m of matches) {
|
|
308
332
|
if (!checkSinglePath(m)) return false;
|
|
309
333
|
}
|
|
334
|
+
if (dec !== str) {
|
|
335
|
+
const decMatches = dec.match(/(?:[a-zA-Z]:[\\\\/][^\\s"'<>]+|(?<![\\w.@/\\\\])\\/(?:[a-zA-Z0-9_\\-.]+\\/)*[a-zA-Z0-9_\\-.]+|(?<![\\w.@/\\\\])\\.\\.[\\\\/][^\\s"'<>]*)/g) || [];
|
|
336
|
+
for (const m of decMatches) {
|
|
337
|
+
if (!checkSinglePath(m)) return false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
310
340
|
return true;
|
|
311
341
|
}
|
|
312
342
|
function guardArgs(cmd, args) {
|