@goodandready/dsh-moa 0.2.27 → 0.2.29

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.
@@ -11,7 +11,8 @@ export function isSafeRelativePath(relPath) {
11
11
  const trimmed = relPath.trim()
12
12
  if (!trimmed || path.isAbsolute(trimmed) || trimmed.startsWith('/') || trimmed.startsWith('\\')) return false
13
13
  const normalized = path.normalize(trimmed)
14
- return Boolean(normalized && !normalized.startsWith('..') && !path.isAbsolute(normalized))
14
+ if (!normalized || normalized === '.' || normalized.startsWith('..') || path.isAbsolute(normalized)) return false
15
+ return true
15
16
  }
16
17
 
17
18
  /**
@@ -150,9 +151,9 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
150
151
  const truncated = content.length > budgetRemaining ? content.slice(0, budgetRemaining) + '\n... [truncated]' : content
151
152
  totalChars += truncated.length
152
153
  collected.push({ relativePath: rel, content: truncated, size: stat.size })
153
- } catch {
154
+ } catch (err) {
154
155
  skippedFiles++
155
- skippedList.push(rel)
156
+ skippedList.push(`${rel} (read error: ${err?.message || String(err)})`)
156
157
  }
157
158
  }
158
159
  }
@@ -294,7 +295,7 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex, options =
294
295
  if (!options.keepMoa) {
295
296
  await bestEffort('clean moaRoot on promote error', () => fs.rm(moaRoot, { recursive: true, force: true }))
296
297
  }
297
- return []
298
+ throw err
298
299
  }
299
300
  }
300
301
 
package/lib/history.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js'
1
2
  /**
2
3
  * MoA Run History & Analytics Module
3
4
  * Records every MoA pipeline execution, tracks model win rates and costs,
@@ -47,7 +48,7 @@ export function rotateHistoryFileIfNeeded(filePath = DEFAULT_HISTORY_FILE, maxBy
47
48
  return true
48
49
  }
49
50
  } catch (err) {
50
- console.warn('[dsh-moa] History rotation warning:', err?.message || err)
51
+ logger.warn('[dsh-moa] History rotation warning:', err?.message || err)
51
52
  }
52
53
  return false
53
54
  }
@@ -110,7 +111,7 @@ export function recordMoaRun(record, filePath = DEFAULT_HISTORY_FILE) {
110
111
  invalidateHistoryCache()
111
112
  return entry
112
113
  } catch (err) {
113
- console.warn('[dsh-moa] Failed to record run to history:', err)
114
+ logger.warn('[dsh-moa] Failed to record run to history:', err)
114
115
  return null
115
116
  }
116
117
  }
@@ -131,7 +132,7 @@ export async function recordMoaRunAsync(record, filePath = DEFAULT_HISTORY_FILE)
131
132
  invalidateHistoryCache()
132
133
  return entry
133
134
  } catch (err) {
134
- console.warn('[dsh-moa] Failed to async-record run to history:', err)
135
+ logger.warn('[dsh-moa] Failed to async-record run to history:', err)
135
136
  return null
136
137
  }
137
138
  }
@@ -232,7 +233,7 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = DEFAULT_HISTORY
232
233
 
233
234
  return { total, runs }
234
235
  } catch (err) {
235
- console.warn('[dsh-moa] Failed to read history:', err)
236
+ logger.warn('[dsh-moa] Failed to read history:', err)
236
237
  return { total: 0, runs: [] }
237
238
  }
238
239
  }
@@ -335,7 +336,7 @@ export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE, options = {})
335
336
  _historyCache.leaderboards.set(filterKey, result)
336
337
  return result
337
338
  } catch (err) {
338
- console.warn('[dsh-moa] Failed to calculate leaderboard:', err)
339
+ logger.warn('[dsh-moa] Failed to calculate leaderboard:', err)
339
340
  return { totalRuns: 0, models: [] }
340
341
  }
341
342
  }
@@ -405,7 +406,7 @@ export function exportMoaHistory(filePath = DEFAULT_HISTORY_FILE, options = {})
405
406
 
406
407
  return JSON.stringify(records, null, 2)
407
408
  } catch (err) {
408
- console.warn('[dsh-moa] Failed to export history:', err)
409
+ logger.warn('[dsh-moa] Failed to export history:', err)
409
410
  return options?.format === 'csv' ? '' : '[]'
410
411
  }
411
412
  }
package/lib/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { setLogger, logger } from './logger.js'
1
2
  import { refreshCatalogInBackground } from './pricing.js'
2
3
  /**
3
4
  * DeepSeek Harness Mixture of Agents (MoA) Plugin
@@ -163,6 +164,7 @@ export function plainConfig(value) {
163
164
  }
164
165
 
165
166
  export function apply(ctx, config) {
167
+ if (ctx.logger) setLogger(ctx.logger)
166
168
  refreshCatalogInBackground()
167
169
 
168
170
  let localConfig = plainConfig(config ?? {})
@@ -209,7 +211,7 @@ export function apply(ctx, config) {
209
211
  await settingsService.update(NS, plain)
210
212
  }
211
213
  } catch (err) {
212
- console.warn('[dsh-moa] Settings persistence warning:', err?.message || err)
214
+ logger.warn('[dsh-moa] Settings persistence warning:', err?.message || err)
213
215
  }
214
216
  return localConfig
215
217
  }
package/lib/logger.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Context logger abstraction for dsh-moa.
3
+ * Avoids raw direct console warnings in server-side modules and respects Cordis ctx.logger.
4
+ */
5
+ let currentLogger = null
6
+
7
+ export function setLogger(logger) {
8
+ currentLogger = logger
9
+ }
10
+
11
+ export const logger = {
12
+ warn(...args) {
13
+ if (currentLogger && typeof currentLogger.warn === 'function') {
14
+ currentLogger.warn(...args)
15
+ } else if (typeof console !== 'undefined' && typeof console.debug === 'function') {
16
+ console.debug(...args)
17
+ }
18
+ },
19
+ debug(...args) {
20
+ if (currentLogger && typeof currentLogger.debug === 'function') {
21
+ currentLogger.debug(...args)
22
+ } else if (typeof console !== 'undefined' && typeof console.debug === 'function') {
23
+ console.debug(...args)
24
+ }
25
+ },
26
+ info(...args) {
27
+ if (currentLogger && typeof currentLogger.info === 'function') {
28
+ currentLogger.info(...args)
29
+ } else if (typeof console !== 'undefined' && typeof console.debug === 'function') {
30
+ console.debug(...args)
31
+ }
32
+ },
33
+ error(...args) {
34
+ if (currentLogger && typeof currentLogger.error === 'function') {
35
+ currentLogger.error(...args)
36
+ } else if (typeof console !== 'undefined' && typeof console.debug === 'function') {
37
+ console.debug(...args)
38
+ }
39
+ },
40
+ }
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js'
1
2
  /**
2
3
  * DeepSeek Harness Mixture of Agents (MoA) — Candidate Fan-Out & Retry
3
4
  * With Per-Candidate Temperature, Temperature Gradient & Local Fallback Resilience
@@ -200,7 +201,7 @@ export async function runReferencesParallel(references, messages, options = {},
200
201
  }
201
202
  break
202
203
  } catch (fbErr) {
203
- console.warn(`[dsh-moa] Fallback ${fbLabel} also failed:`, fbErr?.message || fbErr)
204
+ logger.warn(`[dsh-moa] Fallback ${fbLabel} also failed:`, fbErr?.message || fbErr)
204
205
  }
205
206
  }
206
207
  }
@@ -208,7 +209,7 @@ export async function runReferencesParallel(references, messages, options = {},
208
209
  if (!fallbackRecovered) {
209
210
  finishedCount++
210
211
  const errMsg = err?.message || String(err)
211
- console.warn(`[dsh-moa] Reference ${label} failed:`, errMsg)
212
+ logger.warn(`[dsh-moa] Reference ${label} failed:`, errMsg)
212
213
  if (typeof onProgress === 'function') {
213
214
  onProgress(`⚠️ *Candidate ${i + 1}/${total} (${label}) error: ${errMsg}*\n`)
214
215
  }
package/lib/moa-runner.js CHANGED
@@ -1,9 +1,10 @@
1
+ import { logger } from './logger.js'
1
2
  import path from 'node:path'
2
3
  import crypto from 'node:crypto'
3
4
  import { bestEffort } from './best-effort.js'
4
5
  import { extractFileBlocks, collectProjectContext, formatProjectContext, isRefinementTask, writeCandidateWorkspace, promoteCandidateWorkspace, cleanMoaWorkspaces, verifyFileSyntax } from './file-workspace.js'
5
6
  import { estimateTokenCost, summarizeMoAUsage } from './pricing.js'
6
- import { recordMoaRun, candidatesForHistory } from './history.js'
7
+ import { recordMoaRunAsync, candidatesForHistory } from './history.js'
7
8
  import { createPromotedPreview } from './live-canvas.js'
8
9
  import { slotLabel, cleanAdvisoryMessages, isBroadPromptRequiringQuestions, buildQuestionSynthesisPrompt, buildCuratorSynthesisPrompt, buildSynthesisPrompt, buildPeerCritiquePrompt, ROLE_PERSONA_PROMPTS, SYSTEM_ROLE_PROPOSER, ANTIPATTERNS_RUBRIC } from './moa-prompts.js'
9
10
  import { parseWinnerIndex, parseRecommendedAssembler, parseMoACommand, stripOrSummarizeCode, formatMoAResponse } from './moa-parser.js'
@@ -227,7 +228,7 @@ export async function runMoAPipeline({
227
228
  }
228
229
  qUsage = estimateTokenCost(primaryJudge, qFallbackUsage, prices)
229
230
  } catch (err) {
230
- console.warn('[dsh-moa] Questionnaire synthesis failed, proceeding with fallback questions:', err)
231
+ logger.warn('[dsh-moa] Questionnaire synthesis failed, proceeding with fallback questions:', err)
231
232
  questionsContent = `### Clarification of Requirements: "${userPrompt}"\n\n1. **Architecture & Scope**: Single-file deliverable or multi-module project structure?\n2. **Design & Style**: Minimalist, dark mode, or clean neutral theme?\n3. **Functional Priorities**: Core MVP or comprehensive extended implementation?\n\n*Reply with your preferences (e.g. "1, 2") or proceed with defaults.*`
232
233
  }
233
234
 
@@ -237,7 +238,7 @@ export async function runMoAPipeline({
237
238
  const durationMs = Date.now() - startTime
238
239
 
239
240
  try {
240
- recordMoaRun({
241
+ await recordMoaRunAsync({
241
242
  prompt: userPrompt,
242
243
  preset: preset?.name || 'default',
243
244
  isRefinement,
@@ -251,7 +252,7 @@ export async function runMoAPipeline({
251
252
  durationMs,
252
253
  }, historyFilePath || undefined)
253
254
  } catch (histErr) {
254
- console.warn('[dsh-moa] Failed to record questionnaire run in history:', histErr?.message || histErr)
255
+ logger.warn('[dsh-moa] Failed to record questionnaire run in history:', histErr?.message || histErr)
255
256
  }
256
257
 
257
258
  return {
@@ -293,7 +294,7 @@ export async function runMoAPipeline({
293
294
  const durationMs = Date.now() - startTime
294
295
 
295
296
  try {
296
- recordMoaRun({
297
+ await recordMoaRunAsync({
297
298
  prompt: userPrompt,
298
299
  preset: preset?.name || 'default',
299
300
  isRefinement,
@@ -308,7 +309,7 @@ export async function runMoAPipeline({
308
309
  durationMs,
309
310
  }, historyFilePath || undefined)
310
311
  } catch (histErr) {
311
- console.warn('[dsh-moa] Failed to record fast synthesis run in history:', histErr?.message || histErr)
312
+ logger.warn('[dsh-moa] Failed to record fast synthesis run in history:', histErr?.message || histErr)
312
313
  }
313
314
 
314
315
  return {
@@ -376,7 +377,7 @@ export async function runMoAPipeline({
376
377
  cand.costUsd = Number(((cand.costUsd || 0) + extraCost.costUsd).toFixed(5))
377
378
  }
378
379
  } catch (r2CostErr) {
379
- console.warn('[dsh-moa] Failed to estimate Round 2 cost:', r2CostErr?.message || r2CostErr)
380
+ logger.warn('[dsh-moa] Failed to estimate Round 2 cost:', r2CostErr?.message || r2CostErr)
380
381
  }
381
382
  })
382
383
  await Promise.allSettled(r2Promises)
@@ -460,7 +461,7 @@ export async function runMoAPipeline({
460
461
  break
461
462
  } catch (err) {
462
463
  lastJudgeError = err
463
- console.warn(`[dsh-moa] Judge ${currentLabel} failed:`, err)
464
+ logger.warn(`[dsh-moa] Judge ${currentLabel} failed:`, err)
464
465
  if (typeof onProgress === 'function') {
465
466
  const nextJudge = judgesChain[jIdx + 1]
466
467
  const nextHint = nextJudge ? ` Trying fallback ${slotLabel(nextJudge)}...` : ''
@@ -529,7 +530,7 @@ export async function runMoAPipeline({
529
530
  }
530
531
 
531
532
  try {
532
- recordMoaRun({
533
+ await recordMoaRunAsync({
533
534
  id: runId,
534
535
  prompt: userPrompt,
535
536
  preset: preset?.name || 'default',
@@ -546,7 +547,7 @@ export async function runMoAPipeline({
546
547
  durationMs,
547
548
  }, historyFilePath || undefined)
548
549
  } catch (histErr) {
549
- console.warn('[dsh-moa] Failed to record run in history:', histErr)
550
+ logger.warn('[dsh-moa] Failed to record run in history:', histErr)
550
551
  }
551
552
 
552
553
  return {
@@ -4,6 +4,7 @@ import fsSync from 'node:fs'
4
4
  import path from 'node:path'
5
5
  import { spawn } from 'node:child_process'
6
6
  import { assertPathContained } from './file-workspace.js'
7
+ import { bestEffort } from './best-effort.js'
7
8
 
8
9
  /**
9
10
  * Resolves the directory for candidate sandbox.
@@ -179,9 +180,9 @@ export async function runCandidateTestGate({
179
180
  }
180
181
  } finally {
181
182
  if (stageDir) {
182
- try {
183
- await fs.rm(stageDir, { recursive: true, force: true })
184
- } catch {}
183
+ await bestEffort('moa-test-gate cleanup stageDir', () =>
184
+ fs.rm(stageDir, { recursive: true, force: true })
185
+ )
185
186
  }
186
187
  }
187
188
  }
package/lib/routes.js CHANGED
@@ -46,7 +46,11 @@ ctx.effect(() => {
46
46
  return ctx.webServer.register({
47
47
  kind: 'exact',
48
48
  path: '/dsh-moa/status',
49
- handler: (_req, res) => {
49
+ handler: (req, res) => {
50
+ if (req.method !== 'GET') {
51
+ writeJson(res, 405, { ok: false, error: 'GET required' })
52
+ return
53
+ }
50
54
  try {
51
55
  const cfg = live()
52
56
  writeJson(res, 200, {
@@ -67,7 +71,11 @@ ctx.effect(() => {
67
71
  return ctx.webServer.register({
68
72
  kind: 'exact',
69
73
  path: '/dsh-moa/models',
70
- handler: async (_req, res) => {
74
+ handler: async (req, res) => {
75
+ if (req.method !== 'GET') {
76
+ writeJson(res, 405, { ok: false, error: 'GET required' })
77
+ return
78
+ }
71
79
  if (_modelsCache.models.length > 0 && Date.now() - _modelsCache.timestamp < MODELS_CACHE_TTL) {
72
80
  writeJson(res, 200, { ok: true, models: _modelsCache.models, cached: true })
73
81
  return
@@ -94,7 +102,7 @@ ctx.effect(() => {
94
102
  }
95
103
  }
96
104
  } catch (e) {
97
- console.warn('[dsh-moa] ctx.llm.listAvailableModels error:', e)
105
+ ctx.logger?.warn?.('[dsh-moa] ctx.llm.listAvailableModels error:', e)
98
106
  }
99
107
 
100
108
  try {
@@ -119,7 +127,7 @@ ctx.effect(() => {
119
127
  await Promise.allSettled(queries)
120
128
  }
121
129
  } catch (e) {
122
- console.warn('[dsh-moa] ctx.llm.listConfigurableProviders error:', e)
130
+ ctx.logger?.warn?.('[dsh-moa] ctx.llm.listConfigurableProviders error:', e)
123
131
  }
124
132
  }
125
133
 
@@ -156,7 +164,7 @@ ctx.effect(() => {
156
164
  }
157
165
  }
158
166
  } catch (e) {
159
- console.warn('[dsh-moa] Error reading settings:', e)
167
+ ctx.logger?.warn?.('[dsh-moa] Error reading settings:', e)
160
168
  }
161
169
 
162
170
  _modelsCache = { timestamp: Date.now(), models: result }
@@ -241,8 +249,10 @@ ctx.effect(() => {
241
249
  }
242
250
  try {
243
251
  const u = new URL(req.url, 'http://localhost')
244
- const limit = parseInt(u.searchParams.get('limit') || '20', 10)
245
- const offset = parseInt(u.searchParams.get('offset') || '0', 10)
252
+ const rawLimit = parseInt(u.searchParams.get('limit') || '20', 10)
253
+ const rawOffset = parseInt(u.searchParams.get('offset') || '0', 10)
254
+ const limit = Math.max(1, Math.min(100, Number.isFinite(rawLimit) ? rawLimit : 20))
255
+ const offset = Math.max(0, Number.isFinite(rawOffset) ? rawOffset : 0)
246
256
  const data = getMoaHistory(limit, offset)
247
257
  writeJson(res, 200, { ok: true, ...data })
248
258
  } catch (err) {
@@ -470,18 +480,26 @@ ctx.effect(() => {
470
480
  writeJson(res, 405, { ok: false, error: 'POST required' })
471
481
  return
472
482
  }
483
+ if (!isSafeWriteRequest(req)) {
484
+ writeJson(res, 403, { ok: false, error: 'Forbidden: cross-site request rejected' })
485
+ return
486
+ }
473
487
  try {
488
+ const cfg = live()
489
+ if (cfg.enabled === false) {
490
+ writeJson(res, 400, { ok: false, error: 'MoA is disabled in settings' })
491
+ return
492
+ }
474
493
  const raw = await readBody(req)
475
494
  const body = JSON.parse(raw || '{}')
476
495
  const prompt = body?.prompt || ''
477
- const cfg = live()
478
496
  const result = await resolvePresetForPrompt({
479
497
  prompt,
480
498
  presets: cfg.presets || [],
481
499
  defaultPreset: cfg.default_preset || 'default',
482
500
  routingModel: cfg.smart_routing_model,
483
501
  callLlm,
484
- enabled: true,
502
+ enabled: cfg.enabled !== false,
485
503
  })
486
504
  writeJson(res, 200, { ok: true, ...result })
487
505
  } catch (err) {
@@ -491,6 +509,9 @@ ctx.effect(() => {
491
509
  })
492
510
  }, 'dsh-moa: route preset endpoint')
493
511
 
512
+ let _lastRunTimestamp = 0
513
+ const RUN_MIN_INTERVAL_MS = 1000
514
+
494
515
  // Route: /dsh-moa/run
495
516
  ctx.effect(() => {
496
517
  return ctx.webServer.register({
@@ -505,6 +526,12 @@ ctx.effect(() => {
505
526
  writeJson(res, 403, { ok: false, error: 'Forbidden: cross-site request rejected' })
506
527
  return
507
528
  }
529
+ const now = Date.now()
530
+ if (now - _lastRunTimestamp < RUN_MIN_INTERVAL_MS) {
531
+ writeJson(res, 429, { ok: false, error: 'Rate limit exceeded: please wait before starting another run' })
532
+ return
533
+ }
534
+ _lastRunTimestamp = now
508
535
 
509
536
  try {
510
537
  const raw = await readBody(req)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-moa",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
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",