@goodandready/dsh-moa 0.2.41 → 0.2.43
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 +7 -0
- package/README.ru.md +7 -0
- package/README.zh.md +11 -0
- package/lib/client.js +421 -107
- package/lib/file-workspace.js +40 -43
- package/lib/history.js +8 -1
- package/lib/index.js +55 -4
- package/lib/live-canvas.js +39 -6
- package/lib/moa-budget.js +28 -19
- package/lib/moa-candidates.js +3 -0
- package/lib/moa-multi-judge.js +2 -1
- package/lib/moa-prompts.js +5 -1
- package/lib/moa-runner.js +48 -46
- package/lib/moa-stream.js +59 -1
- package/lib/moa-test-gate.js +11 -0
- package/lib/routes.js +10 -3
- package/lib/updater.js +23 -0
- package/package.json +1 -1
package/lib/moa-runner.js
CHANGED
|
@@ -18,7 +18,7 @@ export { parseWinnerIndex, parseRecommendedAssembler, parseMoACommand, stripOrSu
|
|
|
18
18
|
export { estimateTokenCost, summarizeMoAUsage } from './pricing.js'
|
|
19
19
|
export { REFERENCE_SYSTEM_PROMPT, callWithTransientRetry, runReferencesParallel } from './moa-candidates.js'
|
|
20
20
|
export { executeTestGateForCandidates, runCandidateTestGate } from './moa-test-gate.js'
|
|
21
|
-
export { streamMoATurn } from './moa-stream.js'
|
|
21
|
+
export { streamMoATurn, streamPromoteTurn } from './moa-stream.js'
|
|
22
22
|
/**
|
|
23
23
|
* Executes the full Mixture of Agents pipeline.
|
|
24
24
|
*/
|
|
@@ -38,7 +38,8 @@ export async function runMoAPipeline({
|
|
|
38
38
|
webServerPort = null, timeMachineEngine = null, timeMachineUrl = null,
|
|
39
39
|
force = isTestEnvironment() ? true : false, checkpoint = true, checkpointFn = null, runId: customRunId = null,
|
|
40
40
|
}) {
|
|
41
|
-
const
|
|
41
|
+
const runId = (customRunId && String(customRunId).trim()) || crypto.randomUUID()
|
|
42
|
+
const promoteOpts = { webServerPort, timeMachineEngine, timeMachineUrl, force: Boolean(force), checkpoint, checkpointFn, signal }
|
|
42
43
|
if (signal?.aborted) return { error: new Error('Turn aborted') }
|
|
43
44
|
const startTime = Date.now()
|
|
44
45
|
// 1. Resolve configurations
|
|
@@ -58,7 +59,7 @@ export async function runMoAPipeline({
|
|
|
58
59
|
const candidateRetries = 1, refTimeoutSec = typeof preset?.reference_timeout_sec === 'number' ? preset.reference_timeout_sec : 60
|
|
59
60
|
const aggTimeoutSec = typeof preset?.aggregator_timeout_sec === 'number' ? preset.aggregator_timeout_sec : 180
|
|
60
61
|
const isBlindEvaluation = Boolean(preset?.blind_evaluation), isPeerCritiqueEnabled = Boolean(preset?.peer_critique_enabled)
|
|
61
|
-
const allowCandidateOverride = Boolean(preset?.allow_candidate_override)
|
|
62
|
+
const allowCandidateOverride = Boolean(preset?.allow_candidate_override)
|
|
62
63
|
// Feature 6: Budget Guardrail check
|
|
63
64
|
const budgetCheck = applyBudgetGuardrails({
|
|
64
65
|
references: referenceModels,
|
|
@@ -229,6 +230,7 @@ export async function runMoAPipeline({
|
|
|
229
230
|
const durationMs = Date.now() - startTime
|
|
230
231
|
try {
|
|
231
232
|
await recordMoaRunAsync({
|
|
233
|
+
cwd,
|
|
232
234
|
prompt: userPrompt,
|
|
233
235
|
preset: preset?.name || 'default',
|
|
234
236
|
isRefinement,
|
|
@@ -274,57 +276,47 @@ export async function runMoAPipeline({
|
|
|
274
276
|
return { error: new Error('Turn aborted') }
|
|
275
277
|
}
|
|
276
278
|
const single = successfulRefs[0]
|
|
277
|
-
let promotedFiles = []
|
|
279
|
+
let promotedFiles = [], promoteErr = null
|
|
278
280
|
if (single.files?.length > 0) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
try {
|
|
282
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, single.index, { ...promoteOpts, runId, keepMoa: allowCandidateOverride })
|
|
283
|
+
} catch (e) { promoteErr = e }
|
|
284
|
+
} else if (!allowCandidateOverride) {
|
|
281
285
|
await cleanMoaWorkspaces(cwd, { runId })
|
|
282
286
|
}
|
|
287
|
+
if (signal?.aborted || promoteErr?.message?.includes('aborted') || promoteErr?.name === 'AbortError') {
|
|
288
|
+
if (!allowCandidateOverride) await cleanMoaWorkspaces(cwd, { runId })
|
|
289
|
+
return { error: new Error('Turn aborted') }
|
|
290
|
+
}
|
|
283
291
|
|
|
284
|
-
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs)
|
|
292
|
+
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs, { winningIndex: single.index })
|
|
285
293
|
const totalTokens = single.usage?.totalTokens || 0
|
|
286
294
|
const totalCostUsd = Number((single.costUsd || 0).toFixed(5))
|
|
287
295
|
const durationMs = Date.now() - startTime
|
|
288
296
|
|
|
289
297
|
try {
|
|
290
298
|
await recordMoaRunAsync({
|
|
291
|
-
prompt: userPrompt,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
candidates: candidatesForHistory(referenceOutputs),
|
|
296
|
-
aggregator: null,
|
|
297
|
-
winnerIndex: single.index,
|
|
298
|
-
winnerModel: single.label,
|
|
299
|
-
promotedFiles,
|
|
300
|
-
totalTokens,
|
|
301
|
-
totalCostUsd,
|
|
302
|
-
durationMs,
|
|
299
|
+
id: runId, cwd, prompt: userPrompt, preset: preset?.name || 'default',
|
|
300
|
+
isRefinement, isFastMode: true, candidates: candidatesForHistory(referenceOutputs),
|
|
301
|
+
aggregator: null, winnerIndex: single.index, winnerModel: single.label,
|
|
302
|
+
promotedFiles, totalTokens, totalCostUsd, durationMs,
|
|
303
303
|
}, historyFilePath || undefined)
|
|
304
304
|
} catch (histErr) {
|
|
305
305
|
logger.warn('[dsh-moa] Failed to record fast synthesis run in history:', histErr?.message || histErr)
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
return {
|
|
309
|
-
kind: 'synthesis',
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
presetName: preset?.name || 'default',
|
|
314
|
-
isRefinement,
|
|
315
|
-
isFastMode: true,
|
|
316
|
-
winningIndex: single.index,
|
|
317
|
-
winnerModel: single.label,
|
|
318
|
-
promotedFiles,
|
|
309
|
+
kind: 'synthesis', content: single.text, aggregator: 'Fast Mode (Direct)',
|
|
310
|
+
references: referenceOutputs, presetName: preset?.name || 'default',
|
|
311
|
+
isRefinement, isFastMode: true, winningIndex: single.index,
|
|
312
|
+
winnerModel: single.label, promotedFiles, runId,
|
|
319
313
|
...(livePreview ? { liveCanvas: livePreview } : {}),
|
|
320
314
|
usage: {
|
|
321
|
-
totalTokens,
|
|
322
|
-
totalCostUsd,
|
|
315
|
+
totalTokens, totalCostUsd,
|
|
323
316
|
candidates: referenceOutputs.map((r) => ({ label: r.label, usage: r.usage, costUsd: r.costUsd })),
|
|
324
317
|
aggregator: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 },
|
|
325
318
|
},
|
|
326
|
-
skippedFiles: collectedCtx?.skippedFiles || 0,
|
|
327
|
-
skippedList: collectedCtx?.skippedList || [],
|
|
319
|
+
skippedFiles: collectedCtx?.skippedFiles || 0, skippedList: collectedCtx?.skippedList || [],
|
|
328
320
|
durationMs,
|
|
329
321
|
}
|
|
330
322
|
}
|
|
@@ -500,19 +492,29 @@ export async function runMoAPipeline({
|
|
|
500
492
|
let promotedFiles = []
|
|
501
493
|
|
|
502
494
|
const keepWorkspaces = allowCandidateOverride
|
|
503
|
-
if (
|
|
504
|
-
await
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
495
|
+
if (signal?.aborted) {
|
|
496
|
+
if (!keepWorkspaces) await cleanMoaWorkspaces(cwd, { runId })
|
|
497
|
+
return { error: new Error('Turn aborted') }
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
if (synthesizedFiles.length > 0) {
|
|
501
|
+
await writeCandidateWorkspace(cwd, 'curator-synthesis', synthesizedFiles, { runId })
|
|
502
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, 'curator-synthesis', { ...promoteOpts, runId, keepMoa: keepWorkspaces })
|
|
503
|
+
} else if (referenceOutputs[winningIndex - 1]?.files?.length > 0) {
|
|
504
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, winningIndex, { ...promoteOpts, runId, keepMoa: keepWorkspaces })
|
|
505
|
+
} else if (successfulRefs[0]?.files?.length > 0) {
|
|
506
|
+
const fallbackIdx = successfulRefs[0].index
|
|
507
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, fallbackIdx, { ...promoteOpts, runId, keepMoa: keepWorkspaces })
|
|
508
|
+
} else if (!keepWorkspaces) {
|
|
509
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
510
|
+
}
|
|
511
|
+
} catch (promoteErr) {
|
|
512
|
+
if (!keepWorkspaces) await cleanMoaWorkspaces(cwd, { runId })
|
|
513
|
+
if (signal?.aborted || promoteErr?.message === 'Turn aborted') return { error: new Error('Turn aborted') }
|
|
514
|
+
throw promoteErr
|
|
513
515
|
}
|
|
514
516
|
|
|
515
|
-
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs)
|
|
517
|
+
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs, { winningIndex, synthesizedFiles })
|
|
516
518
|
const durationMs = Date.now() - startTime
|
|
517
519
|
const usageSummary = summarizeMoAUsage(referenceOutputs, aggUsage, multiJudgeResult?.totalUsage)
|
|
518
520
|
const winningRef = referenceOutputs[winningIndex - 1]
|
|
@@ -542,6 +544,7 @@ export async function runMoAPipeline({
|
|
|
542
544
|
try {
|
|
543
545
|
await recordMoaRunAsync({
|
|
544
546
|
id: runId,
|
|
547
|
+
cwd,
|
|
545
548
|
prompt: userPrompt,
|
|
546
549
|
preset: preset?.name || 'default',
|
|
547
550
|
isRefinement,
|
|
@@ -579,8 +582,7 @@ export async function runMoAPipeline({
|
|
|
579
582
|
consensus: multiJudgeResult,
|
|
580
583
|
...(livePreview ? { liveCanvas: livePreview } : {}),
|
|
581
584
|
usage: usageSummary,
|
|
582
|
-
skippedFiles: collectedCtx?.skippedFiles || 0,
|
|
583
|
-
skippedList: collectedCtx?.skippedList || [],
|
|
585
|
+
skippedFiles: collectedCtx?.skippedFiles || 0, skippedList: collectedCtx?.skippedList || [],
|
|
584
586
|
durationMs,
|
|
585
587
|
}
|
|
586
588
|
}
|
package/lib/moa-stream.js
CHANGED
|
@@ -3,7 +3,8 @@ import crypto from 'node:crypto'
|
|
|
3
3
|
* Zero-latency Live Streaming Generator for MoA Turns
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { cleanMoaWorkspaces } from './file-workspace.js'
|
|
6
|
+
import { cleanMoaWorkspaces, promoteCandidateWorkspace } from './file-workspace.js'
|
|
7
|
+
import { isTestEnvironment, getMoaHistory } from './history.js'
|
|
7
8
|
import { formatMoAResponse } from './moa-parser.js'
|
|
8
9
|
import { runMoAPipeline } from './moa-runner.js'
|
|
9
10
|
|
|
@@ -56,6 +57,7 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
|
|
|
56
57
|
timeMachineEngine,
|
|
57
58
|
force,
|
|
58
59
|
signal: combinedSignal,
|
|
60
|
+
runId,
|
|
59
61
|
})
|
|
60
62
|
.catch((err) => ({ error: err }))
|
|
61
63
|
.finally(() => {
|
|
@@ -132,3 +134,59 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
|
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Streams a /moa promote candidate turn directly to chat with zero LLM calls (#177).
|
|
140
|
+
*/
|
|
141
|
+
export async function* streamPromoteTurn({ runId, candidateIndex, cwd, webServerPort, timeMachineEngine, force = isTestEnvironment() }, options = {}) {
|
|
142
|
+
const signal = options?.signal
|
|
143
|
+
if (signal?.aborted) return
|
|
144
|
+
|
|
145
|
+
yield { type: 'block-start', index: 0, blockType: 'text' }
|
|
146
|
+
|
|
147
|
+
if (typeof candidateIndex !== 'number' || isNaN(candidateIndex) || candidateIndex < 1) {
|
|
148
|
+
const errText = '⚠️ **MoA Promote Error**: Valid candidate index required (>= 1).'
|
|
149
|
+
yield { type: 'text-delta', index: 0, text: errText }
|
|
150
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
151
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const effectiveRunId = runId || getMoaHistory(1, 0).runs[0]?.id || null
|
|
157
|
+
yield { type: 'text-delta', index: 0, text: `🔄 *Promoting candidate ${candidateIndex}${effectiveRunId ? ` for run \`${effectiveRunId}\`` : ''}...*\n\n` }
|
|
158
|
+
|
|
159
|
+
const promotedFiles = await promoteCandidateWorkspace(cwd, candidateIndex, {
|
|
160
|
+
runId: effectiveRunId,
|
|
161
|
+
keepMoa: true,
|
|
162
|
+
port: webServerPort,
|
|
163
|
+
timeMachineEngine,
|
|
164
|
+
force: Boolean(force || options?.force),
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
if (!promotedFiles || promotedFiles.length === 0) {
|
|
168
|
+
const noFilesText = `⚠️ **MoA Promote Notice**: No candidate files found for candidate ${candidateIndex}${effectiveRunId ? ` in run \`${effectiveRunId}\`` : ''}. Workspace may have expired or run does not exist.`
|
|
169
|
+
yield { type: 'text-delta', index: 0, text: noFilesText }
|
|
170
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: noFilesText } }
|
|
171
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const fileList = promotedFiles.map((f) => `\`${f}\``).join(', ')
|
|
176
|
+
const checkpointId = promotedFiles.checkpoint || null
|
|
177
|
+
let successMsg = `✅ **MoA Candidate ${candidateIndex} promoted successfully!**\n\n`
|
|
178
|
+
successMsg += `> 📦 **Files applied to workspace**: ${fileList}\n`
|
|
179
|
+
if (checkpointId) {
|
|
180
|
+
successMsg += `> 🛡️ **Time Machine Checkpoint**: \`${checkpointId}\`\n`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
yield { type: 'text-delta', index: 0, text: successMsg }
|
|
184
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: successMsg } }
|
|
185
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
186
|
+
} catch (err) {
|
|
187
|
+
const errText = `⚠️ **MoA Promote Failed**: ${err?.message || String(err)}`
|
|
188
|
+
yield { type: 'text-delta', index: 0, text: errText }
|
|
189
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
190
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
191
|
+
}
|
|
192
|
+
}
|
package/lib/moa-test-gate.js
CHANGED
|
@@ -115,12 +115,23 @@ export async function runCandidateTestGate({
|
|
|
115
115
|
stderr = customRes?.stderr || ''
|
|
116
116
|
} else {
|
|
117
117
|
const nodePath = path.join(cwd, 'node_modules')
|
|
118
|
+
const allowedReads = [
|
|
119
|
+
executionDir,
|
|
120
|
+
cwd,
|
|
121
|
+
...(candidateDir && candidateDir !== executionDir ? [candidateDir] : []),
|
|
122
|
+
...(process.platform === 'win32'
|
|
123
|
+
? [process.env.SYSTEMROOT || 'C:\\Windows', path.dirname(process.execPath)]
|
|
124
|
+
: ['/usr', '/lib', '/lib64', '/etc', path.resolve(path.dirname(process.execPath), '..'), process.env.HOME ? path.join(process.env.HOME, '.npmrc') : null]),
|
|
125
|
+
].filter(Boolean)
|
|
126
|
+
const permOpts = `--permission ${allowedReads.map((p) => `--allow-fs-read=${p}`).join(' ')} --allow-fs-write=${executionDir} --allow-child-process`
|
|
127
|
+
|
|
118
128
|
const env = getScrubbedEnv({
|
|
119
129
|
MOA_CANDIDATE_INDEX: String(candidateIndex),
|
|
120
130
|
MOA_CANDIDATE_DIR: candidateDir,
|
|
121
131
|
MOA_BASE_DIR: cwd,
|
|
122
132
|
...(runId ? { MOA_RUN_ID: String(runId) } : {}),
|
|
123
133
|
NODE_PATH: process.env.NODE_PATH ? `${nodePath}:${process.env.NODE_PATH}` : nodePath,
|
|
134
|
+
NODE_OPTIONS: process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ${permOpts}` : permOpts,
|
|
124
135
|
})
|
|
125
136
|
|
|
126
137
|
const runPromise = new Promise((resolve) => {
|
package/lib/routes.js
CHANGED
|
@@ -356,7 +356,11 @@ ctx.effect(() => {
|
|
|
356
356
|
}
|
|
357
357
|
try {
|
|
358
358
|
const u = new URL(req.url, 'http://localhost')
|
|
359
|
-
const
|
|
359
|
+
const rawRunId = u.searchParams.get('runId')
|
|
360
|
+
const latestRun = (!rawRunId || rawRunId === 'latest' || rawRunId === 'active') ? getMoaHistory(1, 0).runs[0] : null
|
|
361
|
+
const runId = (rawRunId && rawRunId !== 'latest' && rawRunId !== 'active') ? rawRunId : latestRun?.id
|
|
362
|
+
const run = runId ? getMoaRunById(runId) : null
|
|
363
|
+
const cwd = run?.cwd || latestRun?.cwd || u.searchParams.get('cwd') || process.cwd()
|
|
360
364
|
const sanitizeTarget = (val, def) => {
|
|
361
365
|
const clean = String(val || '').trim().replace(/[^a-zA-Z0-9_-]/g, '')
|
|
362
366
|
return clean || def
|
|
@@ -366,7 +370,6 @@ ctx.effect(() => {
|
|
|
366
370
|
const rawFile = u.searchParams.get('file') || ''
|
|
367
371
|
const file = (rawFile && isSafeRelativePath(rawFile)) ? rawFile : ''
|
|
368
372
|
|
|
369
|
-
const runId = u.searchParams.get('runId') || undefined
|
|
370
373
|
const fromFiles = await readCandidateFiles(cwd, from, { runId })
|
|
371
374
|
const toFiles = await readCandidateFiles(cwd, to, { runId })
|
|
372
375
|
|
|
@@ -510,11 +513,14 @@ ctx.effect(() => {
|
|
|
510
513
|
const raw = await readBody(req)
|
|
511
514
|
const body = JSON.parse(raw || '{}')
|
|
512
515
|
const prompt = body?.prompt || ''
|
|
516
|
+
const activePreset = (cfg.presets || []).find((p) => p.name === (cfg.default_preset || 'default')) || cfg.presets?.[0]
|
|
517
|
+
const routingModel = (activePreset?.smart_routing_model?.model && activePreset.smart_routing_model)
|
|
518
|
+
|| (cfg.smart_routing_model?.model && cfg.smart_routing_model) || activePreset?.smart_routing_model || cfg.smart_routing_model
|
|
513
519
|
const result = await resolvePresetForPrompt({
|
|
514
520
|
prompt,
|
|
515
521
|
presets: cfg.presets || [],
|
|
516
522
|
defaultPreset: cfg.default_preset || 'default',
|
|
517
|
-
routingModel
|
|
523
|
+
routingModel,
|
|
518
524
|
callLlm,
|
|
519
525
|
enabled: cfg.enabled !== false,
|
|
520
526
|
})
|
|
@@ -574,6 +580,7 @@ ctx.effect(() => {
|
|
|
574
580
|
timeMachineEngine: timeMachineEngine || ctx.reflect?.get?.('timeMachineEngine', false),
|
|
575
581
|
timeMachineUrl: payload.timeMachineUrl, force: payload.force,
|
|
576
582
|
checkpoint: payload.checkpoint, checkpointFn: payload.checkpointFn,
|
|
583
|
+
runId: payload.runId,
|
|
577
584
|
})
|
|
578
585
|
|
|
579
586
|
if (result?.skippedFiles > 0) {
|
package/lib/updater.js
CHANGED
|
@@ -43,18 +43,39 @@ export function isTrustedUpdateRequest(request) {
|
|
|
43
43
|
|
|
44
44
|
/** General safe write check for plugin write endpoints (CSRF / Cross-Site protection) */
|
|
45
45
|
export function isSafeWriteRequest(request) {
|
|
46
|
+
const remote = request?.socket?.remoteAddress || request?.connection?.remoteAddress;
|
|
47
|
+
const isLoop = !remote || isLoopback(remote);
|
|
48
|
+
|
|
46
49
|
const site = header(request, 'sec-fetch-site');
|
|
47
50
|
if (site === 'cross-site') return false;
|
|
51
|
+
|
|
48
52
|
const origin = header(request, 'origin');
|
|
49
53
|
const host = header(request, 'host');
|
|
54
|
+
|
|
55
|
+
// If non-loopback caller, require valid same-origin browser proof
|
|
56
|
+
if (!isLoop) {
|
|
57
|
+
if (!origin || !host) return false;
|
|
58
|
+
if (site && site !== 'same-origin' && site !== 'none') return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Same-site without matching origin is rejected regardless of remote
|
|
62
|
+
if (site === 'same-site') {
|
|
63
|
+
if (!origin || !host) return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
if (origin) {
|
|
67
|
+
if (origin === 'null') return false;
|
|
51
68
|
try {
|
|
52
69
|
const url = new URL(origin);
|
|
53
70
|
if (host && url.host !== host) return false;
|
|
71
|
+
if (!isLoop) {
|
|
72
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
|
|
73
|
+
}
|
|
54
74
|
} catch {
|
|
55
75
|
return false;
|
|
56
76
|
}
|
|
57
77
|
}
|
|
78
|
+
|
|
58
79
|
return true;
|
|
59
80
|
}
|
|
60
81
|
|
|
@@ -353,6 +374,8 @@ export function registerPluginUpdater(ctx, options) {
|
|
|
353
374
|
}
|
|
354
375
|
await installExact(target, `${options.packageName}@${before.latestVersion}`, options);
|
|
355
376
|
json(response, 200, {
|
|
377
|
+
ok: true,
|
|
378
|
+
updated: true,
|
|
356
379
|
...before,
|
|
357
380
|
updatedVersion: before.latestVersion,
|
|
358
381
|
restartRequired: true,
|