@goodandready/dsh-moa 0.2.8 → 0.2.10

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.
@@ -107,7 +107,9 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
107
107
  const list = await fs.readdir(currentDir, { withFileTypes: true })
108
108
  for (const item of list) {
109
109
  if (totalChars >= maxCharBudget) break
110
- if (item.name.startsWith('.') && item.name !== '.env') continue
110
+ // Skip all dotfiles, including .env and .env.* variants: their
111
+ // contents must never be shipped to third-party model providers.
112
+ if (item.name.startsWith('.')) continue
111
113
 
112
114
  const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name
113
115
  const full = path.join(currentDir, item.name)
package/lib/history.js CHANGED
@@ -35,6 +35,7 @@ export function recordMoaRun(record, filePath = DEFAULT_HISTORY_FILE) {
35
35
  prompt: record.prompt || '',
36
36
  preset: record.preset || 'default',
37
37
  isRefinement: Boolean(record.isRefinement),
38
+ isFastMode: Boolean(record.isFastMode),
38
39
  candidates: Array.isArray(record.candidates)
39
40
  ? record.candidates.map((c) => ({
40
41
  provider: c.provider || '',
@@ -144,7 +145,9 @@ export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE) {
144
145
  st.totalTokens += inTok + outTok
145
146
  st.totalCostUsd += Number(cand.costUsd || 0)
146
147
 
147
- if (winner && (winner === key || winner === cand.model || winner.includes(cand.model))) {
148
+ // Exact match only: loose substring matching combined with empty
149
+ // candidate fields credited wins to every model in the run.
150
+ if (winner && cand.model && (winner === key || winner === cand.model)) {
148
151
  st.wins += 1
149
152
  }
150
153
  }
package/lib/index.js CHANGED
@@ -6,7 +6,7 @@ import { refreshCatalogInBackground } from './pricing.js'
6
6
  * 1. User issues /moa <prompt>
7
7
  * 2. Parallel candidate proposers write to isolated workspaces (.moa/candidate-X/)
8
8
  * 3. Aggregator evaluates and synthesizes the optimal solution
9
- * 4. Winner candidate files are promoted to the project root and previewed in Live Canvas
9
+ * 4. Winner candidate files are promoted to the project root
10
10
  * 5. Native cost tracking, history logging and leaderboard analytics
11
11
  */
12
12
 
@@ -25,6 +25,8 @@ import {
25
25
  getMoaRunById,
26
26
  } from './history.js'
27
27
 
28
+ import { createLiveCanvasClient } from './live-canvas.js'
29
+
28
30
  export const name = 'dsh-moa'
29
31
  export const inject = ['webServer', 'llm', 'settings', 'sessions', 'tools']
30
32
 
@@ -45,13 +47,17 @@ export const PresetSchema = z.object({
45
47
  name: z.string(),
46
48
  enabled: z.boolean().default(true),
47
49
  ask_clarifying_questions: z.boolean().default(true),
50
+ curator_synthesis: z.boolean().default(false),
51
+ stream_aggregator: z.boolean().default(true),
52
+ quorum_enabled: z.boolean().default(false),
53
+ grace_period_sec: z.number().default(10),
48
54
  reference_models: z.array(ModelSlotSchema).default([]),
49
55
  aggregator: ModelSlotSchema.default({ provider: 'codex', model: 'gpt-5.6-sol' }),
56
+ aggregator_fallbacks: z.array(ModelSlotSchema).default([]),
50
57
  reference_temperature: z.number().default(0.6),
51
58
  aggregator_temperature: z.number().default(0.4),
52
59
  max_tokens: z.number().default(4096),
53
60
  judge_criteria: z.string().default(''),
54
- judge_mode: z.string().default('auto'),
55
61
  })
56
62
 
57
63
  export const Config = z.object({
@@ -72,7 +78,6 @@ export const Config = z.object({
72
78
  aggregator_temperature: 0.4,
73
79
  max_tokens: 4096,
74
80
  judge_criteria: '',
75
- judge_mode: 'auto',
76
81
  },
77
82
  {
78
83
  name: 'fast',
@@ -86,7 +91,6 @@ export const Config = z.object({
86
91
  aggregator_temperature: 0.2,
87
92
  max_tokens: 4096,
88
93
  judge_criteria: '',
89
- judge_mode: 'auto',
90
94
  },
91
95
  {
92
96
  name: 'deep-reasoning',
@@ -100,8 +104,7 @@ export const Config = z.object({
100
104
  reference_temperature: 0.6,
101
105
  aggregator_temperature: 0.2,
102
106
  max_tokens: 8192,
103
- judge_criteria: 'Приоритет: математическая строгость и отсутствие галлюцинаций',
104
- judge_mode: 'auto',
107
+ judge_criteria: '',
105
108
  },
106
109
  ]),
107
110
  })
@@ -153,7 +156,7 @@ export function apply(ctx, config) {
153
156
  /**
154
157
  * Unified LLM dispatch function using ctx.llm.prepareCall / stream
155
158
  */
156
- const callLlm = async ({ provider, model, messages, temperature = 0.6, maxTokens = 4096 }) => {
159
+ const callLlm = async ({ provider, model, messages, temperature = 0.6, maxTokens = 4096, onStreamDelta }) => {
157
160
  if (!ctx.llm) {
158
161
  throw new Error('ctx.llm is not available in cordis context')
159
162
  }
@@ -195,6 +198,9 @@ export function apply(ctx, config) {
195
198
  for await (const chunk of stream) {
196
199
  if (chunk.type === 'text-delta' && typeof chunk.text === 'string') {
197
200
  fullText += chunk.text
201
+ if (typeof onStreamDelta === 'function') {
202
+ onStreamDelta(chunk.text)
203
+ }
198
204
  } else if (chunk.type === 'block-end' && chunk.block?.text) {
199
205
  if (!fullText) fullText = chunk.block.text
200
206
  } else if (chunk.type === 'usage' && chunk.usage) {
@@ -217,6 +223,11 @@ export function apply(ctx, config) {
217
223
  }
218
224
  }
219
225
 
226
+ // Optional Live Canvas preview client: the harness port is resolved per
227
+ // call and any failure degrades to "no preview link" (live-canvas is not
228
+ // required to be installed).
229
+ const liveCanvas = createLiveCanvasClient({ getPort: () => ctx.webServer?.port })
230
+
220
231
  // Route: /dsh-moa/status
221
232
  ctx.effect(() => {
222
233
  return ctx.webServer.register({
@@ -342,23 +353,35 @@ export function apply(ctx, config) {
342
353
  const raw = await readBody(req)
343
354
  const payload = JSON.parse(raw)
344
355
  const newConfig = {
345
- enabled: cfg.enabled !== false,
356
+ enabled: typeof payload.enabled === 'boolean' ? payload.enabled : cfg.enabled !== false,
346
357
  default_preset: payload.default_preset || cfg.default_preset || 'default',
347
358
  presets: Array.isArray(payload.presets) ? payload.presets : (cfg.presets || []),
348
359
  }
349
360
 
361
+ // Validate the merged payload through the plugin schema before it
362
+ // reaches the settings storage: malformed payloads must be
363
+ // rejected with 400 instead of being persisted.
364
+ let validated
365
+ try {
366
+ validated = Config(structuredClone(newConfig))
367
+ } catch (verr) {
368
+ writeJson(res, 400, { ok: false, error: 'Invalid presets payload: ' + (verr?.message || String(verr)) })
369
+ return
370
+ }
371
+
350
372
  // Persist via Cordis Settings API
351
373
  if (settingsScope && typeof settingsScope.replace === 'function') {
352
- await settingsScope.replace(newConfig)
374
+ await settingsScope.replace(validated)
353
375
  } else {
354
376
  // In-memory fallback if settings service is unavailable
355
- getConfig = () => newConfig
377
+ getConfig = () => validated
356
378
  }
357
379
 
358
380
  writeJson(res, 200, {
359
381
  ok: true,
360
- defaultPreset: newConfig.default_preset,
361
- presets: newConfig.presets,
382
+ enabled: validated.enabled !== false,
383
+ defaultPreset: validated.default_preset,
384
+ presets: validated.presets,
362
385
  })
363
386
  } catch (err) {
364
387
  writeJson(res, 400, { ok: false, error: err?.message || String(err) })
@@ -414,6 +437,29 @@ export function apply(ctx, config) {
414
437
  })
415
438
  }, 'dsh-moa: leaderboard route')
416
439
 
440
+ // Route: /dsh-moa/runs/<id> (run replay by id)
441
+ ctx.effect(() => {
442
+ return ctx.webServer.register({
443
+ kind: 'prefix',
444
+ path: '/dsh-moa/runs/',
445
+ handler: (req, res) => {
446
+ if (req.method !== 'GET') {
447
+ writeJson(res, 405, { ok: false, error: 'GET required' })
448
+ return
449
+ }
450
+ const urlPath = req.url.split('?')[0]
451
+ const parts = urlPath.split('/').filter(Boolean)
452
+ const runId = parts[2] || ''
453
+ const run = runId ? getMoaRunById(runId) : null
454
+ if (!run) {
455
+ writeJson(res, 404, { ok: false, error: 'Run not found' })
456
+ return
457
+ }
458
+ writeJson(res, 200, { ok: true, run })
459
+ },
460
+ })
461
+ }, 'dsh-moa: run by id route')
462
+
417
463
  // Route: /dsh-moa/run
418
464
  ctx.effect(() => {
419
465
  return ctx.webServer.register({
@@ -435,6 +481,10 @@ export function apply(ctx, config) {
435
481
  }
436
482
 
437
483
  const cfg = live()
484
+ if (cfg.enabled === false) {
485
+ writeJson(res, 400, { ok: false, error: 'MoA is disabled in settings' })
486
+ return
487
+ }
438
488
  const presetName = payload.preset || cfg.default_preset || 'default'
439
489
  const preset = (cfg.presets || []).find((p) => p.name === presetName) || cfg.presets?.[0]
440
490
 
@@ -444,6 +494,8 @@ export function apply(ctx, config) {
444
494
  preset,
445
495
  callLlm,
446
496
  cwd: payload.cwd || process.cwd(),
497
+ prices: cfg.prices || {},
498
+ liveCanvas,
447
499
  })
448
500
 
449
501
  writeJson(res, 200, { ok: true, ...result })
@@ -511,6 +563,8 @@ export function apply(ctx, config) {
511
563
  cwd: sessCwd,
512
564
  aggregator: { provider: aggProvider, model: aggModel },
513
565
  callLlm,
566
+ prices: cfg.prices || {},
567
+ liveCanvas,
514
568
  }
515
569
 
516
570
  if (event.signal) {
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Optional Live Canvas preview client (@goodandready/dsh-live-canvas).
3
+ *
4
+ * The plugin posts the promoted HTML to live-canvas's own REST contract
5
+ * (POST /dsh-live-canvas/api/preview, served by the same harness webServer),
6
+ * so no cross-plugin service coupling or module imports are needed. Any
7
+ * failure — live-canvas not installed, endpoint missing, timeout, malformed
8
+ * response — degrades to null and the MoA answer simply carries no preview
9
+ * link, without errors in the harness log.
10
+ */
11
+ export function createLiveCanvasClient({ getPort, fetchImpl = globalThis.fetch, timeoutMs = 3000 } = {}) {
12
+ if (typeof fetchImpl !== 'function') return null
13
+ return {
14
+ async createPreviewFromContent({ content, title, filePath } = {}) {
15
+ if (!content || typeof content !== 'string') return null
16
+ const port = typeof getPort === 'function' ? getPort() : getPort
17
+ if (!port) return null
18
+ try {
19
+ const res = await fetchImpl(`http://127.0.0.1:${port}/dsh-live-canvas/api/preview`, {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json' },
22
+ body: JSON.stringify({ content, title, filePath }),
23
+ signal: AbortSignal.timeout(timeoutMs),
24
+ })
25
+ if (!res.ok) return null
26
+ const data = await res.json()
27
+ if (!data || data.success !== true || !data.canvasId || !data.previewUrl) return null
28
+ return { canvasId: data.canvasId, previewUrl: data.previewUrl, title: title || 'Live Preview' }
29
+ } catch {
30
+ return null
31
+ }
32
+ },
33
+ }
34
+ }