@goodandready/dsh-moa 0.2.2 → 0.2.4
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 +14 -0
- package/docs/README.ru.md +14 -0
- package/lib/client.js +55 -3
- package/lib/file-workspace.js +94 -5
- package/lib/history.js +191 -0
- package/lib/index.js +185 -164
- package/lib/moa-runner.js +237 -86
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,32 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness Mixture of Agents (MoA) Plugin
|
|
3
|
+
*
|
|
4
|
+
* Implements an intelligent ensemble pipeline:
|
|
5
|
+
* 1. User issues /moa <prompt>
|
|
6
|
+
* 2. Parallel candidate proposers write to isolated workspaces (.moa/candidate-X/)
|
|
7
|
+
* 3. Aggregator evaluates and synthesizes the optimal solution
|
|
8
|
+
* 4. Winner candidate files are promoted to the project root and previewed in Live Canvas
|
|
9
|
+
* 5. Native cost tracking, history logging and leaderboard analytics
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { z } from 'zod'
|
|
2
13
|
import path from 'node:path'
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
14
|
+
import os from 'node:os'
|
|
15
|
+
import {
|
|
16
|
+
parseMoACommand,
|
|
17
|
+
runMoAPipeline,
|
|
18
|
+
MoaRunnerAdapter,
|
|
19
|
+
} from './moa-runner.js'
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
getMoaHistory,
|
|
23
|
+
getMoaLeaderboard,
|
|
24
|
+
getMoaRunById,
|
|
25
|
+
} from './history.js'
|
|
5
26
|
|
|
6
|
-
export const name = '
|
|
7
|
-
export const inject = ['
|
|
27
|
+
export const name = 'dsh-moa'
|
|
28
|
+
export const inject = ['webServer', 'llm', 'settings', 'sessions', 'tools']
|
|
8
29
|
|
|
9
|
-
export
|
|
30
|
+
export const NS = 'dsh-moa'
|
|
10
31
|
|
|
11
32
|
export const ModelSlotSchema = z.object({
|
|
12
|
-
provider: z.string().default(''),
|
|
13
|
-
model: z.string().default(''),
|
|
33
|
+
provider: z.string().default('opencode-go'),
|
|
34
|
+
model: z.string().default('deepseek-v4-flash'),
|
|
14
35
|
})
|
|
15
36
|
|
|
16
37
|
export const PresetSchema = z.object({
|
|
17
|
-
name: z.string()
|
|
38
|
+
name: z.string(),
|
|
18
39
|
enabled: z.boolean().default(true),
|
|
19
|
-
reference_models: z.array(ModelSlotSchema).default([
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}),
|
|
27
|
-
reference_temperature: z.number().step(0.05).min(0).max(2).default(0.6),
|
|
28
|
-
aggregator_temperature: z.number().step(0.05).min(0).max(2).default(0.4),
|
|
29
|
-
max_tokens: z.number().min(256).max(65536).default(4096),
|
|
40
|
+
reference_models: z.array(ModelSlotSchema).default([]),
|
|
41
|
+
aggregator: ModelSlotSchema.default({ provider: 'codex', model: 'gpt-5.6-sol' }),
|
|
42
|
+
reference_temperature: z.number().min(0).max(2).default(0.6),
|
|
43
|
+
aggregator_temperature: z.number().min(0).max(2).default(0.4),
|
|
44
|
+
max_tokens: z.number().int().positive().default(4096),
|
|
45
|
+
judge_criteria: z.string().optional().default(''),
|
|
46
|
+
judge_mode: z.enum(['auto', 'review']).optional().default('auto'),
|
|
30
47
|
})
|
|
31
48
|
|
|
32
49
|
export const Config = z.object({
|
|
@@ -38,162 +55,141 @@ export const Config = z.object({
|
|
|
38
55
|
enabled: true,
|
|
39
56
|
reference_models: [
|
|
40
57
|
{ provider: 'opencode-go', model: 'deepseek-v4-flash' },
|
|
41
|
-
{ provider: '
|
|
58
|
+
{ provider: 'grok', model: 'grok-build-0.1' },
|
|
42
59
|
],
|
|
43
|
-
aggregator: {
|
|
44
|
-
provider: 'antigravity',
|
|
45
|
-
model: 'gemini-3-flash',
|
|
46
|
-
},
|
|
60
|
+
aggregator: { provider: 'codex', model: 'gpt-5.6-sol' },
|
|
47
61
|
reference_temperature: 0.6,
|
|
48
62
|
aggregator_temperature: 0.4,
|
|
49
63
|
max_tokens: 4096,
|
|
64
|
+
judge_criteria: '',
|
|
65
|
+
judge_mode: 'auto',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'fast',
|
|
69
|
+
enabled: true,
|
|
70
|
+
reference_models: [
|
|
71
|
+
{ provider: 'opencode-go', model: 'deepseek-v4-flash' },
|
|
72
|
+
],
|
|
73
|
+
aggregator: { provider: 'opencode-go', model: 'deepseek-v4-flash' },
|
|
74
|
+
reference_temperature: 0.6,
|
|
75
|
+
aggregator_temperature: 0.2,
|
|
76
|
+
max_tokens: 4096,
|
|
77
|
+
judge_criteria: '',
|
|
78
|
+
judge_mode: 'auto',
|
|
50
79
|
},
|
|
51
80
|
{
|
|
52
81
|
name: 'deep-reasoning',
|
|
53
82
|
enabled: true,
|
|
54
83
|
reference_models: [
|
|
55
|
-
{ provider: 'grok', model: 'grok-
|
|
56
|
-
{ provider: '
|
|
84
|
+
{ provider: 'grok', model: 'grok-build-0.1' },
|
|
85
|
+
{ provider: 'commandcode', model: 'deepseek/deepseek-v4-flash' },
|
|
57
86
|
],
|
|
58
|
-
aggregator: {
|
|
59
|
-
|
|
60
|
-
model: 'gemini-3-flash',
|
|
61
|
-
},
|
|
62
|
-
reference_temperature: 0.7,
|
|
87
|
+
aggregator: { provider: 'codex', model: 'gpt-5.6-sol' },
|
|
88
|
+
reference_temperature: 0.6,
|
|
63
89
|
aggregator_temperature: 0.2,
|
|
64
90
|
max_tokens: 8192,
|
|
91
|
+
judge_criteria: 'Приоритет: математическая строгость и отсутствие галлюцинаций',
|
|
92
|
+
judge_mode: 'auto',
|
|
65
93
|
},
|
|
66
94
|
]),
|
|
67
95
|
})
|
|
68
96
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} catch {
|
|
76
|
-
/* socket closed */
|
|
77
|
-
}
|
|
97
|
+
function writeJson(res, status, body) {
|
|
98
|
+
res.writeHead(status, {
|
|
99
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
100
|
+
'Cache-Control': 'no-store',
|
|
101
|
+
})
|
|
102
|
+
res.end(JSON.stringify(body))
|
|
78
103
|
}
|
|
79
104
|
|
|
80
105
|
function readBody(req) {
|
|
81
106
|
return new Promise((resolve, reject) => {
|
|
82
|
-
|
|
83
|
-
req.on('data', (
|
|
84
|
-
|
|
107
|
+
let acc = ''
|
|
108
|
+
req.on('data', (chunk) => {
|
|
109
|
+
acc += chunk
|
|
110
|
+
if (acc.length > 5 * 1024 * 1024) {
|
|
111
|
+
req.destroy()
|
|
112
|
+
reject(new Error('Payload Too Large'))
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
req.on('end', () => resolve(acc))
|
|
85
116
|
req.on('error', reject)
|
|
86
117
|
})
|
|
87
118
|
}
|
|
88
119
|
|
|
89
|
-
|
|
90
120
|
export function apply(ctx, config) {
|
|
121
|
+
let currentConfig = Config.parse(config || {})
|
|
91
122
|
let settingsApi = null
|
|
92
|
-
let currentConfig = structuredClone(config)
|
|
93
|
-
|
|
94
|
-
// Try reading persisted config from ~/.dsh/settings.yaml on load
|
|
95
|
-
try {
|
|
96
|
-
const fs = require('node:fs')
|
|
97
|
-
const yaml = require('yaml')
|
|
98
|
-
const p = path.join(os.homedir(), '.dsh', 'settings.yaml')
|
|
99
|
-
if (fs.existsSync(p)) {
|
|
100
|
-
const parsed = yaml.parse(fs.readFileSync(p, 'utf8'))
|
|
101
|
-
if (parsed && parsed[NS]) {
|
|
102
|
-
currentConfig = Config(parsed[NS])
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} catch {}
|
|
106
123
|
|
|
107
124
|
ctx.inject(['settings'], (sctx) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
125
|
+
try {
|
|
126
|
+
settingsApi = sctx.settings.register(NS, Config, { base: currentConfig })
|
|
127
|
+
const val = settingsApi.get()
|
|
128
|
+
if (val) currentConfig = Config.parse(val)
|
|
129
|
+
settingsApi.onChange((next) => {
|
|
130
|
+
if (next) currentConfig = Config.parse(next)
|
|
131
|
+
})
|
|
132
|
+
} catch (e) {
|
|
133
|
+
console.warn('[dsh-moa] Settings registration warning:', e)
|
|
113
134
|
}
|
|
114
|
-
sctx.effect(() => () => {
|
|
115
|
-
settingsApi = null
|
|
116
|
-
})
|
|
117
135
|
})
|
|
118
136
|
|
|
119
|
-
const getConfig = () =>
|
|
120
|
-
if (settingsApi) {
|
|
121
|
-
const live = settingsApi.get()
|
|
122
|
-
if (live && live.presets && live.presets.length) return live
|
|
123
|
-
}
|
|
124
|
-
return currentConfig || config
|
|
125
|
-
}
|
|
137
|
+
const getConfig = () => currentConfig
|
|
126
138
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
out += chunk.text
|
|
134
|
-
sawDelta = true
|
|
135
|
-
} else if (
|
|
136
|
-
!sawDelta && chunk && chunk.type === 'block-end' &&
|
|
137
|
-
chunk.block && chunk.block.type === 'text' && typeof chunk.block.text === 'string'
|
|
138
|
-
) {
|
|
139
|
-
out += chunk.block.text
|
|
140
|
-
} else if (chunk && chunk.type === 'finish' && chunk.reason && chunk.reason.kind === 'error') {
|
|
141
|
-
throw new Error(chunk.reason.failure?.message || 'LLM stream failed')
|
|
142
|
-
}
|
|
139
|
+
/**
|
|
140
|
+
* Unified LLM dispatch function using ctx.llm.prepareCall / stream
|
|
141
|
+
*/
|
|
142
|
+
const callLlm = async ({ provider, model, messages, temperature = 0.6, maxTokens = 4096 }) => {
|
|
143
|
+
if (!ctx.llm) {
|
|
144
|
+
throw new Error('ctx.llm is not available in cordis context')
|
|
143
145
|
}
|
|
144
|
-
return out.trim()
|
|
145
|
-
}
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
throw new Error('ctx.llm.stream service is not available')
|
|
147
|
+
const prep = await ctx.llm.prepareCall(provider, model)
|
|
148
|
+
if (!prep || typeof prep.stream !== 'function') {
|
|
149
|
+
throw new Error(`LLM provider/model ${provider}:${model} could not be prepared`)
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// Each message must have role, content (array of blocks or string), and source
|
|
157
|
-
const formattedMessages = (messages || []).map((m) => {
|
|
158
|
-
let content = m.content
|
|
159
|
-
if (typeof content === 'string') {
|
|
160
|
-
content = [{ type: 'text', text: content }]
|
|
161
|
-
} else if (Array.isArray(content)) {
|
|
162
|
-
content = content.map((b) => (typeof b === 'string' ? { type: 'text', text: b } : b))
|
|
163
|
-
} else {
|
|
164
|
-
content = [{ type: 'text', text: String(content || '') }]
|
|
165
|
-
}
|
|
152
|
+
const abortCtrl = new AbortController()
|
|
153
|
+
const timeoutId = setTimeout(() => abortCtrl.abort(new Error('LLM call timeout')), 120000)
|
|
154
|
+
timeoutId.unref?.()
|
|
166
155
|
|
|
167
|
-
|
|
168
|
-
const
|
|
156
|
+
try {
|
|
157
|
+
const stream = prep.stream({
|
|
158
|
+
messages,
|
|
159
|
+
temperature,
|
|
160
|
+
maxTokens,
|
|
161
|
+
signal: abortCtrl.signal,
|
|
162
|
+
})
|
|
169
163
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
content,
|
|
173
|
-
source,
|
|
174
|
-
}
|
|
175
|
-
})
|
|
164
|
+
let fullText = ''
|
|
165
|
+
let usageInfo = null
|
|
176
166
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
167
|
+
for await (const chunk of stream) {
|
|
168
|
+
if (chunk.type === 'text-delta' && typeof chunk.text === 'string') {
|
|
169
|
+
fullText += chunk.text
|
|
170
|
+
} else if (chunk.type === 'block-end' && chunk.block?.text) {
|
|
171
|
+
if (!fullText) fullText = chunk.block.text
|
|
172
|
+
} else if (chunk.type === 'usage' && chunk.usage) {
|
|
173
|
+
usageInfo = chunk.usage
|
|
174
|
+
}
|
|
175
|
+
}
|
|
185
176
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
clearTimeout(timeoutId)
|
|
178
|
+
return {
|
|
179
|
+
content: fullText,
|
|
180
|
+
text: fullText,
|
|
181
|
+
usage: usageInfo || {
|
|
182
|
+
inputTokens: Math.round(JSON.stringify(messages).length / 4),
|
|
183
|
+
outputTokens: Math.round(fullText.length / 4),
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
clearTimeout(timeoutId)
|
|
188
|
+
throw err
|
|
189
189
|
}
|
|
190
|
-
|
|
191
|
-
const chunks = ctx.llm.stream(streamOpts)
|
|
192
|
-
|
|
193
|
-
return await collectStreamText(chunks)
|
|
194
190
|
}
|
|
195
191
|
|
|
196
|
-
//
|
|
192
|
+
// Route: /dsh-moa/status
|
|
197
193
|
ctx.effect(() => {
|
|
198
194
|
return ctx.webServer.register({
|
|
199
195
|
kind: 'exact',
|
|
@@ -204,15 +200,13 @@ export function apply(ctx, config) {
|
|
|
204
200
|
ok: true,
|
|
205
201
|
enabled: cfg.enabled !== false,
|
|
206
202
|
defaultPreset: cfg.default_preset || 'default',
|
|
207
|
-
hasCtxLlm: Boolean(ctx.llm),
|
|
208
|
-
llmKeys: ctx.llm ? Object.keys(ctx.llm) : [],
|
|
209
|
-
llmProto: ctx.llm ? Object.getOwnPropertyNames(Object.getPrototypeOf(ctx.llm)) : [],
|
|
210
203
|
presetsCount: (cfg.presets || []).length,
|
|
211
204
|
})
|
|
212
205
|
},
|
|
213
206
|
})
|
|
214
207
|
}, 'dsh-moa: status route')
|
|
215
208
|
|
|
209
|
+
// Route: /dsh-moa/models
|
|
216
210
|
ctx.effect(() => {
|
|
217
211
|
return ctx.webServer.register({
|
|
218
212
|
kind: 'exact',
|
|
@@ -221,43 +215,25 @@ export function apply(ctx, config) {
|
|
|
221
215
|
const result = []
|
|
222
216
|
const seen = new Set()
|
|
223
217
|
|
|
224
|
-
const add = (
|
|
225
|
-
if (!prov || !mod) return
|
|
226
|
-
const p = String(prov).trim()
|
|
227
|
-
const m = String(mod).trim()
|
|
218
|
+
const add = (p, m) => {
|
|
228
219
|
if (!p || !m) return
|
|
229
220
|
const key = `${p}:${m}`
|
|
230
|
-
if (seen.has(key))
|
|
231
|
-
|
|
232
|
-
|
|
221
|
+
if (!seen.has(key)) {
|
|
222
|
+
seen.add(key)
|
|
223
|
+
result.push({ provider: p, model: m, label: key })
|
|
224
|
+
}
|
|
233
225
|
}
|
|
234
226
|
|
|
235
|
-
// ONLY query REAL models from runtime adapters & settings. NO hardcoded fallbacks!
|
|
236
227
|
if (ctx.llm) {
|
|
237
228
|
try {
|
|
238
|
-
if (typeof ctx.llm.
|
|
239
|
-
const
|
|
240
|
-
for (const
|
|
241
|
-
|
|
242
|
-
if (!pId) continue
|
|
243
|
-
if (typeof ctx.llm.listModels === 'function') {
|
|
244
|
-
try {
|
|
245
|
-
const mList = await ctx.llm.listModels(pId)
|
|
246
|
-
for (const m of (mList || [])) {
|
|
247
|
-
const mId = typeof m === 'string' ? m : (m.id || m.model || m.name)
|
|
248
|
-
const mLabel = m.displayName || m.name || mId
|
|
249
|
-
if (mId) add(pId, mId, `${pId}: ${mLabel}`)
|
|
250
|
-
}
|
|
251
|
-
} catch {}
|
|
252
|
-
}
|
|
253
|
-
for (const m of (p.models || [])) {
|
|
254
|
-
const mId = typeof m === 'string' ? m : (m.id || m.model || m.name)
|
|
255
|
-
if (mId) add(pId, mId)
|
|
256
|
-
}
|
|
229
|
+
if (typeof ctx.llm.listAvailableModels === 'function') {
|
|
230
|
+
const list = await ctx.llm.listAvailableModels()
|
|
231
|
+
for (const item of (list || [])) {
|
|
232
|
+
if (item?.provider && item?.id) add(item.provider, item.id)
|
|
257
233
|
}
|
|
258
234
|
}
|
|
259
235
|
} catch (e) {
|
|
260
|
-
console.warn('[dsh-moa] ctx.llm.
|
|
236
|
+
console.warn('[dsh-moa] ctx.llm.listAvailableModels error:', e)
|
|
261
237
|
}
|
|
262
238
|
|
|
263
239
|
try {
|
|
@@ -317,6 +293,7 @@ export function apply(ctx, config) {
|
|
|
317
293
|
})
|
|
318
294
|
}, 'dsh-moa: models route')
|
|
319
295
|
|
|
296
|
+
// Route: /dsh-moa/presets
|
|
320
297
|
ctx.effect(() => {
|
|
321
298
|
return ctx.webServer.register({
|
|
322
299
|
kind: 'exact',
|
|
@@ -378,6 +355,50 @@ export function apply(ctx, config) {
|
|
|
378
355
|
})
|
|
379
356
|
}, 'dsh-moa: presets route')
|
|
380
357
|
|
|
358
|
+
// Route: /dsh-moa/history (#9)
|
|
359
|
+
ctx.effect(() => {
|
|
360
|
+
return ctx.webServer.register({
|
|
361
|
+
kind: 'exact',
|
|
362
|
+
path: '/dsh-moa/history',
|
|
363
|
+
handler: async (req, res) => {
|
|
364
|
+
if (req.method !== 'GET') {
|
|
365
|
+
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const u = new URL(req.url, 'http://localhost')
|
|
370
|
+
const limit = parseInt(u.searchParams.get('limit') || '20', 10)
|
|
371
|
+
const offset = parseInt(u.searchParams.get('offset') || '0', 10)
|
|
372
|
+
const data = getMoaHistory(limit, offset)
|
|
373
|
+
writeJson(res, 200, { ok: true, ...data })
|
|
374
|
+
} catch (err) {
|
|
375
|
+
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
})
|
|
379
|
+
}, 'dsh-moa: history route')
|
|
380
|
+
|
|
381
|
+
// Route: /dsh-moa/leaderboard (#11)
|
|
382
|
+
ctx.effect(() => {
|
|
383
|
+
return ctx.webServer.register({
|
|
384
|
+
kind: 'exact',
|
|
385
|
+
path: '/dsh-moa/leaderboard',
|
|
386
|
+
handler: async (req, res) => {
|
|
387
|
+
if (req.method !== 'GET') {
|
|
388
|
+
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const data = getMoaLeaderboard()
|
|
393
|
+
writeJson(res, 200, { ok: true, ...data })
|
|
394
|
+
} catch (err) {
|
|
395
|
+
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
})
|
|
399
|
+
}, 'dsh-moa: leaderboard route')
|
|
400
|
+
|
|
401
|
+
// Route: /dsh-moa/run
|
|
381
402
|
ctx.effect(() => {
|
|
382
403
|
return ctx.webServer.register({
|
|
383
404
|
kind: 'exact',
|