@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.
- package/lib/file-workspace.js +5 -4
- package/lib/history.js +7 -6
- package/lib/index.js +3 -1
- package/lib/logger.js +40 -0
- package/lib/moa-candidates.js +3 -2
- package/lib/moa-runner.js +11 -10
- package/lib/moa-test-gate.js +4 -3
- package/lib/routes.js +36 -9
- package/package.json +1 -1
package/lib/file-workspace.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/lib/moa-candidates.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
550
|
+
logger.warn('[dsh-moa] Failed to record run in history:', histErr)
|
|
550
551
|
}
|
|
551
552
|
|
|
552
553
|
return {
|
package/lib/moa-test-gate.js
CHANGED
|
@@ -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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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: (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
245
|
-
const
|
|
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:
|
|
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)
|