@goodandready/dsh-moa 0.2.48 → 0.2.50

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/history.js CHANGED
@@ -51,6 +51,14 @@ export function invalidateHistoryCache() {
51
51
  _historyCache = null
52
52
  }
53
53
 
54
+ function getOrInitCache(filePath, stat) {
55
+ if (!_historyCache || _historyCache.filePath !== filePath || _historyCache.mtimeMs !== stat.mtimeMs || _historyCache.size !== stat.size) {
56
+ _historyCache = { filePath, mtimeMs: stat.mtimeMs, size: stat.size, leaderboards: new Map(), runsById: new Map(), totalCount: null, fullyIndexed: false }
57
+ }
58
+ if (!_historyCache.runsById) _historyCache.runsById = new Map()
59
+ return _historyCache
60
+ }
61
+
54
62
  /**
55
63
  * Checks if the history file exceeds the size threshold and rotates it.
56
64
  * Moves current file to `${filePath}.1`, removing any older `.1` backup.
@@ -277,22 +285,8 @@ export function countFileLinesSync(filePath, stat) {
277
285
  try { fs.closeSync(fd) } catch { /* best effort */ }
278
286
  }
279
287
 
280
- if (
281
- !_historyCache ||
282
- _historyCache.filePath !== filePath ||
283
- _historyCache.mtimeMs !== stat.mtimeMs ||
284
- _historyCache.size !== stat.size
285
- ) {
286
- _historyCache = {
287
- filePath,
288
- mtimeMs: stat.mtimeMs,
289
- size: stat.size,
290
- leaderboards: new Map(),
291
- totalCount: count,
292
- }
293
- } else {
294
- _historyCache.totalCount = count
295
- }
288
+ const cache = getOrInitCache(filePath, stat)
289
+ cache.totalCount = count
296
290
  return count
297
291
  }
298
292
 
@@ -314,29 +308,17 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = getDefaultHisto
314
308
  const lines = raw.trim().split('\n').filter(Boolean)
315
309
  const total = lines.length
316
310
 
317
- if (
318
- !_historyCache ||
319
- _historyCache.filePath !== filePath ||
320
- _historyCache.mtimeMs !== stat.mtimeMs ||
321
- _historyCache.size !== stat.size
322
- ) {
323
- _historyCache = {
324
- filePath,
325
- mtimeMs: stat.mtimeMs,
326
- size: stat.size,
327
- leaderboards: new Map(),
328
- totalCount: total,
329
- }
330
- } else {
331
- _historyCache.totalCount = total
332
- }
311
+ const cache = getOrInitCache(filePath, stat)
312
+ cache.totalCount = total
333
313
 
334
314
  const sliced = lines
335
315
  .slice(Math.max(0, total - offset - limit), total - offset)
336
316
  .reverse()
337
317
  .map((line) => {
338
318
  try {
339
- return JSON.parse(line)
319
+ const item = JSON.parse(line)
320
+ if (item?.id) cache.runsById.set(item.id, item)
321
+ return item
340
322
  } catch {
341
323
  return null
342
324
  }
@@ -348,24 +330,22 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = getDefaultHisto
348
330
 
349
331
  // For larger files: read only tail lines backwards to avoid splitting 10MB in memory
350
332
  const tailLines = readTailLinesSync(filePath, needed)
333
+ const cache = getOrInitCache(filePath, stat)
351
334
  const pageLines = tailLines.slice(offset, offset + limit)
352
335
  const runs = pageLines
353
336
  .map((line) => {
354
337
  try {
355
- return JSON.parse(line)
338
+ const item = JSON.parse(line)
339
+ if (item?.id) cache.runsById.set(item.id, item)
340
+ return item
356
341
  } catch {
357
342
  return null
358
343
  }
359
344
  })
360
345
  .filter(Boolean)
361
346
 
362
- let total = (_historyCache &&
363
- _historyCache.filePath === filePath &&
364
- _historyCache.mtimeMs === stat.mtimeMs &&
365
- _historyCache.size === stat.size &&
366
- typeof _historyCache.totalCount === 'number')
367
- ? _historyCache.totalCount
368
- : undefined
347
+ const isCached = _historyCache && _historyCache.filePath === filePath && _historyCache.mtimeMs === stat.mtimeMs && _historyCache.size === stat.size
348
+ let total = isCached && typeof _historyCache.totalCount === 'number' ? _historyCache.totalCount : undefined
369
349
  if (typeof total !== 'number') {
370
350
  total = tailLines.length < needed ? tailLines.length : countFileLinesSync(filePath, stat)
371
351
  }
@@ -392,24 +372,9 @@ export function getMoaLeaderboard(filePath = getDefaultHistoryFile(), options =
392
372
  const presetFilter = typeof options === 'string' ? options : (options?.presetFilter || options?.preset || null)
393
373
  const filterKey = presetFilter || 'all'
394
374
 
395
- if (
396
- _historyCache &&
397
- _historyCache.filePath === filePath &&
398
- _historyCache.mtimeMs === stat.mtimeMs &&
399
- _historyCache.size === stat.size
400
- ) {
401
- if (_historyCache.leaderboards.has(filterKey)) {
402
- return _historyCache.leaderboards.get(filterKey)
403
- }
404
- } else {
405
- _historyCache = {
406
- filePath,
407
- mtimeMs: stat.mtimeMs,
408
- size: stat.size,
409
- leaderboards: new Map(),
410
- totalCount: null,
411
- }
412
- }
375
+ const isCached = _historyCache && _historyCache.filePath === filePath && _historyCache.mtimeMs === stat.mtimeMs && _historyCache.size === stat.size
376
+ if (isCached && _historyCache.leaderboards.has(filterKey)) return _historyCache.leaderboards.get(filterKey)
377
+ const cache = getOrInitCache(filePath, stat)
413
378
 
414
379
  const raw = fs.readFileSync(filePath, 'utf8')
415
380
  const lines = raw.trim().split('\n').filter(Boolean)
@@ -500,19 +465,53 @@ export function getMoaLeaderboard(filePath = getDefaultHistoryFile(), options =
500
465
  }
501
466
 
502
467
  /**
503
- * Retrieves a single history run by ID.
468
+ * Retrieves a single history run by ID using in-memory cache and tail reading (#235).
504
469
  */
505
470
  export function getMoaRunById(runId, filePath = getDefaultHistoryFile()) {
506
471
  try {
507
- if (!fs.existsSync(filePath)) return null
472
+ if (!runId || !fs.existsSync(filePath)) return null
473
+ const stat = fs.statSync(filePath)
474
+ const cache = getOrInitCache(filePath, stat)
475
+ if (cache.runsById.has(runId)) return cache.runsById.get(runId)
476
+ if (cache.fullyIndexed) return null
477
+
478
+ if (stat.size <= 65536) {
479
+ const raw = fs.readFileSync(filePath, 'utf8')
480
+ const lines = raw.trim().split(String.fromCharCode(10)).filter(Boolean)
481
+ for (let i = lines.length - 1; i >= 0; i--) {
482
+ try {
483
+ const item = JSON.parse(lines[i])
484
+ if (item?.id) cache.runsById.set(item.id, item)
485
+ } catch { /* skip corrupt */ }
486
+ }
487
+ cache.fullyIndexed = true
488
+ if (cache.runsById.has(runId)) return cache.runsById.get(runId)
489
+ cache.runsById.set(runId, null)
490
+ return null
491
+ }
492
+
493
+ const tailLines = readTailLinesSync(filePath, 100)
494
+ for (const line of tailLines) {
495
+ try {
496
+ const item = JSON.parse(line)
497
+ if (item?.id) {
498
+ cache.runsById.set(item.id, item)
499
+ if (item.id === runId) return item
500
+ }
501
+ } catch { /* skip corrupt */ }
502
+ }
503
+
508
504
  const raw = fs.readFileSync(filePath, 'utf8')
509
- const lines = raw.trim().split('\n').filter(Boolean)
505
+ const lines = raw.trim().split(String.fromCharCode(10)).filter(Boolean)
510
506
  for (let i = lines.length - 1; i >= 0; i--) {
511
507
  try {
512
508
  const item = JSON.parse(lines[i])
513
- if (item && item.id === runId) return item
514
- } catch { /* corrupt line skipped */ }
509
+ if (item?.id) cache.runsById.set(item.id, item)
510
+ } catch { /* skip corrupt */ }
515
511
  }
512
+ cache.fullyIndexed = true
513
+ if (cache.runsById.has(runId)) return cache.runsById.get(runId)
514
+ cache.runsById.set(runId, null)
516
515
  return null
517
516
  } catch {
518
517
  return null
package/lib/moa-runner.js CHANGED
@@ -48,7 +48,7 @@ export async function runMoAPipeline({
48
48
  const aggTemp = typeof preset?.aggregator_temperature === 'number' ? preset.aggregator_temperature : 0.4
49
49
  const maxTokens = typeof preset?.max_tokens === 'number' ? preset.max_tokens : 4096
50
50
  const judgeCriteria = preset?.judge_criteria || ''
51
- const isFastMode = referenceModels.length === 1 && !preset?.curator_synthesis
51
+ let isFastMode = referenceModels.length === 1 && !preset?.curator_synthesis
52
52
  const isCuratorSynthesis = Boolean(preset?.curator_synthesis), isStreamAggregator = preset?.stream_aggregator !== false
53
53
  const isQuorumEnabled = Boolean(preset?.quorum_enabled), gracePeriodSec = typeof preset?.grace_period_sec === 'number' ? preset.grace_period_sec : 10
54
54
  const candidateRetries = 1, refTimeoutSec = typeof preset?.reference_timeout_sec === 'number' ? preset.reference_timeout_sec : 60
@@ -93,6 +93,9 @@ export async function runMoAPipeline({
93
93
  return createBudgetAbortPayload({ reason: budgetCheck.reason, primaryJudge, referenceOutputs: [], currentSpentUsd: 0, preset, isRefinement, isFastMode, collectedCtx, startTime })
94
94
  }
95
95
  const effectiveReferences = budgetCheck.references
96
+ if (effectiveReferences.length === 1 && !preset?.curator_synthesis) {
97
+ isFastMode = true
98
+ }
96
99
  if (budgetCheck.action === 'trim' && typeof onProgress === 'function') {
97
100
  onProgress(`✂️ *${budgetCheck.reason}*\n`)
98
101
  }
@@ -45,21 +45,12 @@ export function resolveCandidateDir(baseDir, candidateIndex, options = {}) {
45
45
 
46
46
  export function decodeEscapedStrings(str) {
47
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) =>
48
+ return str.replace(/\\+/g, '\\').replace(/\\(?:([0-7]{1,3})|x([0-9a-fA-F]{2}))/g, (m, oct, hex) =>
49
49
  oct ? String.fromCharCode(parseInt(oct, 8)) : hex ? String.fromCharCode(parseInt(hex, 16)) : m
50
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
51
  }
61
52
 
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
53
+ 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|\bprintf\s+(?:%s|["']%s["'])\s+.*["'](?:\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\$\(\s*(?:printf|echo)\s+.*["'](?:\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\x60\s*(?:printf|echo)\s+.*["'](?:\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\b(?:cat|head|tail|source|\.|touch|rm|cp|mv)\s+["']?\$[A-Za-z_]|>\s*["']?\$[A-Za-z_]|>>\s*["']?\$[A-Za-z_])/i
63
54
 
64
55
  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)(?:\/|$)/
65
56
 
@@ -82,6 +73,12 @@ function scanPathTokensForLeak(text, allowedRoots, executionDir) {
82
73
  }
83
74
  const rawLeak = checkText(text)
84
75
  if (rawLeak) return rawLeak
76
+ const segmented = text.match(/\bprintf\s+(?:%s|['"]%s['"])\s+((?:['"][^'"]*['"]\s*)+)/i)
77
+ if (segmented) {
78
+ const assembled = (segmented[1].match(/['"]([^'"]*)['"]/g) || []).map((s) => s.slice(1, -1)).join('')
79
+ const assembledLeak = checkText(assembled)
80
+ if (assembledLeak) return assembledLeak
81
+ }
85
82
  const decoded = decodeEscapedStrings(text)
86
83
  return decoded !== text ? checkText(decoded) : null
87
84
  }
@@ -166,6 +163,18 @@ export async function runCandidateTestGate({
166
163
  stderr = customRes?.stderr || ''
167
164
  } else {
168
165
  const allowedRoots = [executionDir, cwd, ...(candidateDir && candidateDir !== executionDir ? [candidateDir] : [])]
166
+ if (COMPUTED_SHELL_EVASION.test(resolvedCmd)) {
167
+ const durationMs = Date.now() - startTime
168
+ return {
169
+ candidateIndex,
170
+ passed: false,
171
+ exitCode: 1,
172
+ durationMs,
173
+ command: resolvedCmd,
174
+ output: 'Access denied: test command contains obfuscated or computed command execution',
175
+ summary: `FAIL (code 1, ${durationMs}ms)`,
176
+ }
177
+ }
169
178
  const cmdTokens = resolvedCmd.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []
170
179
 
171
180
  // 1. Check command tokens for absolute path leaks
@@ -310,7 +319,7 @@ function decodeEscaped(str) {
310
319
  return m;
311
320
  });
312
321
  }
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;
322
+ 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|\\bprintf\\s+(?:%s|["']%s["'])\\s+.*["'](?:\\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\\$\\(\\s*(?:printf|echo)\\s+.*["'](?:\\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\\x60\\s*(?:printf|echo)\\s+.*["'](?:\\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\\b(?:cat|head|tail|source|\\.|touch|rm|cp|mv)\\s+["']?\\$[A-Za-z_]|>\\s*["']?\\$[A-Za-z_]|>>\\s*["']?\\$[A-Za-z_])/i;
314
323
  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)(?:\\/|$)/;
315
324
  function checkSinglePath(p) {
316
325
  if (!p || typeof p !== 'string') return true;
@@ -355,36 +364,12 @@ function guardArgs(cmd, args) {
355
364
  }
356
365
  }
357
366
  }
358
- const origSpawn = cp.spawn;
359
- cp.spawn = function(cmd, args, opts) {
360
- guardArgs(cmd, args);
361
- return origSpawn.apply(this, arguments);
362
- };
363
- const origSpawnSync = cp.spawnSync;
364
- cp.spawnSync = function(cmd, args, opts) {
365
- guardArgs(cmd, args);
366
- return origSpawnSync.apply(this, arguments);
367
- };
368
- const origExecFile = cp.execFile;
369
- cp.execFile = function(file, args, opts, cb) {
370
- guardArgs(file, args);
371
- return origExecFile.apply(this, arguments);
372
- };
373
- const origExecFileSync = cp.execFileSync;
374
- cp.execFileSync = function(file, args, opts) {
375
- guardArgs(file, args);
376
- return origExecFileSync.apply(this, arguments);
377
- };
378
- const origExec = cp.exec;
379
- cp.exec = function(cmd, opts, cb) {
380
- guardArgs(cmd, [cmd]);
381
- return origExec.apply(this, arguments);
382
- };
383
- const origExecSync = cp.execSync;
384
- cp.execSync = function(cmd, opts) {
385
- guardArgs(cmd, [cmd]);
386
- return origExecSync.apply(this, arguments);
387
- };
367
+ const origSpawn = cp.spawn; cp.spawn = function(cmd, args, opts) { guardArgs(cmd, args); return origSpawn.apply(this, arguments); };
368
+ const origSpawnSync = cp.spawnSync; cp.spawnSync = function(cmd, args, opts) { guardArgs(cmd, args); return origSpawnSync.apply(this, arguments); };
369
+ const origExecFile = cp.execFile; cp.execFile = function(file, args, opts, cb) { guardArgs(file, args); return origExecFile.apply(this, arguments); };
370
+ const origExecFileSync = cp.execFileSync; cp.execFileSync = function(file, args, opts) { guardArgs(file, args); return origExecFileSync.apply(this, arguments); };
371
+ const origExec = cp.exec; cp.exec = function(cmd, opts, cb) { guardArgs(cmd, [cmd]); return origExec.apply(this, arguments); };
372
+ const origExecSync = cp.execSync; cp.execSync = function(cmd, opts) { guardArgs(cmd, [cmd]); return origExecSync.apply(this, arguments); };
388
373
  const fsMod = require('node:fs');
389
374
  function guardFs(p, isWrite) {
390
375
  if (!p || typeof p !== 'string') return;
package/lib/routes.js CHANGED
@@ -512,7 +512,6 @@ ctx.effect(() => {
512
512
  writeJson(res, 429, { ok: false, error: 'Rate limit exceeded: please wait before starting another run' })
513
513
  return
514
514
  }
515
- _lastRunTimestamp = now
516
515
 
517
516
  try {
518
517
  const raw = await readBody(req)
@@ -522,6 +521,7 @@ ctx.effect(() => {
522
521
  writeJson(res, 400, { ok: false, error: 'Empty prompt' })
523
522
  return
524
523
  }
524
+ _lastRunTimestamp = now
525
525
 
526
526
  const cfg = live()
527
527
  if (cfg.enabled === false) {
package/lib/updater.js CHANGED
@@ -41,10 +41,11 @@ export function isTrustedUpdateRequest(request) {
41
41
  }
42
42
  }
43
43
 
44
- /** General safe write check for plugin write endpoints (CSRF / Cross-Site protection) */
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
47
  const isLoop = !remote || isLoopback(remote);
48
+ if (!isLoop) return false;
48
49
 
49
50
  const site = header(request, 'sec-fetch-site');
50
51
  if (site === 'cross-site') return false;
@@ -52,13 +53,6 @@ export function isSafeWriteRequest(request) {
52
53
  const origin = header(request, 'origin');
53
54
  const host = header(request, 'host');
54
55
 
55
- // If non-loopback caller, require valid same-origin browser proof
56
- if (!isLoop) {
57
- if (!origin || !host) return false;
58
- if (site && site !== 'same-origin' && site !== 'none') return false;
59
- }
60
-
61
- // Same-site without matching origin is rejected regardless of remote
62
56
  if (site === 'same-site') {
63
57
  if (!origin || !host) return false;
64
58
  }
@@ -68,9 +62,7 @@ export function isSafeWriteRequest(request) {
68
62
  try {
69
63
  const url = new URL(origin);
70
64
  if (host && url.host !== host) return false;
71
- if (!isLoop) {
72
- if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
73
- }
65
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
74
66
  } catch {
75
67
  return false;
76
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-moa",
3
- "version": "0.2.48",
3
+ "version": "0.2.50",
4
4
  "description": "Mixture of Agents (MoA) plugin for DeepSeek Harness with /moa slash command",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",