@goodandready/dsh-moa 0.2.5 → 0.2.7
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/client.js +57 -5
- package/lib/index.js +45 -46
- package/lib/moa-runner.js +62 -100
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -888,14 +888,45 @@ window.__ModuleLoader__.load({
|
|
|
888
888
|
|
|
889
889
|
function useMoASettings(props) {
|
|
890
890
|
const t = makeT(rootCtx, props)
|
|
891
|
+
const effectiveCtx = (props && props.ctx) || rootCtx
|
|
892
|
+
|
|
893
|
+
// Reactive binding to settingsScope if available
|
|
894
|
+
const scope = React.useMemo(() => {
|
|
895
|
+
if (!effectiveCtx) return undefined
|
|
896
|
+
const s = effectiveCtx.get ? effectiveCtx.get('settingsScope') : effectiveCtx.settingsScope
|
|
897
|
+
return s && typeof s.bind === 'function' ? s.bind({ namespace: NS }) : undefined
|
|
898
|
+
}, [effectiveCtx])
|
|
899
|
+
|
|
900
|
+
const snapshot = React.useSyncExternalStore
|
|
901
|
+
? React.useSyncExternalStore(
|
|
902
|
+
React.useMemo(() => (cb) => (scope ? scope.subscribe(cb) : () => {}), [scope]),
|
|
903
|
+
React.useCallback(() => (scope ? scope.getSnapshot() : { status: 'ready' }), [scope]),
|
|
904
|
+
React.useCallback(() => ({ status: 'loading' }), [])
|
|
905
|
+
)
|
|
906
|
+
: null
|
|
907
|
+
|
|
891
908
|
const [status, setStatus] = React.useState('loading')
|
|
892
909
|
const [presets, setPresets] = React.useState([])
|
|
893
910
|
const [defaultPreset, setDefaultPreset] = React.useState('default')
|
|
894
911
|
const [availableModels, setAvailableModels] = React.useState([])
|
|
895
912
|
const [saveStatus, setSaveStatus] = React.useState('')
|
|
896
913
|
|
|
914
|
+
// Sync from settingsScope snapshot when available
|
|
915
|
+
React.useEffect(() => {
|
|
916
|
+
if (!scope || !snapshot) return
|
|
917
|
+
const snapStatus = snapshot.status || 'ready'
|
|
918
|
+
if (snapStatus === 'ready') {
|
|
919
|
+
const val = snapshot.value || {}
|
|
920
|
+
if (Array.isArray(val.presets)) setPresets(val.presets)
|
|
921
|
+
if (val.default_preset) setDefaultPreset(val.default_preset)
|
|
922
|
+
setStatus('ready')
|
|
923
|
+
} else if (snapStatus === 'unavailable') {
|
|
924
|
+
setStatus('unavailable')
|
|
925
|
+
}
|
|
926
|
+
}, [scope, snapshot])
|
|
927
|
+
|
|
897
928
|
const reload = React.useCallback(() => {
|
|
898
|
-
setStatus('loading')
|
|
929
|
+
if (!scope) setStatus('loading')
|
|
899
930
|
|
|
900
931
|
fetch('/dsh-moa/presets', { cache: 'no-store' })
|
|
901
932
|
.then((r) => {
|
|
@@ -907,13 +938,13 @@ window.__ModuleLoader__.load({
|
|
|
907
938
|
setPresets(data.presets || [])
|
|
908
939
|
setDefaultPreset(data.defaultPreset || 'default')
|
|
909
940
|
setStatus('ready')
|
|
910
|
-
} else {
|
|
941
|
+
} else if (!scope) {
|
|
911
942
|
setStatus('unavailable')
|
|
912
943
|
}
|
|
913
944
|
})
|
|
914
945
|
.catch((err) => {
|
|
915
946
|
console.warn('[dsh-moa] Presets fetch failed:', err)
|
|
916
|
-
setStatus('unavailable')
|
|
947
|
+
if (!scope) setStatus('unavailable')
|
|
917
948
|
})
|
|
918
949
|
|
|
919
950
|
fetch('/dsh-moa/models', { cache: 'no-store' })
|
|
@@ -926,7 +957,7 @@ window.__ModuleLoader__.load({
|
|
|
926
957
|
.catch((err) => {
|
|
927
958
|
console.warn('[dsh-moa] Models fetch failed:', err)
|
|
928
959
|
})
|
|
929
|
-
}, [])
|
|
960
|
+
}, [scope])
|
|
930
961
|
|
|
931
962
|
React.useEffect(() => {
|
|
932
963
|
reload()
|
|
@@ -934,6 +965,27 @@ window.__ModuleLoader__.load({
|
|
|
934
965
|
|
|
935
966
|
const handleSave = () => {
|
|
936
967
|
setSaveStatus(t('loading'))
|
|
968
|
+
|
|
969
|
+
if (scope && typeof scope.set === 'function') {
|
|
970
|
+
Promise.all([
|
|
971
|
+
scope.set('presets', presets),
|
|
972
|
+
scope.set('default_preset', defaultPreset),
|
|
973
|
+
])
|
|
974
|
+
.then(() => {
|
|
975
|
+
setSaveStatus(t('saved'))
|
|
976
|
+
setTimeout(() => setSaveStatus(''), 2500)
|
|
977
|
+
})
|
|
978
|
+
.catch((err) => {
|
|
979
|
+
console.warn('[dsh-moa] scope.set failed, trying REST fallback:', err)
|
|
980
|
+
doRestSave()
|
|
981
|
+
})
|
|
982
|
+
return
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
doRestSave()
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const doRestSave = () => {
|
|
937
989
|
fetch('/dsh-moa/presets', {
|
|
938
990
|
method: 'POST',
|
|
939
991
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1111,7 +1163,7 @@ window.__ModuleLoader__.load({
|
|
|
1111
1163
|
)
|
|
1112
1164
|
}
|
|
1113
1165
|
|
|
1114
|
-
exports.inject = ['slots', 'locale', 'inputTriggers']
|
|
1166
|
+
exports.inject = ['slots', 'locale', 'inputTriggers', 'settingsScope']
|
|
1115
1167
|
exports.apply = function apply(ctx) {
|
|
1116
1168
|
rootCtx = ctx
|
|
1117
1169
|
const t = makeT(ctx)
|
package/lib/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import os from 'node:os'
|
|
|
16
16
|
import {
|
|
17
17
|
parseMoACommand,
|
|
18
18
|
runMoAPipeline,
|
|
19
|
-
|
|
19
|
+
streamMoATurn,
|
|
20
20
|
} from './moa-runner.js'
|
|
21
21
|
|
|
22
22
|
import {
|
|
@@ -450,44 +450,9 @@ export function apply(ctx, config) {
|
|
|
450
450
|
})
|
|
451
451
|
}, 'dsh-moa: run pipeline route')
|
|
452
452
|
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const adapter = new MoaRunnerAdapter((options) => {
|
|
457
|
-
const msgs = options.messages || []
|
|
458
|
-
const lastUser = [...msgs].reverse().find((m) => m && m.role === 'user')
|
|
459
|
-
const userText = typeof lastUser?.content === 'string'
|
|
460
|
-
? lastUser.content
|
|
461
|
-
: (Array.isArray(lastUser?.content)
|
|
462
|
-
? lastUser.content.filter((p) => p.type === 'text').map((p) => p.text).join('\n')
|
|
463
|
-
: '')
|
|
464
|
-
|
|
465
|
-
const cfg = live()
|
|
466
|
-
const parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: 'default' }
|
|
467
|
-
const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName) || cfg.presets?.[0]
|
|
468
|
-
|
|
469
|
-
const extraCtx = options.signal ? moaPendingContexts.get(options.signal) : null
|
|
470
|
-
const cwd = options.cwd || extraCtx?.cwd || process.cwd()
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
targetPreset,
|
|
474
|
-
userPrompt: parsed.prompt || userText,
|
|
475
|
-
messages: msgs.slice(0, -1),
|
|
476
|
-
callLlm,
|
|
477
|
-
cwd,
|
|
478
|
-
}
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
const dispose = ctx.llm.registerAdapter(['moa-runner'], adapter)
|
|
482
|
-
return () => {
|
|
483
|
-
if (typeof dispose === 'function') dispose()
|
|
484
|
-
}
|
|
485
|
-
}, 'dsh-moa: llm adapter')
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Session turn interceptor: flag MoA turns and route to moa-runner adapter
|
|
489
|
-
const moaPendingSignals = new WeakSet()
|
|
490
|
-
const moaPendingContexts = new WeakMap()
|
|
453
|
+
// Session turn interceptor: route turn to real aggregator model & stream MoA synthesis
|
|
454
|
+
const moaPendingTurns = new WeakMap()
|
|
455
|
+
const moaSessionTurns = new Map()
|
|
491
456
|
|
|
492
457
|
ctx.on('agent/pre-step', async (event, next) => {
|
|
493
458
|
const messages = event.messages || []
|
|
@@ -501,10 +466,26 @@ export function apply(ctx, config) {
|
|
|
501
466
|
if (userText.startsWith('/moa')) {
|
|
502
467
|
const cfg = live()
|
|
503
468
|
if (cfg.enabled !== false) {
|
|
469
|
+
const parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: cfg.default_preset || 'default' }
|
|
470
|
+
const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName) || cfg.presets?.[0]
|
|
471
|
+
const aggProvider = targetPreset?.aggregator?.provider || 'codex'
|
|
472
|
+
const aggModel = targetPreset?.aggregator?.model || 'gpt-5.6-sol'
|
|
473
|
+
const sessCwd = event.cwd || event.session?.cwd || (event.session?.path ? path.dirname(event.session.path) : undefined) || process.cwd()
|
|
474
|
+
|
|
475
|
+
const turnContext = {
|
|
476
|
+
targetPreset,
|
|
477
|
+
userPrompt: parsed.prompt || userText,
|
|
478
|
+
messages: messages.slice(0, -1),
|
|
479
|
+
cwd: sessCwd,
|
|
480
|
+
aggregator: { provider: aggProvider, model: aggModel },
|
|
481
|
+
callLlm,
|
|
482
|
+
}
|
|
483
|
+
|
|
504
484
|
if (event.signal) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
485
|
+
moaPendingTurns.set(event.signal, turnContext)
|
|
486
|
+
}
|
|
487
|
+
if (event.session?.id) {
|
|
488
|
+
moaSessionTurns.set(event.session.id, turnContext)
|
|
508
489
|
}
|
|
509
490
|
}
|
|
510
491
|
}
|
|
@@ -514,14 +495,32 @@ export function apply(ctx, config) {
|
|
|
514
495
|
|
|
515
496
|
ctx.on('agent/request', async (payload, next) => {
|
|
516
497
|
const resolved = await next()
|
|
517
|
-
|
|
518
|
-
|
|
498
|
+
const turnContext = (payload.signal ? moaPendingTurns.get(payload.signal) : null)
|
|
499
|
+
|| (payload.session?.id ? moaSessionTurns.get(payload.session.id) : null)
|
|
500
|
+
|
|
501
|
+
if (turnContext) {
|
|
519
502
|
return {
|
|
520
503
|
...resolved,
|
|
521
|
-
provider:
|
|
522
|
-
model:
|
|
504
|
+
provider: turnContext.aggregator.provider,
|
|
505
|
+
model: turnContext.aggregator.model,
|
|
523
506
|
}
|
|
524
507
|
}
|
|
525
508
|
return resolved
|
|
526
509
|
})
|
|
510
|
+
|
|
511
|
+
ctx.effect(() => {
|
|
512
|
+
return ctx.on('llm/stream', (options, next) => {
|
|
513
|
+
const turnContext = (options.signal ? moaPendingTurns.get(options.signal) : null)
|
|
514
|
+
|| (options.session?.id ? moaSessionTurns.get(options.session.id) : null)
|
|
515
|
+
|
|
516
|
+
if (turnContext) {
|
|
517
|
+
if (options.signal) moaPendingTurns.delete(options.signal)
|
|
518
|
+
if (options.session?.id) moaSessionTurns.delete(options.session.id)
|
|
519
|
+
|
|
520
|
+
return streamMoATurn(turnContext, options)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return next(options)
|
|
524
|
+
})
|
|
525
|
+
}, 'dsh-moa: llm stream interceptor')
|
|
527
526
|
}
|
package/lib/moa-runner.js
CHANGED
|
@@ -231,7 +231,7 @@ export function parseMoACommand(text, presets = []) {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
const matchPreset = /^--preset
|
|
234
|
+
const matchPreset = /^--preset(?:=|\s+)([a-zA-Z0-9_-]+)\s*(.*)/s.exec(remainder)
|
|
235
235
|
if (matchPreset) {
|
|
236
236
|
return {
|
|
237
237
|
presetName: matchPreset[1],
|
|
@@ -684,118 +684,80 @@ export function formatMoAResponse({ moaResult, presetName }) {
|
|
|
684
684
|
return parts.join('\n')
|
|
685
685
|
}
|
|
686
686
|
|
|
687
|
-
export
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
providerInfo(provider) {
|
|
693
|
-
return { id: provider, name: 'Mixture of Agents Runner' }
|
|
694
|
-
}
|
|
687
|
+
export async function* streamMoATurn({ targetPreset, userPrompt, messages, callLlm, cwd }, options = {}) {
|
|
688
|
+
const signal = options?.signal
|
|
689
|
+
if (signal?.aborted) return
|
|
695
690
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
imageRequestPricing(_provider, _model) {
|
|
701
|
-
return undefined
|
|
702
|
-
}
|
|
691
|
+
yield { type: 'block-start', index: 0, blockType: 'text' }
|
|
692
|
+
yield { type: 'text-delta', index: 0, text: '🧠 *Mixture of Agents запущен...*\n\n' }
|
|
703
693
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
async resolveModel(provider, model) {
|
|
710
|
-
return { provider, id: model, name: 'MoA Ensemble' }
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
async prepareCall(providerOrConfig, modelOrSignal, _signal) {
|
|
714
|
-
const provider = typeof providerOrConfig === 'object' && providerOrConfig !== null ? providerOrConfig.provider : providerOrConfig
|
|
715
|
-
const model = typeof providerOrConfig === 'object' && providerOrConfig !== null ? providerOrConfig.model : modelOrSignal
|
|
716
|
-
return {
|
|
717
|
-
model: { provider, id: model, name: 'MoA Ensemble' },
|
|
718
|
-
stream: (options) => this.stream(options),
|
|
719
|
-
}
|
|
694
|
+
// Queue for streaming progress updates from within runMoAPipeline
|
|
695
|
+
const progressChunks = []
|
|
696
|
+
const onProgress = (text) => {
|
|
697
|
+
progressChunks.push(text)
|
|
720
698
|
}
|
|
721
699
|
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
// Queue for streaming progress updates from within runMoAPipeline
|
|
732
|
-
const progressChunks = []
|
|
733
|
-
const onProgress = (text) => {
|
|
734
|
-
progressChunks.push(text)
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
try {
|
|
738
|
-
const pipelinePromise = runMoAPipeline({
|
|
739
|
-
userPrompt,
|
|
740
|
-
messages,
|
|
741
|
-
preset: targetPreset,
|
|
742
|
-
callLlm,
|
|
743
|
-
cwd,
|
|
744
|
-
onProgress,
|
|
745
|
-
})
|
|
746
|
-
|
|
747
|
-
const startTime = Date.now()
|
|
748
|
-
let lastYieldTime = Date.now()
|
|
749
|
-
|
|
750
|
-
// Yield progressive updates while pipeline runs
|
|
751
|
-
while (true) {
|
|
752
|
-
if (signal?.aborted) return
|
|
700
|
+
try {
|
|
701
|
+
const pipelinePromise = runMoAPipeline({
|
|
702
|
+
userPrompt,
|
|
703
|
+
messages,
|
|
704
|
+
preset: targetPreset,
|
|
705
|
+
callLlm,
|
|
706
|
+
cwd,
|
|
707
|
+
onProgress,
|
|
708
|
+
})
|
|
753
709
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const chunk = progressChunks.shift()
|
|
757
|
-
yield { type: 'text-delta', index: 0, text: chunk }
|
|
758
|
-
emittedAny = true
|
|
759
|
-
lastYieldTime = Date.now()
|
|
760
|
-
}
|
|
710
|
+
const startTime = Date.now()
|
|
711
|
+
let lastYieldTime = Date.now()
|
|
761
712
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
])
|
|
766
|
-
if (done) break
|
|
767
|
-
|
|
768
|
-
const elapsedSec = Math.floor((Date.now() - startTime) / 1000)
|
|
769
|
-
// If 3 seconds passed without any progress message, send a live tick
|
|
770
|
-
if (!emittedAny && Date.now() - lastYieldTime >= 3000) {
|
|
771
|
-
yield { type: 'text-delta', index: 0, text: `⏳ *[${elapsedSec}с] Идёт обработка...*\n` }
|
|
772
|
-
lastYieldTime = Date.now()
|
|
773
|
-
}
|
|
774
|
-
}
|
|
713
|
+
// Yield progressive updates while pipeline runs
|
|
714
|
+
while (true) {
|
|
715
|
+
if (signal?.aborted) return
|
|
775
716
|
|
|
717
|
+
let emittedAny = false
|
|
776
718
|
while (progressChunks.length > 0) {
|
|
777
719
|
const chunk = progressChunks.shift()
|
|
778
720
|
yield { type: 'text-delta', index: 0, text: chunk }
|
|
721
|
+
emittedAny = true
|
|
722
|
+
lastYieldTime = Date.now()
|
|
779
723
|
}
|
|
780
724
|
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
725
|
+
const done = await Promise.race([
|
|
726
|
+
pipelinePromise.then(() => true),
|
|
727
|
+
new Promise((r) => { const t = setTimeout(() => r(false), 500); t.unref?.(); }),
|
|
728
|
+
])
|
|
729
|
+
if (done) break
|
|
730
|
+
|
|
731
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1000)
|
|
732
|
+
// If 3 seconds passed without any progress message, send a live tick
|
|
733
|
+
if (!emittedAny && Date.now() - lastYieldTime >= 3000) {
|
|
734
|
+
yield { type: 'text-delta', index: 0, text: `⏳ *[${elapsedSec}с] Идёт обработка...*\n` }
|
|
735
|
+
lastYieldTime = Date.now()
|
|
736
|
+
}
|
|
737
|
+
}
|
|
788
738
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
yield { type: '
|
|
792
|
-
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
793
|
-
} catch (err) {
|
|
794
|
-
if (signal?.aborted) return
|
|
795
|
-
const errText = '\n\n⚠️ **Ошибка Mixture of Agents**: ' + (err?.message || String(err))
|
|
796
|
-
yield { type: 'text-delta', index: 0, text: errText }
|
|
797
|
-
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
798
|
-
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
739
|
+
while (progressChunks.length > 0) {
|
|
740
|
+
const chunk = progressChunks.shift()
|
|
741
|
+
yield { type: 'text-delta', index: 0, text: chunk }
|
|
799
742
|
}
|
|
743
|
+
|
|
744
|
+
const moaResult = await pipelinePromise
|
|
745
|
+
if (signal?.aborted) return
|
|
746
|
+
|
|
747
|
+
const formatted = formatMoAResponse({
|
|
748
|
+
moaResult,
|
|
749
|
+
presetName: targetPreset?.name || 'default',
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
yield { type: 'text-delta', index: 0, text: '\n---\n\n' + formatted }
|
|
753
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: formatted } }
|
|
754
|
+
yield { type: 'usage', usage: { inputTokens: moaResult?.usage?.totalTokens || 100, outputTokens: formatted.length } }
|
|
755
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
756
|
+
} catch (err) {
|
|
757
|
+
if (signal?.aborted) return
|
|
758
|
+
const errText = '\n\n⚠️ **Ошибка Mixture of Agents**: ' + (err?.message || String(err))
|
|
759
|
+
yield { type: 'text-delta', index: 0, text: errText }
|
|
760
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
761
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
800
762
|
}
|
|
801
763
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-moa",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
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",
|
|
@@ -58,4 +58,4 @@
|
|
|
58
58
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
59
59
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
60
60
|
}
|
|
61
|
-
}
|
|
61
|
+
}
|