@goodandready/dsh-moa 0.2.3 → 0.2.5
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 +21 -0
- package/docs/README.ru.md +21 -0
- package/lib/client.js +226 -124
- package/lib/file-workspace.js +94 -5
- package/lib/history.js +191 -0
- package/lib/index.js +221 -188
- package/lib/moa-runner.js +216 -87
- package/lib/pricing.js +194 -0
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -1,218 +1,236 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { refreshCatalogInBackground } from './pricing.js'
|
|
2
|
+
/**
|
|
3
|
+
* DeepSeek Harness Mixture of Agents (MoA) Plugin
|
|
4
|
+
*
|
|
5
|
+
* Implements an intelligent ensemble pipeline:
|
|
6
|
+
* 1. User issues /moa <prompt>
|
|
7
|
+
* 2. Parallel candidate proposers write to isolated workspaces (.moa/candidate-X/)
|
|
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
|
|
10
|
+
* 5. Native cost tracking, history logging and leaderboard analytics
|
|
11
|
+
*/
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
import z from '@deepseek-ai/schemastery'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import os from 'node:os'
|
|
16
|
+
import {
|
|
17
|
+
parseMoACommand,
|
|
18
|
+
runMoAPipeline,
|
|
19
|
+
MoaRunnerAdapter,
|
|
20
|
+
} from './moa-runner.js'
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
getMoaHistory,
|
|
24
|
+
getMoaLeaderboard,
|
|
25
|
+
getMoaRunById,
|
|
26
|
+
} from './history.js'
|
|
27
|
+
|
|
28
|
+
export const name = 'dsh-moa'
|
|
29
|
+
export const inject = ['webServer', 'llm', 'settings', 'sessions', 'tools']
|
|
30
|
+
|
|
31
|
+
export const NS = 'dsh-moa'
|
|
32
|
+
|
|
33
|
+
export const PriceRow = z.object({
|
|
34
|
+
input: z.number().default(0),
|
|
35
|
+
output: z.number().default(0),
|
|
36
|
+
cacheHit: z.number().default(0),
|
|
37
|
+
})
|
|
10
38
|
|
|
11
39
|
export const ModelSlotSchema = z.object({
|
|
12
|
-
provider: z.string().default(''),
|
|
13
|
-
model: z.string().default(''),
|
|
40
|
+
provider: z.string().default('opencode-go'),
|
|
41
|
+
model: z.string().default('deepseek-v4-flash'),
|
|
14
42
|
})
|
|
15
43
|
|
|
16
44
|
export const PresetSchema = z.object({
|
|
17
|
-
name: z.string()
|
|
45
|
+
name: z.string(),
|
|
18
46
|
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),
|
|
47
|
+
reference_models: z.array(ModelSlotSchema).default([]),
|
|
48
|
+
aggregator: ModelSlotSchema.default({ provider: 'codex', model: 'gpt-5.6-sol' }),
|
|
49
|
+
reference_temperature: z.number().default(0.6),
|
|
50
|
+
aggregator_temperature: z.number().default(0.4),
|
|
51
|
+
max_tokens: z.number().default(4096),
|
|
52
|
+
judge_criteria: z.string().default(''),
|
|
53
|
+
judge_mode: z.string().default('auto'),
|
|
30
54
|
})
|
|
31
55
|
|
|
32
56
|
export const Config = z.object({
|
|
33
57
|
enabled: z.boolean().default(true),
|
|
34
58
|
default_preset: z.string().default('default'),
|
|
59
|
+
prices: z.dict(PriceRow).default({}),
|
|
35
60
|
presets: z.array(PresetSchema).default([
|
|
36
61
|
{
|
|
37
62
|
name: 'default',
|
|
38
63
|
enabled: true,
|
|
39
64
|
reference_models: [
|
|
40
65
|
{ provider: 'opencode-go', model: 'deepseek-v4-flash' },
|
|
41
|
-
{ provider: '
|
|
66
|
+
{ provider: 'grok', model: 'grok-build-0.1' },
|
|
42
67
|
],
|
|
43
|
-
aggregator: {
|
|
44
|
-
provider: 'antigravity',
|
|
45
|
-
model: 'gemini-3-flash',
|
|
46
|
-
},
|
|
68
|
+
aggregator: { provider: 'codex', model: 'gpt-5.6-sol' },
|
|
47
69
|
reference_temperature: 0.6,
|
|
48
70
|
aggregator_temperature: 0.4,
|
|
49
71
|
max_tokens: 4096,
|
|
72
|
+
judge_criteria: '',
|
|
73
|
+
judge_mode: 'auto',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'fast',
|
|
77
|
+
enabled: true,
|
|
78
|
+
reference_models: [
|
|
79
|
+
{ provider: 'opencode-go', model: 'deepseek-v4-flash' },
|
|
80
|
+
],
|
|
81
|
+
aggregator: { provider: 'opencode-go', model: 'deepseek-v4-flash' },
|
|
82
|
+
reference_temperature: 0.6,
|
|
83
|
+
aggregator_temperature: 0.2,
|
|
84
|
+
max_tokens: 4096,
|
|
85
|
+
judge_criteria: '',
|
|
86
|
+
judge_mode: 'auto',
|
|
50
87
|
},
|
|
51
88
|
{
|
|
52
89
|
name: 'deep-reasoning',
|
|
53
90
|
enabled: true,
|
|
54
91
|
reference_models: [
|
|
55
|
-
{ provider: 'grok', model: 'grok-
|
|
56
|
-
{ provider: '
|
|
92
|
+
{ provider: 'grok', model: 'grok-build-0.1' },
|
|
93
|
+
{ provider: 'commandcode', model: 'deepseek/deepseek-v4-flash' },
|
|
57
94
|
],
|
|
58
|
-
aggregator: {
|
|
59
|
-
|
|
60
|
-
model: 'gemini-3-flash',
|
|
61
|
-
},
|
|
62
|
-
reference_temperature: 0.7,
|
|
95
|
+
aggregator: { provider: 'codex', model: 'gpt-5.6-sol' },
|
|
96
|
+
reference_temperature: 0.6,
|
|
63
97
|
aggregator_temperature: 0.2,
|
|
64
98
|
max_tokens: 8192,
|
|
99
|
+
judge_criteria: 'Приоритет: математическая строгость и отсутствие галлюцинаций',
|
|
100
|
+
judge_mode: 'auto',
|
|
65
101
|
},
|
|
66
102
|
]),
|
|
67
103
|
})
|
|
68
104
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} catch {
|
|
76
|
-
/* socket closed */
|
|
77
|
-
}
|
|
105
|
+
function writeJson(res, status, body) {
|
|
106
|
+
res.writeHead(status, {
|
|
107
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
108
|
+
'Cache-Control': 'no-store',
|
|
109
|
+
})
|
|
110
|
+
res.end(JSON.stringify(body))
|
|
78
111
|
}
|
|
79
112
|
|
|
80
113
|
function readBody(req) {
|
|
81
114
|
return new Promise((resolve, reject) => {
|
|
82
|
-
|
|
83
|
-
req.on('data', (
|
|
84
|
-
|
|
115
|
+
let acc = ''
|
|
116
|
+
req.on('data', (chunk) => {
|
|
117
|
+
acc += chunk
|
|
118
|
+
if (acc.length > 5 * 1024 * 1024) {
|
|
119
|
+
req.destroy()
|
|
120
|
+
reject(new Error('Payload Too Large'))
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
req.on('end', () => resolve(acc))
|
|
85
124
|
req.on('error', reject)
|
|
86
125
|
})
|
|
87
126
|
}
|
|
88
127
|
|
|
89
|
-
|
|
90
128
|
export function apply(ctx, config) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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 {}
|
|
129
|
+
refreshCatalogInBackground()
|
|
130
|
+
|
|
131
|
+
let settingsScope = null
|
|
132
|
+
let getConfig = () => config
|
|
133
|
+
const live = () => Config(structuredClone(getConfig() ?? {})) ?? config
|
|
106
134
|
|
|
107
135
|
ctx.inject(['settings'], (sctx) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
try {
|
|
137
|
+
const scope = sctx.settings.register(NS, Config, { base: config })
|
|
138
|
+
settingsScope = scope
|
|
139
|
+
getConfig = () => scope.get() ?? config
|
|
140
|
+
sctx.effect(() => () => {
|
|
141
|
+
settingsScope = null
|
|
142
|
+
getConfig = () => config
|
|
143
|
+
}, 'dsh-moa: settings')
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.warn('[dsh-moa] Settings registration warning:', e)
|
|
113
146
|
}
|
|
114
|
-
sctx.effect(() => () => {
|
|
115
|
-
settingsApi = null
|
|
116
|
-
})
|
|
117
147
|
})
|
|
118
148
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Unified LLM dispatch function using ctx.llm.prepareCall / stream
|
|
151
|
+
*/
|
|
152
|
+
const callLlm = async ({ provider, model, messages, temperature = 0.6, maxTokens = 4096 }) => {
|
|
153
|
+
if (!ctx.llm) {
|
|
154
|
+
throw new Error('ctx.llm is not available in cordis context')
|
|
123
155
|
}
|
|
124
|
-
return currentConfig || config
|
|
125
|
-
}
|
|
126
156
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
} else if (chunk && chunk.type === 'finish' && chunk.reason && chunk.reason.kind === 'error') {
|
|
141
|
-
throw new Error(chunk.reason.failure?.message || 'LLM stream failed')
|
|
157
|
+
let prep
|
|
158
|
+
try {
|
|
159
|
+
// DSH 0.1.2-rc.1 API expects { provider, model } object
|
|
160
|
+
prep = await ctx.llm.prepareCall({ provider, model })
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (typeof ctx.llm.prepareCall === 'function') {
|
|
163
|
+
try {
|
|
164
|
+
prep = await ctx.llm.prepareCall(provider, model)
|
|
165
|
+
} catch {
|
|
166
|
+
throw err
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
throw err
|
|
142
170
|
}
|
|
143
171
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// Adapter to ctx.llm.stream
|
|
148
|
-
const callLlm = async (params) => {
|
|
149
|
-
if (!ctx.llm || typeof ctx.llm.stream !== 'function') {
|
|
150
|
-
throw new Error('ctx.llm.stream service is not available')
|
|
172
|
+
if (!prep || typeof prep.stream !== 'function') {
|
|
173
|
+
throw new Error(`LLM provider/model ${provider}:${model} could not be prepared`)
|
|
151
174
|
}
|
|
152
175
|
|
|
153
|
-
const
|
|
176
|
+
const abortCtrl = new AbortController()
|
|
177
|
+
const timeoutId = setTimeout(() => abortCtrl.abort(new Error('LLM call timeout')), 120000)
|
|
178
|
+
timeoutId.unref?.()
|
|
154
179
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
content = content.map((b) => (typeof b === 'string' ? { type: 'text', text: b } : b))
|
|
163
|
-
} else {
|
|
164
|
-
content = [{ type: 'text', text: String(content || '') }]
|
|
165
|
-
}
|
|
180
|
+
try {
|
|
181
|
+
const stream = prep.stream({
|
|
182
|
+
messages,
|
|
183
|
+
temperature,
|
|
184
|
+
maxTokens,
|
|
185
|
+
signal: abortCtrl.signal,
|
|
186
|
+
})
|
|
166
187
|
|
|
167
|
-
|
|
168
|
-
|
|
188
|
+
let fullText = ''
|
|
189
|
+
let usageInfo = null
|
|
169
190
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
191
|
+
for await (const chunk of stream) {
|
|
192
|
+
if (chunk.type === 'text-delta' && typeof chunk.text === 'string') {
|
|
193
|
+
fullText += chunk.text
|
|
194
|
+
} else if (chunk.type === 'block-end' && chunk.block?.text) {
|
|
195
|
+
if (!fullText) fullText = chunk.block.text
|
|
196
|
+
} else if (chunk.type === 'usage' && chunk.usage) {
|
|
197
|
+
usageInfo = chunk.usage
|
|
198
|
+
}
|
|
174
199
|
}
|
|
175
|
-
})
|
|
176
200
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
201
|
+
clearTimeout(timeoutId)
|
|
202
|
+
return {
|
|
203
|
+
content: fullText,
|
|
204
|
+
text: fullText,
|
|
205
|
+
usage: usageInfo || {
|
|
206
|
+
inputTokens: Math.round(JSON.stringify(messages).length / 4),
|
|
207
|
+
outputTokens: Math.round(fullText.length / 4),
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
} catch (err) {
|
|
211
|
+
clearTimeout(timeoutId)
|
|
212
|
+
throw err
|
|
189
213
|
}
|
|
190
|
-
|
|
191
|
-
const chunks = ctx.llm.stream(streamOpts)
|
|
192
|
-
|
|
193
|
-
return await collectStreamText(chunks)
|
|
194
214
|
}
|
|
195
215
|
|
|
196
|
-
//
|
|
216
|
+
// Route: /dsh-moa/status
|
|
197
217
|
ctx.effect(() => {
|
|
198
218
|
return ctx.webServer.register({
|
|
199
219
|
kind: 'exact',
|
|
200
220
|
path: '/dsh-moa/status',
|
|
201
221
|
handler: (_req, res) => {
|
|
202
|
-
const cfg =
|
|
222
|
+
const cfg = live()
|
|
203
223
|
writeJson(res, 200, {
|
|
204
224
|
ok: true,
|
|
205
225
|
enabled: cfg.enabled !== false,
|
|
206
226
|
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
227
|
presetsCount: (cfg.presets || []).length,
|
|
211
228
|
})
|
|
212
229
|
},
|
|
213
230
|
})
|
|
214
231
|
}, 'dsh-moa: status route')
|
|
215
232
|
|
|
233
|
+
// Route: /dsh-moa/models
|
|
216
234
|
ctx.effect(() => {
|
|
217
235
|
return ctx.webServer.register({
|
|
218
236
|
kind: 'exact',
|
|
@@ -221,43 +239,25 @@ export function apply(ctx, config) {
|
|
|
221
239
|
const result = []
|
|
222
240
|
const seen = new Set()
|
|
223
241
|
|
|
224
|
-
const add = (
|
|
225
|
-
if (!prov || !mod) return
|
|
226
|
-
const p = String(prov).trim()
|
|
227
|
-
const m = String(mod).trim()
|
|
242
|
+
const add = (p, m) => {
|
|
228
243
|
if (!p || !m) return
|
|
229
244
|
const key = `${p}:${m}`
|
|
230
|
-
if (seen.has(key))
|
|
231
|
-
|
|
232
|
-
|
|
245
|
+
if (!seen.has(key)) {
|
|
246
|
+
seen.add(key)
|
|
247
|
+
result.push({ provider: p, model: m, label: key })
|
|
248
|
+
}
|
|
233
249
|
}
|
|
234
250
|
|
|
235
|
-
// ONLY query REAL models from runtime adapters & settings. NO hardcoded fallbacks!
|
|
236
251
|
if (ctx.llm) {
|
|
237
252
|
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
|
-
}
|
|
253
|
+
if (typeof ctx.llm.listAvailableModels === 'function') {
|
|
254
|
+
const list = await ctx.llm.listAvailableModels()
|
|
255
|
+
for (const item of (list || [])) {
|
|
256
|
+
if (item?.provider && item?.id) add(item.provider, item.id)
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
} catch (e) {
|
|
260
|
-
console.warn('[dsh-moa] ctx.llm.
|
|
260
|
+
console.warn('[dsh-moa] ctx.llm.listAvailableModels error:', e)
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
try {
|
|
@@ -317,12 +317,13 @@ export function apply(ctx, config) {
|
|
|
317
317
|
})
|
|
318
318
|
}, 'dsh-moa: models route')
|
|
319
319
|
|
|
320
|
+
// Route: /dsh-moa/presets
|
|
320
321
|
ctx.effect(() => {
|
|
321
322
|
return ctx.webServer.register({
|
|
322
323
|
kind: 'exact',
|
|
323
324
|
path: '/dsh-moa/presets',
|
|
324
325
|
handler: async (req, res) => {
|
|
325
|
-
const cfg =
|
|
326
|
+
const cfg = live()
|
|
326
327
|
if (req.method === 'GET') {
|
|
327
328
|
writeJson(res, 200, {
|
|
328
329
|
ok: true,
|
|
@@ -342,24 +343,12 @@ export function apply(ctx, config) {
|
|
|
342
343
|
presets: Array.isArray(payload.presets) ? payload.presets : (cfg.presets || []),
|
|
343
344
|
}
|
|
344
345
|
|
|
345
|
-
//
|
|
346
|
-
if (
|
|
347
|
-
await
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
// 2. Direct persistence to ~/.dsh/settings.yaml
|
|
352
|
-
try {
|
|
353
|
-
const fs = await import('node:fs')
|
|
354
|
-
const yaml = await import('yaml')
|
|
355
|
-
const p = path.join(os.homedir(), '.dsh', 'settings.yaml')
|
|
356
|
-
if (fs.existsSync(p)) {
|
|
357
|
-
const curYaml = yaml.parse(fs.readFileSync(p, 'utf8')) || {}
|
|
358
|
-
curYaml[NS] = newConfig
|
|
359
|
-
fs.writeFileSync(p, yaml.stringify(curYaml), 'utf8')
|
|
360
|
-
}
|
|
361
|
-
} catch (fsErr) {
|
|
362
|
-
console.warn('[dsh-moa] direct file write error:', fsErr)
|
|
346
|
+
// Persist via Cordis Settings API
|
|
347
|
+
if (settingsScope && typeof settingsScope.replace === 'function') {
|
|
348
|
+
await settingsScope.replace(newConfig)
|
|
349
|
+
} else {
|
|
350
|
+
// In-memory fallback if settings service is unavailable
|
|
351
|
+
getConfig = () => newConfig
|
|
363
352
|
}
|
|
364
353
|
|
|
365
354
|
writeJson(res, 200, {
|
|
@@ -378,6 +367,50 @@ export function apply(ctx, config) {
|
|
|
378
367
|
})
|
|
379
368
|
}, 'dsh-moa: presets route')
|
|
380
369
|
|
|
370
|
+
// Route: /dsh-moa/history (#9)
|
|
371
|
+
ctx.effect(() => {
|
|
372
|
+
return ctx.webServer.register({
|
|
373
|
+
kind: 'exact',
|
|
374
|
+
path: '/dsh-moa/history',
|
|
375
|
+
handler: async (req, res) => {
|
|
376
|
+
if (req.method !== 'GET') {
|
|
377
|
+
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const u = new URL(req.url, 'http://localhost')
|
|
382
|
+
const limit = parseInt(u.searchParams.get('limit') || '20', 10)
|
|
383
|
+
const offset = parseInt(u.searchParams.get('offset') || '0', 10)
|
|
384
|
+
const data = getMoaHistory(limit, offset)
|
|
385
|
+
writeJson(res, 200, { ok: true, ...data })
|
|
386
|
+
} catch (err) {
|
|
387
|
+
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
})
|
|
391
|
+
}, 'dsh-moa: history route')
|
|
392
|
+
|
|
393
|
+
// Route: /dsh-moa/leaderboard (#11)
|
|
394
|
+
ctx.effect(() => {
|
|
395
|
+
return ctx.webServer.register({
|
|
396
|
+
kind: 'exact',
|
|
397
|
+
path: '/dsh-moa/leaderboard',
|
|
398
|
+
handler: async (req, res) => {
|
|
399
|
+
if (req.method !== 'GET') {
|
|
400
|
+
writeJson(res, 405, { ok: false, error: 'GET required' })
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
const data = getMoaLeaderboard()
|
|
405
|
+
writeJson(res, 200, { ok: true, ...data })
|
|
406
|
+
} catch (err) {
|
|
407
|
+
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
})
|
|
411
|
+
}, 'dsh-moa: leaderboard route')
|
|
412
|
+
|
|
413
|
+
// Route: /dsh-moa/run
|
|
381
414
|
ctx.effect(() => {
|
|
382
415
|
return ctx.webServer.register({
|
|
383
416
|
kind: 'exact',
|
|
@@ -397,7 +430,7 @@ export function apply(ctx, config) {
|
|
|
397
430
|
return
|
|
398
431
|
}
|
|
399
432
|
|
|
400
|
-
const cfg =
|
|
433
|
+
const cfg = live()
|
|
401
434
|
const presetName = payload.preset || cfg.default_preset || 'default'
|
|
402
435
|
const preset = (cfg.presets || []).find((p) => p.name === presetName) || cfg.presets?.[0]
|
|
403
436
|
|
|
@@ -429,7 +462,7 @@ export function apply(ctx, config) {
|
|
|
429
462
|
? lastUser.content.filter((p) => p.type === 'text').map((p) => p.text).join('\n')
|
|
430
463
|
: '')
|
|
431
464
|
|
|
432
|
-
const cfg =
|
|
465
|
+
const cfg = live()
|
|
433
466
|
const parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: 'default' }
|
|
434
467
|
const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName) || cfg.presets?.[0]
|
|
435
468
|
|
|
@@ -466,7 +499,7 @@ export function apply(ctx, config) {
|
|
|
466
499
|
: '')
|
|
467
500
|
|
|
468
501
|
if (userText.startsWith('/moa')) {
|
|
469
|
-
const cfg =
|
|
502
|
+
const cfg = live()
|
|
470
503
|
if (cfg.enabled !== false) {
|
|
471
504
|
if (event.signal) {
|
|
472
505
|
moaPendingSignals.add(event.signal)
|