@ljwei-stak/model-router-galgame 0.4.12 → 0.4.15
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/.dsh-plugin/client.js +1 -1
- package/.dsh-plugin/index.mjs +65 -3
- package/.dsh-plugin/shared/approval-gate.mjs +109 -0
- package/.dsh-plugin/shared/web-routing.mjs +90 -0
- package/README.md +350 -20
- package/README.zh.md +311 -19
- package/cordis.patch.yml +16 -0
- package/package.json +75 -57
package/.dsh-plugin/client.js
CHANGED
|
@@ -61740,7 +61740,7 @@ var gal_scene_default = { version: 1, settings: { stageW: 1920, stageH: 1080, sh
|
|
|
61740
61740
|
var name = "gal-view";
|
|
61741
61741
|
var PROJECT_URL = "https://github.com/ljwei-stak/deepseek-harness";
|
|
61742
61742
|
var RELEASES_URL = `${PROJECT_URL}/releases`;
|
|
61743
|
-
var PLUGIN_VERSION = "0.4.
|
|
61743
|
+
var PLUGIN_VERSION = "0.4.14";
|
|
61744
61744
|
function createUpdateApi() {
|
|
61745
61745
|
const bridge = globalThis.deepSeekHarnessDesktop;
|
|
61746
61746
|
const openExternal = (url) => {
|
package/.dsh-plugin/index.mjs
CHANGED
|
@@ -15,6 +15,17 @@ import {
|
|
|
15
15
|
modLensUpstream,
|
|
16
16
|
routeThroughModLens,
|
|
17
17
|
} from './shared/modlens-routing.mjs'
|
|
18
|
+
import {
|
|
19
|
+
approvalGateStatus,
|
|
20
|
+
approvalSafetyContext,
|
|
21
|
+
decorateApprovalReason,
|
|
22
|
+
isApprovalGateReason,
|
|
23
|
+
} from './shared/approval-gate.mjs'
|
|
24
|
+
import {
|
|
25
|
+
webCapabilityForPlan,
|
|
26
|
+
webCapabilityStatus,
|
|
27
|
+
webInstruction,
|
|
28
|
+
} from './shared/web-routing.mjs'
|
|
18
29
|
|
|
19
30
|
let settingsRuntimePromise
|
|
20
31
|
let routerSettings = { ...DEFAULT_ROUTER_SETTINGS }
|
|
@@ -213,6 +224,17 @@ function stageMessage(plan, step) {
|
|
|
213
224
|
}
|
|
214
225
|
}
|
|
215
226
|
|
|
227
|
+
function webMessage(plan) {
|
|
228
|
+
const text = webInstruction(plan?.web)
|
|
229
|
+
if (text === '') return null
|
|
230
|
+
return {
|
|
231
|
+
id: newMessageId(),
|
|
232
|
+
role: 'user',
|
|
233
|
+
content: [{ type: 'text', text }],
|
|
234
|
+
source: { kind: 'plugin', plugin: name, form: 'web-capability', summary: '联网与可见浏览器策略' },
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
216
238
|
/**
|
|
217
239
|
* Persona is a final-answer context only. It is intentionally a separate
|
|
218
240
|
* message so the collaboration stages and their audit records remain free of
|
|
@@ -268,6 +290,7 @@ function analysisMessage(plan) {
|
|
|
268
290
|
`缓存计费比例:读取 ${Math.round(Number(plan.optimization?.cacheReadRatio ?? 0) * 100)}%,写入 ${Math.round(Number(plan.optimization?.cacheWriteRatio ?? 0) * 100)}%(未填写时按普通输入计费)`,
|
|
269
291
|
Number(plan.optimization?.budgetUsd ?? 0) > 0 ? `预算上限:$${Number(plan.optimization.budgetUsd).toFixed(6)};${plan.optimization.budgetExceeded ? '仍超预算,已在质量下限内尽量压缩' : '满足预算约束'}` : '',
|
|
270
292
|
`LiveBench:${plan.optimization?.liveBench?.fetchedAt ? `快照于 ${new Date(Number(plan.optimization.liveBench.fetchedAt)).toISOString()}${plan.optimization.liveBench.stale ? '(本次刷新失败,沿用上次快照)' : ''}` : '未完成联网核验,使用实验基线'}`,
|
|
293
|
+
plan.web?.needsWeb ? `联网策略:${plan.web.directBrowser ? 'Ego Browser 可见窗口优先' : 'ModSearch 搜索/抓取,失败时 Ego Browser 窗口兜底'};反爬处理:人工接管后继续` : '',
|
|
271
294
|
String(plan.reason ?? ''),
|
|
272
295
|
].filter(Boolean).join('\n')
|
|
273
296
|
return {
|
|
@@ -432,6 +455,27 @@ export function apply(ctx) {
|
|
|
432
455
|
routerSettingsPromise = registerRouterSettings(ctx)
|
|
433
456
|
}
|
|
434
457
|
const scheduleOpenCodeRepair = createOpenCodeRepairScheduler(ctx)
|
|
458
|
+
|
|
459
|
+
// dsh-approval-gate owns the actual decision. The router adds auditable
|
|
460
|
+
// stage/route context before that waterfall so multi-task escalations are
|
|
461
|
+
// visible to the gate's Flash classifier and human reviewer. The request
|
|
462
|
+
// object is borrowed by the Host approval service for this dispatch only.
|
|
463
|
+
ctx.on('approval/request', (request, next) => {
|
|
464
|
+
if (!isApprovalGateReason(request?.reason)) return next()
|
|
465
|
+
const state = request?.agent === undefined ? null : stateFor(request.agent)
|
|
466
|
+
const context = approvalSafetyContext(state, state?.lastStep)
|
|
467
|
+
const decorated = decorateApprovalReason(request.reason, context)
|
|
468
|
+
if (decorated !== request.reason) {
|
|
469
|
+
try {
|
|
470
|
+
request.reason = decorated
|
|
471
|
+
} catch {
|
|
472
|
+
// Some hosts freeze event payloads. In that case the gate still
|
|
473
|
+
// receives the original reason and remains fully fail-safe.
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return next()
|
|
477
|
+
}, { prepend: true })
|
|
478
|
+
|
|
435
479
|
ctx.commands.register({
|
|
436
480
|
name: 'router',
|
|
437
481
|
description: 'switch Model Router mode or inspect the latest routing plan',
|
|
@@ -440,7 +484,7 @@ export function apply(ctx) {
|
|
|
440
484
|
// the whole composer submission; the GAL client sends this command
|
|
441
485
|
// without image bytes so the attachment remains available for the next
|
|
442
486
|
// user turn.
|
|
443
|
-
input: { hint: 'mode collective|single | plan', images: true },
|
|
487
|
+
input: { hint: 'mode collective|single | plan | safety', images: true },
|
|
444
488
|
recordInput: true,
|
|
445
489
|
handler: ({ agent, rawInput }) => {
|
|
446
490
|
const state = stateFor(agent)
|
|
@@ -458,7 +502,13 @@ export function apply(ctx) {
|
|
|
458
502
|
if (value === 'plan' || value === '') {
|
|
459
503
|
return { kind: 'success', text: state.plan === null ? '还没有可展示的路由方案。' : JSON.stringify(state.plan) }
|
|
460
504
|
}
|
|
461
|
-
|
|
505
|
+
if (value === 'safety' || value === 'approval') {
|
|
506
|
+
return { kind: 'success', text: JSON.stringify({ ...approvalGateStatus(ctx), context: approvalSafetyContext(state, state.lastStep) }) }
|
|
507
|
+
}
|
|
508
|
+
if (value === 'web' || value === 'network') {
|
|
509
|
+
return { kind: 'success', text: JSON.stringify({ ...webCapabilityStatus(ctx), context: webCapabilityForPlan(state.taskText, state.plan) }) }
|
|
510
|
+
}
|
|
511
|
+
return { kind: 'error', text: '用法:/router mode collective、/router mode single、/router plan、/router safety 或 /router web' }
|
|
462
512
|
},
|
|
463
513
|
})
|
|
464
514
|
|
|
@@ -515,7 +565,7 @@ export function apply(ctx) {
|
|
|
515
565
|
await routerSettingsPromise
|
|
516
566
|
state.taskText = inputText(messages)
|
|
517
567
|
const liveBench = await liveBenchFor(ctx, state)
|
|
518
|
-
|
|
568
|
+
const plan = buildPlan({
|
|
519
569
|
text: state.taskText,
|
|
520
570
|
available,
|
|
521
571
|
mode: state.mode,
|
|
@@ -526,6 +576,16 @@ export function apply(ctx) {
|
|
|
526
576
|
cacheReadRatio: routerSettings.cacheReadRatio,
|
|
527
577
|
cacheWriteRatio: routerSettings.cacheWriteRatio,
|
|
528
578
|
})
|
|
579
|
+
state.plan = {
|
|
580
|
+
...plan,
|
|
581
|
+
web: webCapabilityForPlan(state.taskText, plan),
|
|
582
|
+
safety: {
|
|
583
|
+
...approvalGateStatus(ctx),
|
|
584
|
+
...approvalSafetyContext({ ...state, plan }, Number(step)),
|
|
585
|
+
hardCategories: ['deletion', 'credential', 'remote', 'system', 'bulk'],
|
|
586
|
+
failSafe: true,
|
|
587
|
+
},
|
|
588
|
+
}
|
|
529
589
|
state.collaboration = shouldCollaborate(state.plan, available)
|
|
530
590
|
? { lastStep: 0, queuedStep: null }
|
|
531
591
|
: null
|
|
@@ -538,11 +598,13 @@ export function apply(ctx) {
|
|
|
538
598
|
const currentStep = Number.isFinite(Number(step)) ? Number(step) : state.lastStep + 1
|
|
539
599
|
const stageContext = stageMessage(state.plan, currentStep)
|
|
540
600
|
const analysisContext = currentStep === 1 ? analysisMessage(state.plan) : null
|
|
601
|
+
const webContext = currentStep === 1 ? webMessage(state.plan) : null
|
|
541
602
|
const hasPersona = proposed.messages.some(message => message?.content?.some(block => isPersonaPrompt(block?.text)))
|
|
542
603
|
if (hasPersona) state.personaInjected = true
|
|
543
604
|
const personaContext = state.personaInjected ? null : personaMessage(state, agent, currentStep)
|
|
544
605
|
const additions = []
|
|
545
606
|
if (analysisContext !== null && !hasStageMarker(proposed.messages, currentStep)) additions.push(analysisContext)
|
|
607
|
+
if (webContext !== null && !proposed.messages.some(message => message?.content?.some(block => block?.text === webContext.content[0].text))) additions.push(webContext)
|
|
546
608
|
if (personaContext !== null) {
|
|
547
609
|
additions.push(personaContext)
|
|
548
610
|
state.personaInjected = true
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility bridge for dsh-approval-gate.
|
|
3
|
+
*
|
|
4
|
+
* The approval-gate package owns the approval waterfall, Flash judgement,
|
|
5
|
+
* learning, audit files, snapshots and UI. This module only produces a small,
|
|
6
|
+
* deterministic safety context for the current Model Router work package.
|
|
7
|
+
* Keeping the bridge stateless makes it safe when the gate is installed by
|
|
8
|
+
* another profile layer as well as when it is bundled by this plugin.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const APPROVAL_GATE_PACKAGE = '@ljwei-stak/dsh-approval-gate'
|
|
12
|
+
export const APPROVAL_GATE_VERSION = '0.5.2'
|
|
13
|
+
|
|
14
|
+
const ESCALATION_RE = /escalate\s+sandbox\s+to\s+([^\s:]+):?\s*([\s\S]*)/i
|
|
15
|
+
|
|
16
|
+
function clean(value, max = 240) {
|
|
17
|
+
return String(value ?? '').replace(/[\r\n]+/g, ' ').trim().slice(0, max)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stageFor(state, step) {
|
|
21
|
+
const plan = state?.plan
|
|
22
|
+
const tasks = Array.isArray(plan?.subtasks) ? plan.subtasks : []
|
|
23
|
+
if (tasks.length === 0) return null
|
|
24
|
+
const index = Math.max(0, Number(step || state?.lastStep || 1) - 1)
|
|
25
|
+
return tasks[index] ?? tasks[tasks.length - 1] ?? null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Return the router safety facts that are relevant to an approval request.
|
|
30
|
+
* `bulk` is a candidate supplied as context; dsh-approval-gate still performs
|
|
31
|
+
* the authoritative category decision and fail-safe human handoff.
|
|
32
|
+
*/
|
|
33
|
+
export function approvalSafetyContext(state, step) {
|
|
34
|
+
const plan = state?.plan
|
|
35
|
+
const tasks = Array.isArray(plan?.subtasks) ? plan.subtasks : []
|
|
36
|
+
const stage = stageFor(state, step)
|
|
37
|
+
const collective = state?.mode === 'collective'
|
|
38
|
+
const multiTask = collective && plan?.complexity?.band === 'complex' && tasks.length >= 3
|
|
39
|
+
return {
|
|
40
|
+
mode: state?.mode ?? 'collective',
|
|
41
|
+
complexity: plan?.complexity?.band ?? 'unknown',
|
|
42
|
+
stage: stage?.id ?? null,
|
|
43
|
+
stagePurpose: stage?.purpose ?? null,
|
|
44
|
+
stageType: stage?.type ?? null,
|
|
45
|
+
stageIndex: tasks.length === 0 ? 0 : Math.max(1, Number(step || state?.lastStep || 1)),
|
|
46
|
+
stageCount: tasks.length,
|
|
47
|
+
multiTask,
|
|
48
|
+
candidateCategory: multiTask ? 'bulk' : 'neutral',
|
|
49
|
+
selectedRoute: plan?.selected?.provider && plan?.selected?.model
|
|
50
|
+
? `${plan.selected.provider}/${plan.selected.model}`
|
|
51
|
+
: null,
|
|
52
|
+
activeRoute: state?.lastTarget?.provider && state?.lastTarget?.model
|
|
53
|
+
? `${state.lastTarget.provider}/${state.lastTarget.model}`
|
|
54
|
+
: null,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Decorate the justification consumed by dsh-approval-gate. The original
|
|
60
|
+
* escalation prefix remains intact, so the target plugin can parse it. The
|
|
61
|
+
* marker is deliberately plain text because the target plugin's Flash model
|
|
62
|
+
* judges only the justification string.
|
|
63
|
+
*/
|
|
64
|
+
export function decorateApprovalReason(reason, context) {
|
|
65
|
+
const raw = String(reason ?? '')
|
|
66
|
+
const match = raw.match(ESCALATION_RE)
|
|
67
|
+
if (!match || context === null || context === undefined) return raw
|
|
68
|
+
const mode = clean(match[1], 64)
|
|
69
|
+
const justification = clean(match[2], 500)
|
|
70
|
+
const stage = context.stage ? `${context.stage} ${context.stageIndex}/${context.stageCount}` : 'unknown'
|
|
71
|
+
const route = context.activeRoute || context.selectedRoute || 'unassigned'
|
|
72
|
+
const marker = [
|
|
73
|
+
'[model-router safety context]',
|
|
74
|
+
`mode=${context.mode}`,
|
|
75
|
+
`complexity=${context.complexity}`,
|
|
76
|
+
`stage=${stage}`,
|
|
77
|
+
`purpose=${context.stagePurpose || 'unknown'}`,
|
|
78
|
+
`route=${route}`,
|
|
79
|
+
`task_count=${context.stageCount || 0}`,
|
|
80
|
+
`risk_candidate=${context.candidateCategory}`,
|
|
81
|
+
context.multiTask ? 'multi_task_review=required' : 'multi_task_review=not_applicable',
|
|
82
|
+
].join('; ')
|
|
83
|
+
return `escalate sandbox to ${mode}: ${justification || 'router stage requires sandbox escalation'} ${marker}`.trim()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function isApprovalGateReason(reason) {
|
|
87
|
+
return ESCALATION_RE.test(String(reason ?? ''))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function approvalGateStatus(ctx) {
|
|
91
|
+
let approval = false
|
|
92
|
+
let permissionPresets = false
|
|
93
|
+
try {
|
|
94
|
+
approval = Boolean(ctx?.get?.('approval'))
|
|
95
|
+
permissionPresets = Boolean(ctx?.get?.('permissionPresets'))
|
|
96
|
+
} catch {
|
|
97
|
+
approval = false
|
|
98
|
+
permissionPresets = false
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
package: APPROVAL_GATE_PACKAGE,
|
|
102
|
+
version: APPROVAL_GATE_VERSION,
|
|
103
|
+
bundled: true,
|
|
104
|
+
approvalServiceDetected: approval,
|
|
105
|
+
permissionPresetsDetected: permissionPresets,
|
|
106
|
+
policy: 'hard-risk-human-review',
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web capability selection for the bundled ModSearch + Ego Browser pair.
|
|
3
|
+
*
|
|
4
|
+
* ModSearch owns search/fetch engines and SSRF protection. Ego Browser owns
|
|
5
|
+
* the real visible browser, login state, screenshots, and human-check handoff.
|
|
6
|
+
* This module only classifies intent and creates an auditable instruction for
|
|
7
|
+
* the model; it never fetches an untrusted URL itself.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const MODSEARCH_PACKAGE = '@liustack/modsearch'
|
|
11
|
+
export const MODSEARCH_VERSION = '5.10.1'
|
|
12
|
+
export const EGO_BROWSER_PACKAGE = 'dsh-ego-browser'
|
|
13
|
+
export const EGO_BROWSER_VERSION = '0.8.0'
|
|
14
|
+
|
|
15
|
+
const WEB_RE = /https?:\/\/|www\.|联网|上网|网页|网站|搜索|查找|资料|文献|新闻|最新|实时|网页内容|页面|来源|引用|网络|x\s*帖子|twitter|推特|github|反爬|验证码|登录|人机验证/i
|
|
16
|
+
const BROWSER_RE = /反爬|验证码|人机验证|cloudflare|turnstile|recaptcha|hcaptcha|登录|需要点击|动态页面|网页窗口|浏览器|可见窗口|手动验证|页面交互/i
|
|
17
|
+
|
|
18
|
+
function text(value, max = 12000) {
|
|
19
|
+
return String(value ?? '').slice(-max)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function classifyWebIntent(input) {
|
|
23
|
+
const value = text(input)
|
|
24
|
+
const needsWeb = WEB_RE.test(value)
|
|
25
|
+
const directBrowser = BROWSER_RE.test(value)
|
|
26
|
+
return {
|
|
27
|
+
needsWeb,
|
|
28
|
+
directBrowser,
|
|
29
|
+
antiBotFallback: needsWeb,
|
|
30
|
+
primary: needsWeb ? 'modsearch' : 'native-model',
|
|
31
|
+
fallback: needsWeb ? 'ego-browser' : null,
|
|
32
|
+
reason: directBrowser
|
|
33
|
+
? '用户明确要求动态网页、登录态或反爬页面,优先使用可见 Ego Browser。'
|
|
34
|
+
: needsWeb
|
|
35
|
+
? '先使用 ModSearch 搜索/抓取;失败、内容不完整或触发人机验证时切换 Ego Browser。'
|
|
36
|
+
: '',
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function webCapabilityForPlan(input, plan = null) {
|
|
41
|
+
const intent = classifyWebIntent(input)
|
|
42
|
+
const tasks = Array.isArray(plan?.subtasks) ? plan.subtasks : []
|
|
43
|
+
return {
|
|
44
|
+
...intent,
|
|
45
|
+
taskCount: tasks.length,
|
|
46
|
+
stageAware: tasks.length > 1,
|
|
47
|
+
packages: {
|
|
48
|
+
search: MODSEARCH_PACKAGE,
|
|
49
|
+
browser: EGO_BROWSER_PACKAGE,
|
|
50
|
+
},
|
|
51
|
+
versions: {
|
|
52
|
+
search: MODSEARCH_VERSION,
|
|
53
|
+
browser: EGO_BROWSER_VERSION,
|
|
54
|
+
},
|
|
55
|
+
humanCheckPolicy: 'pause-and-handoff',
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function webCapabilityStatus(ctx) {
|
|
60
|
+
let web = false
|
|
61
|
+
let tools = false
|
|
62
|
+
try {
|
|
63
|
+
web = Boolean(ctx?.get?.('web'))
|
|
64
|
+
tools = Boolean(ctx?.get?.('tools'))
|
|
65
|
+
} catch {
|
|
66
|
+
web = false
|
|
67
|
+
tools = false
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
modsearch: { package: MODSEARCH_PACKAGE, version: MODSEARCH_VERSION, bundled: true, webServiceDetected: web },
|
|
71
|
+
egoBrowser: { package: EGO_BROWSER_PACKAGE, version: EGO_BROWSER_VERSION, bundled: true, toolServiceDetected: tools },
|
|
72
|
+
antiBotWindow: 'ego_space_open -> ego_navigate -> ego_page_info/ego_captcha -> ego_snapshot/ego_screenshot',
|
|
73
|
+
humanCheck: 'pause-and-handoff',
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function webInstruction(capability) {
|
|
78
|
+
if (!capability?.needsWeb) return ''
|
|
79
|
+
const browserFirst = capability.directBrowser
|
|
80
|
+
return [
|
|
81
|
+
'[联网与可见浏览器适配]',
|
|
82
|
+
browserFirst
|
|
83
|
+
? '本任务涉及动态网页、登录态或反爬页面:优先使用 Ego Browser 的真实可见窗口。'
|
|
84
|
+
: '普通联网先使用 ModSearch 提供的 web_search/read_page;需要 X 内容时使用 x_search。',
|
|
85
|
+
'如果搜索或抓取返回 unavailable、内容不完整、JS 页面空白或 warnings,切换 Ego Browser:先 ego_space_open,再 ego_navigate;随后调用 ego_page_info 或 ego_captcha 检查 humanCheck。',
|
|
86
|
+
'页面可读时使用 ego_snapshot、ego_read_element、ego_screenshot 或 ego_http(browser) 提取证据;必须交互时使用 ego_click、ego_fill、ego_wait*。',
|
|
87
|
+
'检测到验证码、Cloudflare、Turnstile、登录或其他 humanCheck=true 时,暂停当前工作包,提示用户在 Agent 浏览器观察窗完成验证;用户确认后再继续。不要绕过验证码或伪造验证结果。',
|
|
88
|
+
'联网证据必须保留 URL、页面状态和不确定性说明;批量抓取仍遵守审批门控。',
|
|
89
|
+
].join('\n')
|
|
90
|
+
}
|