@goodandready/dsh-moa 0.2.15 → 0.2.16
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/README.md +7 -0
- package/lib/best-effort.js +38 -0
- package/lib/client.js +7 -4
- package/lib/file-workspace.js +48 -34
- package/lib/history.js +4 -4
- package/lib/index.js +17 -382
- package/lib/moa-candidates.js +225 -0
- package/lib/moa-runner.js +13 -222
- package/lib/routes.js +408 -0
- package/lib/updater.js +280 -0
- package/package.json +1 -2
- package/docs/README.ru.md +0 -287
- package/docs/README.zh.md +0 -243
- package/docs/design/DESIGN.md +0 -79
- package/docs/plans/47-client-js-split-plan.md +0 -57
- package/docs/plans/55-resilience-plan.md +0 -33
- package/docs/plans/59-power-pack-plan.md +0 -73
- package/docs/plans/63-stability-polish-plan.md +0 -15
package/README.md
CHANGED
|
@@ -282,6 +282,13 @@ npm test
|
|
|
282
282
|
|
|
283
283
|
---
|
|
284
284
|
|
|
285
|
+
|
|
286
|
+
## 🛠️ Internal Tooling & Development
|
|
287
|
+
|
|
288
|
+
For local verification and package integrity validation:
|
|
289
|
+
- `npm test`: runs the full test suite (95 tests)
|
|
290
|
+
- `./deploy.sh`: local infrastructure validation script (verifies package size, identity parity in package.json/cordis.patch.yml/client.js, and tests). Excluded from the published npm package.
|
|
291
|
+
|
|
285
292
|
## 📄 License
|
|
286
293
|
|
|
287
294
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a non-critical side effect; never throw.
|
|
3
|
+
* Logs at debug/warn when a logger is provided or via console.debug.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} label - Context identifier for telemetry / diagnostics
|
|
6
|
+
* @param {Function} fn - Operation to execute
|
|
7
|
+
* @param {object} [logger] - Optional logger instance
|
|
8
|
+
*/
|
|
9
|
+
export function bestEffort(label, fn, logger) {
|
|
10
|
+
const log = (err) => {
|
|
11
|
+
try {
|
|
12
|
+
if (logger && typeof logger.debug === 'function') {
|
|
13
|
+
logger.debug(`[dsh-moa:bestEffort] ${label}:`, err?.message || err)
|
|
14
|
+
} else if (typeof console !== 'undefined' && typeof console.debug === 'function') {
|
|
15
|
+
console.debug(`[dsh-moa:bestEffort] ${label}:`, err?.message || err)
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
// Logger itself must never crash the process
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const result = fn()
|
|
24
|
+
if (result && typeof result.then === 'function') {
|
|
25
|
+
return result.then(
|
|
26
|
+
(value) => value,
|
|
27
|
+
(err) => {
|
|
28
|
+
log(err)
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
return result
|
|
34
|
+
} catch (err) {
|
|
35
|
+
log(err)
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -1444,7 +1444,7 @@ window.__ModuleLoader__.load({
|
|
|
1444
1444
|
const snapshot = React.useSyncExternalStore
|
|
1445
1445
|
? React.useSyncExternalStore(
|
|
1446
1446
|
React.useMemo(() => (cb) => (scope ? scope.subscribe(cb) : () => {}), [scope]),
|
|
1447
|
-
React.useCallback(() => (scope ? scope.getSnapshot() : { status: '
|
|
1447
|
+
React.useCallback(() => (scope ? scope.getSnapshot() : { status: 'unavailable' }), [scope]),
|
|
1448
1448
|
React.useCallback(() => ({ status: 'loading' }), [])
|
|
1449
1449
|
)
|
|
1450
1450
|
: null
|
|
@@ -1461,8 +1461,11 @@ window.__ModuleLoader__.load({
|
|
|
1461
1461
|
const [leaderboardPresetFilter, setLeaderboardPresetFilter] = React.useState('all')
|
|
1462
1462
|
|
|
1463
1463
|
React.useEffect(() => {
|
|
1464
|
-
if (!scope || !snapshot)
|
|
1465
|
-
|
|
1464
|
+
if (!scope || !snapshot) {
|
|
1465
|
+
setStatus('unavailable')
|
|
1466
|
+
return
|
|
1467
|
+
}
|
|
1468
|
+
const snapStatus = snapshot.status || 'unavailable'
|
|
1466
1469
|
if (snapStatus === 'ready') {
|
|
1467
1470
|
const val = snapshot.value || {}
|
|
1468
1471
|
if (Array.isArray(val.presets)) setPresets(val.presets)
|
|
@@ -1730,7 +1733,7 @@ window.__ModuleLoader__.load({
|
|
|
1730
1733
|
return () => {
|
|
1731
1734
|
for (const off of undo) {
|
|
1732
1735
|
if (typeof off === 'function') {
|
|
1733
|
-
try { off() } catch {}
|
|
1736
|
+
try { off() } catch (offErr) { /* unsubscribe best-effort */ }
|
|
1734
1737
|
}
|
|
1735
1738
|
}
|
|
1736
1739
|
}
|
package/lib/file-workspace.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bestEffort } from './best-effort.js'
|
|
1
2
|
import vm from 'node:vm'
|
|
2
3
|
import fs from 'node:fs/promises'
|
|
3
4
|
import path from 'node:path'
|
|
@@ -114,43 +115,55 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
|
|
|
114
115
|
const collected = []
|
|
115
116
|
let totalChars = 0
|
|
116
117
|
|
|
118
|
+
let skippedFiles = 0
|
|
119
|
+
const skippedList = []
|
|
120
|
+
|
|
117
121
|
async function scan(currentDir, relPrefix = '') {
|
|
118
122
|
if (totalChars >= maxCharBudget) return
|
|
123
|
+
let list
|
|
119
124
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
list = await fs.readdir(currentDir, { withFileTypes: true })
|
|
126
|
+
} catch (readDirErr) {
|
|
127
|
+
skippedFiles++
|
|
128
|
+
skippedList.push(relPrefix || '.')
|
|
129
|
+
return
|
|
130
|
+
}
|
|
126
131
|
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
for (const item of list) {
|
|
133
|
+
if (totalChars >= maxCharBudget) break
|
|
134
|
+
// Skip all dotfiles, including .env and .env.* variants: their
|
|
135
|
+
// contents must never be shipped to third-party model providers.
|
|
136
|
+
if (item.name.startsWith('.')) continue
|
|
129
137
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name
|
|
139
|
+
const full = path.join(currentDir, item.name)
|
|
140
|
+
|
|
141
|
+
if (item.isDirectory()) {
|
|
142
|
+
if (!ignoreDirs.has(item.name)) {
|
|
143
|
+
await scan(full, rel)
|
|
144
|
+
}
|
|
145
|
+
} else if (item.isFile()) {
|
|
146
|
+
const ext = path.extname(item.name).toLowerCase()
|
|
147
|
+
if (codeExts.has(ext)) {
|
|
148
|
+
try {
|
|
149
|
+
const stat = await fs.stat(full)
|
|
150
|
+
if (stat.size > 100000) continue // skip files > 100kb
|
|
151
|
+
const content = await fs.readFile(full, 'utf8')
|
|
152
|
+
const budgetRemaining = maxCharBudget - totalChars
|
|
153
|
+
const truncated = content.length > budgetRemaining ? content.slice(0, budgetRemaining) + '\n... [truncated]' : content
|
|
154
|
+
totalChars += truncated.length
|
|
155
|
+
collected.push({ relativePath: rel, content: truncated, size: stat.size })
|
|
156
|
+
} catch (fileErr) {
|
|
157
|
+
skippedFiles++
|
|
158
|
+
skippedList.push(rel)
|
|
146
159
|
}
|
|
147
160
|
}
|
|
148
161
|
}
|
|
149
|
-
}
|
|
162
|
+
}
|
|
150
163
|
}
|
|
151
164
|
|
|
152
165
|
await scan(baseDir)
|
|
153
|
-
return { files: collected, totalChars }
|
|
166
|
+
return { files: collected, totalChars, skippedFiles, skippedList }
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
/**
|
|
@@ -224,7 +237,7 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex, options =
|
|
|
224
237
|
const entries = await getFilesRecursively(winningDir)
|
|
225
238
|
if (entries.length === 0) {
|
|
226
239
|
if (!options.keepMoa) {
|
|
227
|
-
|
|
240
|
+
await bestEffort('clean moaRoot on empty candidate', () => fs.rm(moaRoot, { recursive: true, force: true }))
|
|
228
241
|
}
|
|
229
242
|
return []
|
|
230
243
|
}
|
|
@@ -247,7 +260,7 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex, options =
|
|
|
247
260
|
return promoted
|
|
248
261
|
} catch (err) {
|
|
249
262
|
if (!options.keepMoa) {
|
|
250
|
-
|
|
263
|
+
await bestEffort('clean moaRoot on promote error', () => fs.rm(moaRoot, { recursive: true, force: true }))
|
|
251
264
|
}
|
|
252
265
|
return []
|
|
253
266
|
}
|
|
@@ -259,9 +272,7 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex, options =
|
|
|
259
272
|
export async function cleanMoaWorkspaces(baseDir) {
|
|
260
273
|
if (!baseDir) return
|
|
261
274
|
const moaRoot = path.join(baseDir, '.moa')
|
|
262
|
-
|
|
263
|
-
await fs.rm(moaRoot, { recursive: true, force: true })
|
|
264
|
-
} catch {}
|
|
275
|
+
await bestEffort('cleanMoaWorkspaces', () => fs.rm(moaRoot, { recursive: true, force: true }))
|
|
265
276
|
}
|
|
266
277
|
|
|
267
278
|
async function getFilesRecursively(dir) {
|
|
@@ -269,13 +280,14 @@ async function getFilesRecursively(dir) {
|
|
|
269
280
|
try {
|
|
270
281
|
const stat = await fs.stat(dir)
|
|
271
282
|
if (!stat.isDirectory()) return []
|
|
272
|
-
} catch {
|
|
283
|
+
} catch (statErr) {
|
|
273
284
|
return []
|
|
274
285
|
}
|
|
275
286
|
|
|
276
287
|
async function scan(current, relPrefix = '') {
|
|
288
|
+
let list
|
|
277
289
|
try {
|
|
278
|
-
|
|
290
|
+
list = await fs.readdir(current, { withFileTypes: true })
|
|
279
291
|
for (const item of list) {
|
|
280
292
|
const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name
|
|
281
293
|
const full = path.join(current, item.name)
|
|
@@ -285,7 +297,9 @@ async function getFilesRecursively(dir) {
|
|
|
285
297
|
results.push(rel)
|
|
286
298
|
}
|
|
287
299
|
}
|
|
288
|
-
} catch {
|
|
300
|
+
} catch (readErr) {
|
|
301
|
+
return
|
|
302
|
+
}
|
|
289
303
|
}
|
|
290
304
|
await scan(dir)
|
|
291
305
|
return results
|
package/lib/history.js
CHANGED
|
@@ -15,7 +15,7 @@ const DEFAULT_HISTORY_FILE = path.join(DEFAULT_HISTORY_DIR, 'moa-history.jsonl')
|
|
|
15
15
|
/** Max history file size before rotation (10 MB) */
|
|
16
16
|
export const MAX_HISTORY_BYTES = 10 * 1024 * 1024
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
function getHistoryFilePath(customDir) {
|
|
19
19
|
if (customDir) {
|
|
20
20
|
return path.join(customDir, '.moa-history.jsonl')
|
|
21
21
|
}
|
|
@@ -33,7 +33,7 @@ export function rotateHistoryFileIfNeeded(filePath = DEFAULT_HISTORY_FILE, maxBy
|
|
|
33
33
|
if (stat.size >= maxBytes) {
|
|
34
34
|
const backupPath = `${filePath}.1`
|
|
35
35
|
if (fs.existsSync(backupPath)) {
|
|
36
|
-
try { fs.unlinkSync(backupPath) } catch {}
|
|
36
|
+
try { fs.unlinkSync(backupPath) } catch (unlinkErr) { /* backup unlink best-effort */ }
|
|
37
37
|
}
|
|
38
38
|
fs.renameSync(filePath, backupPath)
|
|
39
39
|
return true
|
|
@@ -241,7 +241,7 @@ export function getMoaRunById(runId, filePath = DEFAULT_HISTORY_FILE) {
|
|
|
241
241
|
try {
|
|
242
242
|
const item = JSON.parse(lines[i])
|
|
243
243
|
if (item && item.id === runId) return item
|
|
244
|
-
} catch {}
|
|
244
|
+
} catch (parseErr) { /* corrupt line skipped */ }
|
|
245
245
|
}
|
|
246
246
|
return null
|
|
247
247
|
} catch {
|
|
@@ -270,7 +270,7 @@ export function exportMoaHistory(filePath = DEFAULT_HISTORY_FILE, options = {})
|
|
|
270
270
|
if (!item) continue
|
|
271
271
|
if (presetFilter && presetFilter !== 'all' && item.preset !== presetFilter) continue
|
|
272
272
|
records.push(item)
|
|
273
|
-
} catch {}
|
|
273
|
+
} catch (parseErr) { /* corrupt line skipped */ }
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
if (format === 'csv') {
|
package/lib/index.js
CHANGED
|
@@ -19,17 +19,12 @@ import {
|
|
|
19
19
|
streamMoATurn,
|
|
20
20
|
} from './moa-runner.js'
|
|
21
21
|
|
|
22
|
-
import {
|
|
23
|
-
getMoaHistory,
|
|
24
|
-
getMoaLeaderboard,
|
|
25
|
-
getMoaRunById,
|
|
26
|
-
exportMoaHistory,
|
|
27
|
-
} from './history.js'
|
|
28
|
-
import { promoteCandidateWorkspace } from './file-workspace.js'
|
|
22
|
+
import { registerMoaRoutes } from './routes.js'
|
|
29
23
|
|
|
30
24
|
import { createLiveCanvasClient } from './live-canvas.js'
|
|
25
|
+
import { registerPluginUpdater, isSafeWriteRequest } from './updater.js'
|
|
31
26
|
|
|
32
|
-
export const name = 'dsh-moa'
|
|
27
|
+
export const name = '@goodandready/dsh-moa'
|
|
33
28
|
export const inject = ['webServer', 'llm', 'settings', 'sessions', 'tools']
|
|
34
29
|
|
|
35
30
|
export const NS = 'dsh-moa'
|
|
@@ -117,28 +112,6 @@ export const Config = z.object({
|
|
|
117
112
|
]),
|
|
118
113
|
})
|
|
119
114
|
|
|
120
|
-
function writeJson(res, status, body) {
|
|
121
|
-
res.writeHead(status, {
|
|
122
|
-
'Content-Type': 'application/json; charset=utf-8',
|
|
123
|
-
'Cache-Control': 'no-store',
|
|
124
|
-
})
|
|
125
|
-
res.end(JSON.stringify(body))
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function readBody(req) {
|
|
129
|
-
return new Promise((resolve, reject) => {
|
|
130
|
-
let acc = ''
|
|
131
|
-
req.on('data', (chunk) => {
|
|
132
|
-
acc += chunk
|
|
133
|
-
if (acc.length > 5 * 1024 * 1024) {
|
|
134
|
-
req.destroy()
|
|
135
|
-
reject(new Error('Payload Too Large'))
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
req.on('end', () => resolve(acc))
|
|
139
|
-
req.on('error', reject)
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
115
|
|
|
143
116
|
export function apply(ctx, config) {
|
|
144
117
|
refreshCatalogInBackground()
|
|
@@ -238,361 +211,23 @@ export function apply(ctx, config) {
|
|
|
238
211
|
const liveCanvas = createLiveCanvasClient({ getPort: () => ctx.webServer?.port })
|
|
239
212
|
|
|
240
213
|
// Route: /dsh-moa/status
|
|
214
|
+
// Register one-click updater endpoint
|
|
241
215
|
ctx.effect(() => {
|
|
242
|
-
return ctx
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const cfg = live()
|
|
247
|
-
writeJson(res, 200, {
|
|
248
|
-
ok: true,
|
|
249
|
-
enabled: cfg.enabled !== false,
|
|
250
|
-
defaultPreset: cfg.default_preset || 'default',
|
|
251
|
-
presetsCount: (cfg.presets || []).length,
|
|
252
|
-
})
|
|
253
|
-
},
|
|
216
|
+
return registerPluginUpdater(ctx, {
|
|
217
|
+
endpoint: '/api/dsh-moa/update',
|
|
218
|
+
packageName: '@goodandready/dsh-moa',
|
|
219
|
+
manifestUrl: new URL('../package.json', import.meta.url),
|
|
254
220
|
})
|
|
255
|
-
}, 'dsh-moa:
|
|
256
|
-
|
|
257
|
-
// Route:
|
|
258
|
-
ctx
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const seen = new Set()
|
|
265
|
-
|
|
266
|
-
const add = (p, m) => {
|
|
267
|
-
if (!p || !m) return
|
|
268
|
-
const key = `${p}:${m}`
|
|
269
|
-
if (!seen.has(key)) {
|
|
270
|
-
seen.add(key)
|
|
271
|
-
result.push({ provider: p, model: m, label: key })
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (ctx.llm) {
|
|
276
|
-
try {
|
|
277
|
-
if (typeof ctx.llm.listAvailableModels === 'function') {
|
|
278
|
-
const list = await ctx.llm.listAvailableModels()
|
|
279
|
-
for (const item of (list || [])) {
|
|
280
|
-
if (item?.provider && item?.id) add(item.provider, item.id)
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
} catch (e) {
|
|
284
|
-
console.warn('[dsh-moa] ctx.llm.listAvailableModels error:', e)
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
if (typeof ctx.llm.listConfigurableProviders === 'function') {
|
|
289
|
-
const cProvs = ctx.llm.listConfigurableProviders() || []
|
|
290
|
-
for (const cp of cProvs) {
|
|
291
|
-
const cpId = cp.provider || cp.id
|
|
292
|
-
if (cpId && typeof ctx.llm.listModels === 'function') {
|
|
293
|
-
try {
|
|
294
|
-
const mList = await ctx.llm.listModels(cpId)
|
|
295
|
-
for (const m of (mList || [])) {
|
|
296
|
-
const mId = typeof m === 'string' ? m : (m.id || m.model || m.name)
|
|
297
|
-
if (mId) add(cpId, mId)
|
|
298
|
-
}
|
|
299
|
-
} catch {}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
} catch (e) {
|
|
304
|
-
console.warn('[dsh-moa] ctx.llm.listConfigurableProviders error:', e)
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Query settings for configured providers
|
|
309
|
-
try {
|
|
310
|
-
const s = ctx.get ? ctx.get('settings') : ctx.settings
|
|
311
|
-
const getSetting = (k) => {
|
|
312
|
-
try { return s?.get?.(k) } catch { return undefined }
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const def = getSetting('agent-default-model')
|
|
316
|
-
if (def?.provider && def?.model) {
|
|
317
|
-
add(def.provider, def.model)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const piAi = getSetting('llm-pi-ai')
|
|
321
|
-
for (const [pName, pData] of Object.entries(piAi?.providers || {})) {
|
|
322
|
-
for (const m of (pData?.models || [])) {
|
|
323
|
-
const mId = typeof m === 'string' ? m : (m?.id || m?.model)
|
|
324
|
-
if (mId) add(pName, mId)
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const sync = getSetting('dsh-model-sync')
|
|
329
|
-
for (const [syncProv, syncModels] of Object.entries(sync?.modelCatalogs || {})) {
|
|
330
|
-
for (const m of (syncModels || [])) {
|
|
331
|
-
const mId = typeof m === 'string' ? m : (m?.id || m?.model)
|
|
332
|
-
if (mId) add(syncProv, mId)
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
} catch (e) {
|
|
336
|
-
console.warn('[dsh-moa] Error reading settings:', e)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
writeJson(res, 200, { ok: true, models: result })
|
|
340
|
-
},
|
|
341
|
-
})
|
|
342
|
-
}, 'dsh-moa: models route')
|
|
343
|
-
|
|
344
|
-
// Route: /dsh-moa/presets
|
|
345
|
-
ctx.effect(() => {
|
|
346
|
-
return ctx.webServer.register({
|
|
347
|
-
kind: 'exact',
|
|
348
|
-
path: '/dsh-moa/presets',
|
|
349
|
-
handler: async (req, res) => {
|
|
350
|
-
const cfg = live()
|
|
351
|
-
if (req.method === 'GET') {
|
|
352
|
-
writeJson(res, 200, {
|
|
353
|
-
ok: true,
|
|
354
|
-
defaultPreset: cfg.default_preset || 'default',
|
|
355
|
-
presets: cfg.presets || [],
|
|
356
|
-
})
|
|
357
|
-
return
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (req.method === 'POST' || req.method === 'PUT') {
|
|
361
|
-
try {
|
|
362
|
-
const raw = await readBody(req)
|
|
363
|
-
const payload = JSON.parse(raw)
|
|
364
|
-
const newConfig = {
|
|
365
|
-
enabled: typeof payload.enabled === 'boolean' ? payload.enabled : cfg.enabled !== false,
|
|
366
|
-
default_preset: payload.default_preset || cfg.default_preset || 'default',
|
|
367
|
-
presets: Array.isArray(payload.presets) ? payload.presets : (cfg.presets || []),
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Validate the merged payload through the plugin schema before it
|
|
371
|
-
// reaches the settings storage: malformed payloads must be
|
|
372
|
-
// rejected with 400 instead of being persisted.
|
|
373
|
-
let validated
|
|
374
|
-
try {
|
|
375
|
-
validated = Config(structuredClone(newConfig))
|
|
376
|
-
} catch (verr) {
|
|
377
|
-
writeJson(res, 400, { ok: false, error: 'Invalid presets payload: ' + (verr?.message || String(verr)) })
|
|
378
|
-
return
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Persist via Cordis Settings API
|
|
382
|
-
if (settingsScope && typeof settingsScope.replace === 'function') {
|
|
383
|
-
await settingsScope.replace(validated)
|
|
384
|
-
} else {
|
|
385
|
-
// In-memory fallback if settings service is unavailable
|
|
386
|
-
getConfig = () => validated
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
writeJson(res, 200, {
|
|
390
|
-
ok: true,
|
|
391
|
-
enabled: validated.enabled !== false,
|
|
392
|
-
defaultPreset: validated.default_preset,
|
|
393
|
-
presets: validated.presets,
|
|
394
|
-
})
|
|
395
|
-
} catch (err) {
|
|
396
|
-
writeJson(res, 400, { ok: false, error: err?.message || String(err) })
|
|
397
|
-
}
|
|
398
|
-
return
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
writeJson(res, 405, { ok: false, error: 'Method Not Allowed' })
|
|
402
|
-
},
|
|
403
|
-
})
|
|
404
|
-
}, 'dsh-moa: presets route')
|
|
405
|
-
|
|
406
|
-
// Route: /dsh-moa/history (#9)
|
|
407
|
-
ctx.effect(() => {
|
|
408
|
-
return ctx.webServer.register({
|
|
409
|
-
kind: 'exact',
|
|
410
|
-
path: '/dsh-moa/history',
|
|
411
|
-
handler: async (req, res) => {
|
|
412
|
-
if (req.method !== 'GET') {
|
|
413
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
414
|
-
return
|
|
415
|
-
}
|
|
416
|
-
try {
|
|
417
|
-
const u = new URL(req.url, 'http://localhost')
|
|
418
|
-
const limit = parseInt(u.searchParams.get('limit') || '20', 10)
|
|
419
|
-
const offset = parseInt(u.searchParams.get('offset') || '0', 10)
|
|
420
|
-
const data = getMoaHistory(limit, offset)
|
|
421
|
-
writeJson(res, 200, { ok: true, ...data })
|
|
422
|
-
} catch (err) {
|
|
423
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
424
|
-
}
|
|
425
|
-
},
|
|
426
|
-
})
|
|
427
|
-
}, 'dsh-moa: history route')
|
|
428
|
-
|
|
429
|
-
// Route: /dsh-moa/leaderboard
|
|
430
|
-
ctx.effect(() => {
|
|
431
|
-
return ctx.webServer.register({
|
|
432
|
-
kind: 'exact',
|
|
433
|
-
path: '/dsh-moa/leaderboard',
|
|
434
|
-
handler: async (req, res) => {
|
|
435
|
-
if (req.method !== 'GET') {
|
|
436
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
437
|
-
return
|
|
438
|
-
}
|
|
439
|
-
try {
|
|
440
|
-
const url = new URL(req.url || '', 'http://127.0.0.1')
|
|
441
|
-
const preset = url.searchParams.get('preset') || null
|
|
442
|
-
const format = url.searchParams.get('format') || 'json'
|
|
443
|
-
|
|
444
|
-
if (format === 'csv') {
|
|
445
|
-
const csv = exportMoaHistory(undefined, { format: 'csv', preset })
|
|
446
|
-
res.writeHead(200, {
|
|
447
|
-
'Content-Type': 'text/csv; charset=utf-8',
|
|
448
|
-
'Content-Disposition': 'attachment; filename="moa-leaderboard.csv"',
|
|
449
|
-
})
|
|
450
|
-
res.end(csv)
|
|
451
|
-
return
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const data = getMoaLeaderboard(undefined, { preset })
|
|
455
|
-
writeJson(res, 200, { ok: true, ...data })
|
|
456
|
-
} catch (err) {
|
|
457
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
458
|
-
}
|
|
459
|
-
},
|
|
460
|
-
})
|
|
461
|
-
}, 'dsh-moa: leaderboard route')
|
|
462
|
-
|
|
463
|
-
// Route: /dsh-moa/promote (Candidate Override Action)
|
|
464
|
-
ctx.effect(() => {
|
|
465
|
-
return ctx.webServer.register({
|
|
466
|
-
kind: 'exact',
|
|
467
|
-
path: '/dsh-moa/promote',
|
|
468
|
-
handler: async (req, res) => {
|
|
469
|
-
if (req.method !== 'POST') {
|
|
470
|
-
writeJson(res, 405, { ok: false, error: 'POST required' })
|
|
471
|
-
return
|
|
472
|
-
}
|
|
473
|
-
try {
|
|
474
|
-
const raw = await readBody(req)
|
|
475
|
-
const body = JSON.parse(raw || '{}')
|
|
476
|
-
const candidateIndex = parseInt(body?.candidateIndex, 10)
|
|
477
|
-
if (isNaN(candidateIndex) || candidateIndex < 1) {
|
|
478
|
-
writeJson(res, 400, { ok: false, error: 'Valid candidateIndex required (>= 1)' })
|
|
479
|
-
return
|
|
480
|
-
}
|
|
481
|
-
const cwd = body?.cwd || process.cwd()
|
|
482
|
-
const promotedFiles = await promoteCandidateWorkspace(cwd, candidateIndex, { keepMoa: true })
|
|
483
|
-
writeJson(res, 200, { ok: true, candidateIndex, promotedFiles })
|
|
484
|
-
} catch (err) {
|
|
485
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
486
|
-
}
|
|
487
|
-
},
|
|
488
|
-
})
|
|
489
|
-
}, 'dsh-moa: promote candidate route')
|
|
490
|
-
|
|
491
|
-
// Route: /dsh-moa/history/export
|
|
492
|
-
ctx.effect(() => {
|
|
493
|
-
return ctx.webServer.register({
|
|
494
|
-
kind: 'exact',
|
|
495
|
-
path: '/dsh-moa/history/export',
|
|
496
|
-
handler: async (req, res) => {
|
|
497
|
-
if (req.method !== 'GET') {
|
|
498
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
499
|
-
return
|
|
500
|
-
}
|
|
501
|
-
try {
|
|
502
|
-
const url = new URL(req.url || '', 'http://127.0.0.1')
|
|
503
|
-
const preset = url.searchParams.get('preset') || null
|
|
504
|
-
const format = url.searchParams.get('format') || 'json'
|
|
505
|
-
const data = exportMoaHistory(undefined, { format, preset })
|
|
506
|
-
|
|
507
|
-
if (format === 'csv') {
|
|
508
|
-
res.writeHead(200, {
|
|
509
|
-
'Content-Type': 'text/csv; charset=utf-8',
|
|
510
|
-
'Content-Disposition': 'attachment; filename="moa-history.csv"',
|
|
511
|
-
})
|
|
512
|
-
res.end(data)
|
|
513
|
-
return
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
res.writeHead(200, {
|
|
517
|
-
'Content-Type': 'application/json; charset=utf-8',
|
|
518
|
-
'Content-Disposition': 'attachment; filename="moa-history.json"',
|
|
519
|
-
})
|
|
520
|
-
res.end(data)
|
|
521
|
-
} catch (err) {
|
|
522
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
523
|
-
}
|
|
524
|
-
},
|
|
525
|
-
})
|
|
526
|
-
}, 'dsh-moa: history export route')
|
|
527
|
-
|
|
528
|
-
// Route: /dsh-moa/runs/<id> (run replay by id)
|
|
529
|
-
ctx.effect(() => {
|
|
530
|
-
return ctx.webServer.register({
|
|
531
|
-
kind: 'prefix',
|
|
532
|
-
path: '/dsh-moa/runs/',
|
|
533
|
-
handler: (req, res) => {
|
|
534
|
-
if (req.method !== 'GET') {
|
|
535
|
-
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
536
|
-
return
|
|
537
|
-
}
|
|
538
|
-
const urlPath = req.url.split('?')[0]
|
|
539
|
-
const parts = urlPath.split('/').filter(Boolean)
|
|
540
|
-
const runId = parts[2] || ''
|
|
541
|
-
const run = runId ? getMoaRunById(runId) : null
|
|
542
|
-
if (!run) {
|
|
543
|
-
writeJson(res, 404, { ok: false, error: 'Run not found' })
|
|
544
|
-
return
|
|
545
|
-
}
|
|
546
|
-
writeJson(res, 200, { ok: true, run })
|
|
547
|
-
},
|
|
548
|
-
})
|
|
549
|
-
}, 'dsh-moa: run by id route')
|
|
550
|
-
|
|
551
|
-
// Route: /dsh-moa/run
|
|
552
|
-
ctx.effect(() => {
|
|
553
|
-
return ctx.webServer.register({
|
|
554
|
-
kind: 'exact',
|
|
555
|
-
path: '/dsh-moa/run',
|
|
556
|
-
handler: async (req, res) => {
|
|
557
|
-
if (req.method !== 'POST') {
|
|
558
|
-
writeJson(res, 405, { ok: false, error: 'POST required' })
|
|
559
|
-
return
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
try {
|
|
563
|
-
const raw = await readBody(req)
|
|
564
|
-
const payload = JSON.parse(raw)
|
|
565
|
-
const prompt = payload.prompt || ''
|
|
566
|
-
if (!prompt.trim()) {
|
|
567
|
-
writeJson(res, 400, { ok: false, error: 'Empty prompt' })
|
|
568
|
-
return
|
|
569
|
-
}
|
|
221
|
+
}, 'dsh-moa: plugin updater route')
|
|
222
|
+
|
|
223
|
+
// Route dispatch: req.method verified fail-closed in lib/routes.js
|
|
224
|
+
registerMoaRoutes(ctx, {
|
|
225
|
+
live,
|
|
226
|
+
callLlm,
|
|
227
|
+
liveCanvas,
|
|
228
|
+
getSettingsScope: () => settingsScope,
|
|
229
|
+
})
|
|
570
230
|
|
|
571
|
-
const cfg = live()
|
|
572
|
-
if (cfg.enabled === false) {
|
|
573
|
-
writeJson(res, 400, { ok: false, error: 'MoA is disabled in settings' })
|
|
574
|
-
return
|
|
575
|
-
}
|
|
576
|
-
const presetName = payload.preset || cfg.default_preset || 'default'
|
|
577
|
-
const preset = (cfg.presets || []).find((p) => p.name === presetName) || cfg.presets?.[0]
|
|
578
|
-
|
|
579
|
-
const result = await runMoAPipeline({
|
|
580
|
-
userPrompt: prompt,
|
|
581
|
-
messages: payload.messages || [],
|
|
582
|
-
preset,
|
|
583
|
-
callLlm,
|
|
584
|
-
cwd: payload.cwd || process.cwd(),
|
|
585
|
-
prices: cfg.prices || {},
|
|
586
|
-
liveCanvas,
|
|
587
|
-
})
|
|
588
|
-
|
|
589
|
-
writeJson(res, 200, { ok: true, ...result })
|
|
590
|
-
} catch (err) {
|
|
591
|
-
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
592
|
-
}
|
|
593
|
-
},
|
|
594
|
-
})
|
|
595
|
-
}, 'dsh-moa: run pipeline route')
|
|
596
231
|
// Session turn interceptor: route turn to real aggregator model & stream MoA synthesis
|
|
597
232
|
const moaPendingTurns = new WeakMap()
|
|
598
233
|
const moaSessionTurns = new Map()
|