@goodandready/dsh-moa 0.2.34 → 0.2.35

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/index.js CHANGED
@@ -1,165 +1,36 @@
1
+ import path from 'node:path'
1
2
  import { setLogger, logger } from './logger.js'
2
3
  import { refreshCatalogInBackground } from './pricing.js'
3
- /**
4
- * DeepSeek Harness Mixture of Agents (MoA) Plugin
5
- *
6
- * Implements an intelligent ensemble pipeline:
7
- * 1. User issues /moa <prompt>
8
- * 2. Parallel candidate proposers write to isolated workspaces (.moa/candidate-X/)
9
- * 3. Aggregator evaluates and synthesizes the optimal solution
10
- * 4. Winner candidate files are promoted to the project root
11
- * 5. Native cost tracking, history logging and leaderboard analytics
12
- */
13
-
14
- import z from '@deepseek-ai/schemastery'
15
-
16
- // Ensure .volatile() helper exists on Schemastery schema prototype
17
- try {
18
- const schemaProto = Object.getPrototypeOf(z.boolean())
19
- if (schemaProto && typeof schemaProto.volatile !== 'function') {
20
- schemaProto.volatile = function () {
21
- return typeof this.extra === 'function' ? this.extra('volatile', true) : this
22
- }
23
- }
24
- } catch {
25
- // safe fallback
26
- }
27
- import path from 'node:path'
4
+ import {
5
+ PriceRow,
6
+ ModelSlotSchema,
7
+ PresetSchema,
8
+ Config,
9
+ plainConfig,
10
+ } from './moa-schema.js'
11
+ import { createLlmCaller } from './moa-llm.js'
28
12
  import {
29
13
  parseMoACommand,
30
14
  streamMoATurn,
31
15
  } from './moa-runner.js'
32
-
33
16
  import { registerMoaRoutes } from './routes.js'
34
17
  import { resolvePresetForPrompt } from './moa-router.js'
35
-
36
18
  import { createLiveCanvasClient } from './live-canvas.js'
37
19
  import { registerPluginUpdater } from './updater.js'
38
20
 
39
21
  export const name = '@goodandready/dsh-moa'
40
22
  export { collectProjectContext, formatProjectContext } from './file-workspace.js'
41
23
  export const inject = ['webServer', 'llm', 'sessions', 'tools']
42
-
43
24
  export const NS = 'dsh-moa'
44
25
 
45
- export const PriceRow = z.object({
46
- input: z.number().default(0),
47
- output: z.number().default(0),
48
- cacheHit: z.number().default(0),
49
- })
50
-
51
- export const ModelSlotSchema = z.object({
52
- provider: z.string().default(''),
53
- model: z.string().default(''),
54
- role_persona: z.string().default(''),
55
- temperature: z.number().default(-1),
56
- })
57
-
58
- export const PresetSchema = z.object({
59
- name: z.string(),
60
- enabled: z.boolean().default(true),
61
- ask_clarifying_questions: z.boolean().default(true),
62
- curator_synthesis: z.boolean().default(false),
63
- stream_aggregator: z.boolean().default(true),
64
- quorum_enabled: z.boolean().default(false),
65
- grace_period_sec: z.number().default(10),
66
- reference_models: z.array(ModelSlotSchema).default([]),
67
- aggregator: ModelSlotSchema.default({ provider: '', model: '' }),
68
- aggregator_fallbacks: z.array(ModelSlotSchema).default([]),
69
- reference_temperature: z.number().default(0.6),
70
- aggregator_temperature: z.number().default(0.4),
71
- reference_timeout_sec: z.number().default(60),
72
- aggregator_timeout_sec: z.number().default(180),
73
- blind_evaluation: z.boolean().default(false),
74
- peer_critique_enabled: z.boolean().default(false),
75
- allow_candidate_override: z.boolean().default(false),
76
- max_tokens: z.number().default(4096),
77
- judge_criteria: z.string().default(''),
78
- test_gate_enabled: z.boolean().default(false),
79
- test_command: z.string().default(''),
80
- test_gate_timeout_sec: z.number().default(15),
81
- multi_judge_enabled: z.boolean().default(false),
82
- judge_models: z.array(ModelSlotSchema).default([]),
83
- judge_voting_strategy: z.string().default('majority'),
84
- composite_merge_enabled: z.boolean().default(false),
85
- smart_routing_enabled: z.boolean().default(false),
86
- smart_routing_model: ModelSlotSchema.default({ provider: '', model: '' }),
87
- budget_guard_enabled: z.boolean().default(false),
88
- max_budget_usd: z.number().default(0),
89
- budget_action: z.string().default('trim'),
90
- temperature_gradient_enabled: z.boolean().default(false),
91
- multi_turn_enabled: z.boolean().default(true),
92
- report_generation_enabled: z.boolean().default(false),
93
- local_fallback_enabled: z.boolean().default(false),
94
- local_fallback_models: z.array(ModelSlotSchema).default([]),
95
- })
96
-
97
- export const Config = z.object({
98
- enabled: z.boolean().default(true).volatile(),
99
- default_preset: z.string().default('default').volatile(),
100
- smart_routing_enabled: z.boolean().default(false).volatile(),
101
- smart_routing_model: ModelSlotSchema.default({ provider: '', model: '' }).volatile(),
102
- prices: z.dict(PriceRow).default({}).volatile(),
103
- presets: z.array(PresetSchema).default([
104
- {
105
- name: 'default',
106
- enabled: true,
107
- ask_clarifying_questions: true,
108
- reference_models: [
109
- { provider: '', model: '', role_persona: 'proposer' },
110
- { provider: '', model: '', role_persona: 'challenger' },
111
- ],
112
- aggregator: { provider: '', model: '' },
113
- reference_temperature: 0.6,
114
- aggregator_temperature: 0.4,
115
- max_tokens: 4096,
116
- judge_criteria: '',
117
- },
118
- {
119
- name: 'fast',
120
- enabled: true,
121
- ask_clarifying_questions: false,
122
- reference_models: [
123
- { provider: '', model: '', role_persona: 'fast' },
124
- ],
125
- aggregator: { provider: '', model: '' },
126
- reference_temperature: 0.6,
127
- aggregator_temperature: 0.2,
128
- max_tokens: 4096,
129
- judge_criteria: '',
130
- },
131
- {
132
- name: 'deep-reasoning',
133
- enabled: true,
134
- ask_clarifying_questions: true,
135
- reference_models: [
136
- { provider: '', model: '', role_persona: 'architect' },
137
- { provider: '', model: '', role_persona: 'critic' },
138
- ],
139
- aggregator: { provider: '', model: '' },
140
- reference_temperature: 0.6,
141
- aggregator_temperature: 0.2,
142
- max_tokens: 8192,
143
- judge_criteria: '',
144
- },
145
- ]).volatile(),
146
- })
147
-
148
-
149
- export function plainConfig(value) {
150
- if (value && typeof value === 'object' && typeof value.get === 'function') {
151
- return plainConfig(value.get())
152
- }
153
- if (Array.isArray(value)) {
154
- return value.map(plainConfig)
155
- }
156
- if (value !== null && typeof value === 'object') {
157
- return Object.fromEntries(
158
- Object.entries(value).map(([key, child]) => [key, plainConfig(child)])
159
- )
160
- }
161
- return value
162
- }
26
+ export {
27
+ PriceRow,
28
+ ModelSlotSchema,
29
+ PresetSchema,
30
+ Config,
31
+ plainConfig,
32
+ } from './moa-schema.js'
33
+ export { createLlmCaller } from './moa-llm.js'
163
34
 
164
35
  export function apply(ctx, config) {
165
36
  if (ctx.logger) setLogger(ctx.logger)
@@ -214,76 +85,7 @@ export function apply(ctx, config) {
214
85
  return localConfig
215
86
  }
216
87
 
217
- /**
218
- * Unified LLM dispatch function using ctx.llm.prepareCall / stream
219
- */
220
- const callLlm = async ({ provider, model, messages, temperature = 0.6, maxTokens = 4096, timeoutMs, onStreamDelta }) => {
221
- if (!ctx.llm) {
222
- throw new Error('ctx.llm is not available in cordis context')
223
- }
224
-
225
- let prep
226
- try {
227
- // DSH 0.1.2-rc.1 API expects { provider, model } object
228
- prep = await ctx.llm.prepareCall({ provider, model })
229
- } catch (err) {
230
- if (typeof ctx.llm.prepareCall === 'function') {
231
- try {
232
- prep = await ctx.llm.prepareCall(provider, model)
233
- } catch {
234
- throw err
235
- }
236
- } else {
237
- throw err
238
- }
239
- }
240
- if (!prep || typeof prep.stream !== 'function') {
241
- throw new Error(`LLM provider/model ${provider}:${model} could not be prepared`)
242
- }
243
-
244
- const effectiveTimeoutMs = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : 120000
245
- const abortCtrl = new AbortController()
246
- const timeoutId = setTimeout(() => abortCtrl.abort(new Error(`LLM call timeout after ${Math.round(effectiveTimeoutMs / 1000)}s`)), effectiveTimeoutMs)
247
- timeoutId.unref?.()
248
-
249
- try {
250
- const stream = prep.stream({
251
- messages,
252
- temperature,
253
- maxTokens,
254
- signal: abortCtrl.signal,
255
- })
256
-
257
- let fullText = ''
258
- let usageInfo = null
259
-
260
- for await (const chunk of stream) {
261
- if (chunk.type === 'text-delta' && typeof chunk.text === 'string') {
262
- fullText += chunk.text
263
- if (typeof onStreamDelta === 'function') {
264
- onStreamDelta(chunk.text)
265
- }
266
- } else if (chunk.type === 'block-end' && chunk.block?.text) {
267
- if (!fullText) fullText = chunk.block.text
268
- } else if (chunk.type === 'usage' && chunk.usage) {
269
- usageInfo = chunk.usage
270
- }
271
- }
272
-
273
- clearTimeout(timeoutId)
274
- return {
275
- content: fullText,
276
- text: fullText,
277
- usage: usageInfo || {
278
- inputTokens: Math.round(JSON.stringify(messages).length / 4),
279
- outputTokens: Math.round(fullText.length / 4),
280
- },
281
- }
282
- } catch (err) {
283
- clearTimeout(timeoutId)
284
- throw err
285
- }
286
- }
88
+ const callLlm = createLlmCaller(ctx)
287
89
 
288
90
  // Optional Live Canvas preview client: the harness port is resolved per
289
91
  // call and any failure degrades to "no preview link" (live-canvas is not
@@ -311,7 +113,6 @@ export function apply(ctx, config) {
311
113
  })
312
114
 
313
115
  // Session turn interceptor: route turn to real aggregator model & stream MoA synthesis
314
-
315
116
  ctx.effect(() => {
316
117
  return () => {
317
118
  for (const entry of moaSessionTurns.values()) {
@@ -354,81 +155,81 @@ export function apply(ctx, config) {
354
155
 
355
156
  ctx.effect(() => {
356
157
  return ctx.on('agent/pre-step', async (event, next) => {
357
- const messages = event.messages || []
358
- const lastUserMsg = [...messages].reverse().find((m) => m && m.role === 'user')
359
- const userText = typeof lastUserMsg?.content === 'string'
360
- ? lastUserMsg.content.trim()
361
- : (Array.isArray(lastUserMsg?.content)
362
- ? lastUserMsg.content.filter((p) => p.type === 'text').map((p) => p.text).join('\n').trim()
363
- : '')
364
-
365
- if (userText.startsWith('/moa')) {
366
- const cfg = live()
367
- if (cfg.enabled !== false) {
368
- const parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: cfg.default_preset || 'default' }
369
- const isExplicitPreset = userText.includes('--preset') || userText.includes('-p ')
370
- if (!isExplicitPreset && (cfg.smart_routing_enabled || cfg.presets?.some((p) => p.smart_routing_enabled))) {
371
- try {
372
- const routed = await resolvePresetForPrompt({
373
- prompt: parsed.prompt || userText,
374
- presets: cfg.presets || [],
375
- defaultPreset: parsed.presetName || cfg.default_preset || 'default',
376
- routingModel: cfg.smart_routing_model,
377
- callLlm,
378
- enabled: true,
379
- })
380
- if (routed?.isAutoRouted && routed.presetName) {
381
- parsed.presetName = routed.presetName
158
+ const messages = event.messages || []
159
+ const lastUserMsg = [...messages].reverse().find((m) => m && m.role === 'user')
160
+ const userText = typeof lastUserMsg?.content === 'string'
161
+ ? lastUserMsg.content.trim()
162
+ : (Array.isArray(lastUserMsg?.content)
163
+ ? lastUserMsg.content.filter((p) => p.type === 'text').map((p) => p.text).join('\n').trim()
164
+ : '')
165
+
166
+ if (userText.startsWith('/moa')) {
167
+ const cfg = live()
168
+ if (cfg.enabled !== false) {
169
+ const parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: cfg.default_preset || 'default' }
170
+ const isExplicitPreset = userText.includes('--preset') || userText.includes('-p ')
171
+ if (!isExplicitPreset && (cfg.smart_routing_enabled || cfg.presets?.some((p) => p.smart_routing_enabled))) {
172
+ try {
173
+ const routed = await resolvePresetForPrompt({
174
+ prompt: parsed.prompt || userText,
175
+ presets: cfg.presets || [],
176
+ defaultPreset: parsed.presetName || cfg.default_preset || 'default',
177
+ routingModel: cfg.smart_routing_model,
178
+ callLlm,
179
+ enabled: true,
180
+ })
181
+ if (routed?.isAutoRouted && routed.presetName) {
182
+ parsed.presetName = routed.presetName
183
+ }
184
+ } catch {
185
+ // fallback silently
382
186
  }
383
- } catch {
384
- // fallback silently
385
187
  }
386
- }
387
- const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName) || cfg.presets?.[0]
388
- const aggProvider = targetPreset?.aggregator?.provider || ''
389
- const aggModel = targetPreset?.aggregator?.model || ''
390
- const sessCwd = event.cwd || event.session?.cwd || (event.session?.path ? path.dirname(event.session.path) : undefined) || process.cwd()
391
-
392
- const turnContext = {
393
- targetPreset,
394
- userPrompt: parsed.prompt || userText,
395
- messages: messages.slice(0, -1),
396
- cwd: sessCwd,
397
- aggregator: { provider: aggProvider, model: aggModel },
398
- callLlm,
399
- prices: cfg.prices || {},
400
- liveCanvas,
401
- webServerPort: ctx.webServer?.port,
402
- timeMachineEngine: ctx.get?.('timeMachineEngine') || ctx.timeMachineEngine,
403
- }
188
+ const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName) || cfg.presets?.[0]
189
+ const aggProvider = targetPreset?.aggregator?.provider || ''
190
+ const aggModel = targetPreset?.aggregator?.model || ''
191
+ const sessCwd = event.cwd || event.session?.cwd || (event.session?.path ? path.dirname(event.session.path) : undefined) || process.cwd()
192
+
193
+ const turnContext = {
194
+ targetPreset,
195
+ userPrompt: parsed.prompt || userText,
196
+ messages: messages.slice(0, -1),
197
+ cwd: sessCwd,
198
+ aggregator: { provider: aggProvider, model: aggModel },
199
+ callLlm,
200
+ prices: cfg.prices || {},
201
+ liveCanvas,
202
+ webServerPort: ctx.webServer?.port,
203
+ timeMachineEngine: ctx.get?.('timeMachineEngine') || ctx.timeMachineEngine,
204
+ }
404
205
 
405
- if (event.signal) {
406
- moaPendingTurns.set(event.signal, turnContext)
407
- }
408
- if (event.session?.id) {
409
- setSessionTurn(event.session.id, turnContext)
206
+ if (event.signal) {
207
+ moaPendingTurns.set(event.signal, turnContext)
208
+ }
209
+ if (event.session?.id) {
210
+ setSessionTurn(event.session.id, turnContext)
211
+ }
410
212
  }
411
213
  }
412
- }
413
214
 
414
- return next()
215
+ return next()
415
216
  })
416
217
  }, 'dsh-moa: agent pre-step interceptor')
417
218
 
418
219
  ctx.effect(() => {
419
220
  return ctx.on('agent/request', async (payload, next) => {
420
- const resolved = await next()
421
- const turnContext = (payload.signal ? moaPendingTurns.get(payload.signal) : null)
422
- || (payload.session?.id ? getSessionTurn(payload.session.id) : null)
221
+ const resolved = await next()
222
+ const turnContext = (payload.signal ? moaPendingTurns.get(payload.signal) : null)
223
+ || (payload.session?.id ? getSessionTurn(payload.session.id) : null)
423
224
 
424
- if (turnContext) {
425
- return {
426
- ...resolved,
427
- provider: turnContext.aggregator.provider,
428
- model: turnContext.aggregator.model,
225
+ if (turnContext) {
226
+ return {
227
+ ...resolved,
228
+ provider: turnContext.aggregator.provider,
229
+ model: turnContext.aggregator.model,
230
+ }
429
231
  }
430
- }
431
- return resolved
232
+ return resolved
432
233
  })
433
234
  }, 'dsh-moa: agent request interceptor')
434
235
 
package/lib/moa-llm.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Unified LLM Dispatcher using ctx.llm.prepareCall / stream
3
+ */
4
+
5
+ export function createLlmCaller(ctx) {
6
+ return async ({ provider, model, messages, temperature = 0.6, maxTokens = 4096, timeoutMs, onStreamDelta }) => {
7
+ if (!ctx.llm) {
8
+ throw new Error('ctx.llm is not available in cordis context')
9
+ }
10
+
11
+ let prep
12
+ try {
13
+ // DSH 0.1.2-rc.1 API expects { provider, model } object
14
+ prep = await ctx.llm.prepareCall({ provider, model })
15
+ } catch (err) {
16
+ if (typeof ctx.llm.prepareCall === 'function') {
17
+ try {
18
+ prep = await ctx.llm.prepareCall(provider, model)
19
+ } catch {
20
+ throw err
21
+ }
22
+ } else {
23
+ throw err
24
+ }
25
+ }
26
+ if (!prep || typeof prep.stream !== 'function') {
27
+ throw new Error(`LLM provider/model ${provider}:${model} could not be prepared`)
28
+ }
29
+
30
+ const effectiveTimeoutMs = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : 120000
31
+ const abortCtrl = new AbortController()
32
+ const timeoutId = setTimeout(() => abortCtrl.abort(new Error(`LLM call timeout after ${Math.round(effectiveTimeoutMs / 1000)}s`)), effectiveTimeoutMs)
33
+ timeoutId.unref?.()
34
+
35
+ try {
36
+ const stream = prep.stream({
37
+ messages,
38
+ temperature,
39
+ maxTokens,
40
+ signal: abortCtrl.signal,
41
+ })
42
+
43
+ let fullText = ''
44
+ let usageInfo = null
45
+
46
+ for await (const chunk of stream) {
47
+ if (chunk.type === 'text-delta' && typeof chunk.text === 'string') {
48
+ fullText += chunk.text
49
+ if (typeof onStreamDelta === 'function') {
50
+ onStreamDelta(chunk.text)
51
+ }
52
+ } else if (chunk.type === 'block-end' && chunk.block?.text) {
53
+ if (!fullText) fullText = chunk.block.text
54
+ } else if (chunk.type === 'usage' && chunk.usage) {
55
+ usageInfo = chunk.usage
56
+ }
57
+ }
58
+
59
+ clearTimeout(timeoutId)
60
+ return {
61
+ content: fullText,
62
+ text: fullText,
63
+ usage: usageInfo || {
64
+ inputTokens: Math.round(JSON.stringify(messages).length / 4),
65
+ outputTokens: Math.round(fullText.length / 4),
66
+ },
67
+ }
68
+ } catch (err) {
69
+ clearTimeout(timeoutId)
70
+ throw err
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,131 @@
1
+ import z from '@deepseek-ai/schemastery'
2
+
3
+ // Ensure .volatile() helper exists on Schemastery schema prototype
4
+ try {
5
+ const schemaProto = Object.getPrototypeOf(z.boolean())
6
+ if (schemaProto && typeof schemaProto.volatile !== 'function') {
7
+ schemaProto.volatile = function () {
8
+ return typeof this.extra === 'function' ? this.extra('volatile', true) : this
9
+ }
10
+ }
11
+ } catch {
12
+ // safe fallback
13
+ }
14
+
15
+ export const PriceRow = z.object({
16
+ input: z.number().default(0),
17
+ output: z.number().default(0),
18
+ cacheHit: z.number().default(0),
19
+ })
20
+
21
+ export const ModelSlotSchema = z.object({
22
+ provider: z.string().default(''),
23
+ model: z.string().default(''),
24
+ role_persona: z.string().default(''),
25
+ temperature: z.number().default(-1),
26
+ })
27
+
28
+ export const PresetSchema = z.object({
29
+ name: z.string(),
30
+ enabled: z.boolean().default(true),
31
+ ask_clarifying_questions: z.boolean().default(true),
32
+ curator_synthesis: z.boolean().default(false),
33
+ stream_aggregator: z.boolean().default(true),
34
+ quorum_enabled: z.boolean().default(false),
35
+ grace_period_sec: z.number().default(10),
36
+ reference_models: z.array(ModelSlotSchema).default([]),
37
+ aggregator: ModelSlotSchema.default({ provider: '', model: '' }),
38
+ aggregator_fallbacks: z.array(ModelSlotSchema).default([]),
39
+ reference_temperature: z.number().default(0.6),
40
+ aggregator_temperature: z.number().default(0.4),
41
+ reference_timeout_sec: z.number().default(60),
42
+ aggregator_timeout_sec: z.number().default(180),
43
+ blind_evaluation: z.boolean().default(false),
44
+ peer_critique_enabled: z.boolean().default(false),
45
+ allow_candidate_override: z.boolean().default(false),
46
+ max_tokens: z.number().default(4096),
47
+ judge_criteria: z.string().default(''),
48
+ test_gate_enabled: z.boolean().default(false),
49
+ test_command: z.string().default(''),
50
+ test_gate_timeout_sec: z.number().default(15),
51
+ multi_judge_enabled: z.boolean().default(false),
52
+ judge_models: z.array(ModelSlotSchema).default([]),
53
+ judge_voting_strategy: z.string().default('majority'),
54
+ composite_merge_enabled: z.boolean().default(false),
55
+ smart_routing_enabled: z.boolean().default(false),
56
+ smart_routing_model: ModelSlotSchema.default({ provider: '', model: '' }),
57
+ budget_guard_enabled: z.boolean().default(false),
58
+ max_budget_usd: z.number().default(0),
59
+ budget_action: z.string().default('trim'),
60
+ temperature_gradient_enabled: z.boolean().default(false),
61
+ multi_turn_enabled: z.boolean().default(true),
62
+ report_generation_enabled: z.boolean().default(false),
63
+ local_fallback_enabled: z.boolean().default(false),
64
+ local_fallback_models: z.array(ModelSlotSchema).default([]),
65
+ })
66
+
67
+ export const Config = z.object({
68
+ enabled: z.boolean().default(true).volatile(),
69
+ default_preset: z.string().default('default').volatile(),
70
+ smart_routing_enabled: z.boolean().default(false).volatile(),
71
+ smart_routing_model: ModelSlotSchema.default({ provider: '', model: '' }).volatile(),
72
+ prices: z.dict(PriceRow).default({}).volatile(),
73
+ presets: z.array(PresetSchema).default([
74
+ {
75
+ name: 'default',
76
+ enabled: true,
77
+ ask_clarifying_questions: true,
78
+ reference_models: [
79
+ { provider: '', model: '', role_persona: 'proposer' },
80
+ { provider: '', model: '', role_persona: 'challenger' },
81
+ ],
82
+ aggregator: { provider: '', model: '' },
83
+ reference_temperature: 0.6,
84
+ aggregator_temperature: 0.4,
85
+ max_tokens: 4096,
86
+ judge_criteria: '',
87
+ },
88
+ {
89
+ name: 'fast',
90
+ enabled: true,
91
+ ask_clarifying_questions: false,
92
+ reference_models: [
93
+ { provider: '', model: '', role_persona: 'fast' },
94
+ ],
95
+ aggregator: { provider: '', model: '' },
96
+ reference_temperature: 0.6,
97
+ aggregator_temperature: 0.2,
98
+ max_tokens: 4096,
99
+ judge_criteria: '',
100
+ },
101
+ {
102
+ name: 'deep-reasoning',
103
+ enabled: true,
104
+ ask_clarifying_questions: true,
105
+ reference_models: [
106
+ { provider: '', model: '', role_persona: 'architect' },
107
+ { provider: '', model: '', role_persona: 'critic' },
108
+ ],
109
+ aggregator: { provider: '', model: '' },
110
+ reference_temperature: 0.6,
111
+ aggregator_temperature: 0.2,
112
+ max_tokens: 8192,
113
+ judge_criteria: '',
114
+ },
115
+ ]).volatile(),
116
+ })
117
+
118
+ export function plainConfig(value) {
119
+ if (value && typeof value === 'object' && typeof value.get === 'function') {
120
+ return plainConfig(value.get())
121
+ }
122
+ if (Array.isArray(value)) {
123
+ return value.map(plainConfig)
124
+ }
125
+ if (value !== null && typeof value === 'object') {
126
+ return Object.fromEntries(
127
+ Object.entries(value).map(([key, child]) => [key, plainConfig(child)])
128
+ )
129
+ }
130
+ return value
131
+ }
package/lib/moa-stream.js CHANGED
@@ -13,53 +13,62 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
13
13
  const signal = options?.signal
14
14
  if (signal?.aborted) return
15
15
 
16
- yield { type: 'block-start', index: 0, blockType: 'text' }
17
- yield { type: 'text-delta', index: 0, text: '🧠 *Mixture of Agents started...*\n\n' }
18
-
19
- // Async push-queue for zero-latency live delta streaming
20
- const queue = []
21
- let notify = null
22
-
23
- const pushUpdate = (text) => {
24
- queue.push({ type: 'text-delta', index: 0, text })
25
- if (notify) {
26
- notify()
27
- notify = null
28
- }
16
+ const internalAbortCtrl = new AbortController()
17
+ let combinedSignal = internalAbortCtrl.signal
18
+ if (typeof AbortSignal?.any === 'function' && (signal instanceof AbortSignal)) {
19
+ combinedSignal = AbortSignal.any([signal, internalAbortCtrl.signal])
20
+ } else if (signal && typeof signal.addEventListener === 'function') {
21
+ signal.addEventListener('abort', () => internalAbortCtrl.abort(), { once: true })
29
22
  }
30
23
 
31
24
  let done = false
32
- const pipelinePromise = runMoAPipeline({
33
- userPrompt,
34
- messages,
35
- preset: targetPreset,
36
- callLlm,
37
- cwd,
38
- onProgress: pushUpdate,
39
- onStreamDelta: (delta) => pushUpdate(delta),
40
- prices,
41
- historyFilePath,
42
- liveCanvas,
43
- webServerPort,
44
- timeMachineEngine,
45
- force,
46
- signal,
47
- })
48
- .catch((err) => ({ error: err }))
49
- .finally(() => {
50
- done = true
25
+
26
+ try {
27
+ yield { type: 'block-start', index: 0, blockType: 'text' }
28
+ yield { type: 'text-delta', index: 0, text: '🧠 *Mixture of Agents started...*\n\n' }
29
+
30
+ // Async push-queue for zero-latency live delta streaming
31
+ const queue = []
32
+ let notify = null
33
+
34
+ const pushUpdate = (text) => {
35
+ queue.push({ type: 'text-delta', index: 0, text })
51
36
  if (notify) {
52
37
  notify()
53
38
  notify = null
54
39
  }
55
- })
40
+ }
56
41
 
57
- const startTime = Date.now()
58
- let lastYieldTime = Date.now()
42
+ const pipelinePromise = runMoAPipeline({
43
+ userPrompt,
44
+ messages,
45
+ preset: targetPreset,
46
+ callLlm,
47
+ cwd,
48
+ onProgress: pushUpdate,
49
+ onStreamDelta: (delta) => pushUpdate(delta),
50
+ prices,
51
+ historyFilePath,
52
+ liveCanvas,
53
+ webServerPort,
54
+ timeMachineEngine,
55
+ force,
56
+ signal: combinedSignal,
57
+ })
58
+ .catch((err) => ({ error: err }))
59
+ .finally(() => {
60
+ done = true
61
+ if (notify) {
62
+ notify()
63
+ notify = null
64
+ }
65
+ })
66
+
67
+ const startTime = Date.now()
68
+ let lastYieldTime = Date.now()
59
69
 
60
- try {
61
70
  while (!done || queue.length > 0) {
62
- if (signal?.aborted) {
71
+ if (combinedSignal.aborted) {
63
72
  await cleanMoaWorkspaces(cwd)
64
73
  return
65
74
  }
@@ -86,7 +95,7 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
86
95
  }
87
96
 
88
97
  const result = await pipelinePromise
89
- if (signal?.aborted) {
98
+ if (combinedSignal.aborted) {
90
99
  await cleanMoaWorkspaces(cwd)
91
100
  return
92
101
  }
@@ -115,6 +124,9 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
115
124
  }
116
125
  yield { type: 'finish', reason: { kind: 'stop' } }
117
126
  } finally {
118
- // cleanup
127
+ internalAbortCtrl.abort()
128
+ if (!done) {
129
+ await cleanMoaWorkspaces(cwd).catch(() => {})
130
+ }
119
131
  }
120
132
  }
package/lib/pricing.js CHANGED
@@ -25,7 +25,6 @@ export function getDefaultCacheFile() {
25
25
  return join(baseDir, 'storages', 'dsh-moa-catalog.json')
26
26
  }
27
27
 
28
- export const CACHE_FILE = getDefaultCacheFile()
29
28
  export const REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1000 // 24 hours
30
29
 
31
30
  /**
@@ -34,8 +33,6 @@ export const REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1000 // 24 hours
34
33
  export const DIRECT_VENDOR_RATES = {
35
34
  'deepseek-chat': { input: 0.14, output: 0.28, cacheHit: 0.014 },
36
35
  'deepseek-reasoner': { input: 0.55, output: 2.19, cacheHit: 0.14 },
37
- 'deepseek-v4-flash': { input: 0.14, output: 0.28, cacheHit: 0.014 },
38
- 'deepseek-v4-pro': { input: 0.55, output: 2.19, cacheHit: 0.044 },
39
36
  }
40
37
 
41
38
  /** Default fallback rate for uncataloged models ($0.50 prompt / $1.50 completion per 1M tokens) */
package/lib/routes.js CHANGED
@@ -198,9 +198,13 @@ ctx.effect(() => {
198
198
  const raw = await readBody(req)
199
199
  const payload = JSON.parse(raw)
200
200
  const newConfig = {
201
+ ...cfg,
201
202
  enabled: typeof payload.enabled === 'boolean' ? payload.enabled : cfg.enabled !== false,
202
203
  default_preset: payload.default_preset || cfg.default_preset || 'default',
203
204
  presets: Array.isArray(payload.presets) ? payload.presets : (cfg.presets || []),
205
+ ...(payload.prices !== undefined ? { prices: payload.prices } : {}),
206
+ ...(payload.smart_routing_enabled !== undefined ? { smart_routing_enabled: payload.smart_routing_enabled } : {}),
207
+ ...(payload.smart_routing_model !== undefined ? { smart_routing_model: payload.smart_routing_model } : {}),
204
208
  }
205
209
 
206
210
  // Validate the merged payload through the plugin schema before it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-moa",
3
- "version": "0.2.34",
3
+ "version": "0.2.35",
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",