@ljwei-stak/model-router-galgame 0.4.12 → 0.4.14

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.
@@ -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.12";
61743
+ var PLUGIN_VERSION = "0.4.14";
61744
61744
  function createUpdateApi() {
61745
61745
  const bridge = globalThis.deepSeekHarnessDesktop;
61746
61746
  const openExternal = (url) => {
@@ -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
- return { kind: 'error', text: '用法:/router mode collective、/router mode single 或 /router plan' }
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
- state.plan = buildPlan({
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,108 @@
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 = 'dsh-approval-gate'
12
+ export const APPROVAL_GATE_VERSION = '0.5.0'
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
+ }
@@ -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
+ }
package/README.md CHANGED
@@ -15,6 +15,8 @@ This plugin installs on an original DeepSeek Harness checkout. It adds a cost-aw
15
15
  - **GAL view** turns each new conversation into an archive. Every line retains its actual provider/model, so the nameplate, color, and portrait follow the active model. ERNIE, Wenxin, and Baidu provider/model identifiers consistently select `ERNIE娘` and `ernie1.png`. The route explanation is an auditable summary, not private model chain-of-thought.
16
16
  - **Markdown/KaTeX** reuses Harness `MarkdownText` for headings, lists, tables, quotes, code, links, and formulas. Wide content scrolls inside the dialogue box; player input remains plain text.
17
17
  - **Attachments and multimodality** use the native image pipeline and extract Markdown/TXT/JSON/code as text. PDF/DOCX and other binary files keep an explicit parsing state rather than silently inventing content.
18
+ - **Multi-task safety approval adapter** bundles `dsh-approval-gate@0.5.0`. Every sandbox escalation in a complex collective task carries the work package, stage, route, and task-count context; multi-task work is marked as a `bulk` risk candidate for Flash classification, hard-risk human review, learning, audit, and snapshots. Single-session mode is never forcibly rerouted.
19
+ - **Web search and anti-bot browsing** bundle `@liustack/modsearch@5.10.1` and `dsh-ego-browser@0.8.0`. ModSearch handles `web_search`, `read_page`, and `x_search`; Ego Browser opens a real Chrome/Edge window for JavaScript pages, login state, screenshots, and human verification. Search failures and anti-bot pages are routed to the visible browser flow.
18
20
  - **OpenCode Zen compatibility** repairs official website overrides to the catalog-owned `/zen` and `/zen/v1` endpoints while leaving custom gateways untouched.
19
21
  - **Release updates and desktop support** provide release checks and a one-click updater. When the desktop is outdated it updates the full client and bundled plugin; otherwise it updates only the plugin. Browser-only installs open Releases because they cannot write local files.
20
22
 
@@ -29,7 +31,7 @@ This plugin installs on an original DeepSeek Harness checkout. It adds a cost-aw
29
31
 
30
32
  ### Recommended: install the published npm package
31
33
 
32
- The package is public and already includes the official `@liustack/modlens@3.25.4` bundle. You do not need to install Docker, Python, a ModLens server, or a second ModLens package.
34
+ The package is public and already includes the official `@liustack/modlens@3.25.4`, `dsh-approval-gate@0.5.0`, `@liustack/modsearch@5.10.1`, and the registry package `dsh-ego-browser@0.8.0` plus its runtime peers (`@deepseek-ai/dsh-tools@0.1.0-rc.8`, `schemastery@3.18.0`). You do not need to clone the Ego Browser GitHub repository or install these dependencies separately.
33
35
 
34
36
  1. Open PowerShell in the DSH Desktop checkout:
35
37
 
@@ -75,7 +77,7 @@ pnpm dsh plugin --profile web add --registry=https://registry.npmjs.org "@ljwei-
75
77
  4. Verify that the router and its ModLens dependency are present:
76
78
 
77
79
  ```powershell
78
- pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens"
80
+ pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens|dsh-approval-gate|modsearch|ego-browser"
79
81
  ```
80
82
 
81
83
  The output should contain:
@@ -83,6 +85,9 @@ The output should contain:
83
85
  ```text
84
86
  @ljwei-stak/model-router-galgame
85
87
  @liustack/modlens
88
+ dsh-approval-gate
89
+ @liustack/modsearch
90
+ dsh-ego-browser
86
91
  ```
87
92
 
88
93
  Do not add `@liustack/modlens` separately after installing this package. The
@@ -95,6 +100,23 @@ pnpm dsh plugin --profile web remove @liustack/modlens
95
100
  pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
96
101
  ```
97
102
 
103
+ Do not add `dsh-approval-gate` separately to the same profile. If it was previously installed as a standalone entry, remove it before reinstalling the router to avoid a duplicate loader:
104
+
105
+ ```powershell
106
+ pnpm dsh plugin --profile web remove dsh-approval-gate
107
+ pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
108
+ ```
109
+
110
+ Do not add `@liustack/modsearch` or `dsh-ego-browser` separately either. If either
111
+ was installed before the router, remove the standalone entries and reinstall the
112
+ router once so each loader is registered exactly once:
113
+
114
+ ```powershell
115
+ pnpm dsh plugin --profile web remove @liustack/modsearch
116
+ pnpm dsh plugin --profile web remove dsh-ego-browser
117
+ pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
118
+ ```
119
+
98
120
  5. Stop any already-running DSH process, then start the selected profile:
99
121
 
100
122
  ```powershell
@@ -112,7 +134,7 @@ the old process has released its profile lock:
112
134
  pnpm dsh web --no-open --port 3081
113
135
  ```
114
136
 
115
- ### Verify ModLens
137
+ ### Verify bundled capabilities
116
138
 
117
139
  Run the diagnostic from the installed Web profile (the command is forwarded to
118
140
  the profile's installed binary):
@@ -123,6 +145,15 @@ pnpm dsh plugin --profile web exec modlens doctor
123
145
 
124
146
  Configure the vision provider in the DSH settings page or in `C:\Users\<your-user>\.modlens\config.json`. Then create a conversation, upload an image, and ask the model to transcribe or explain it. Text-only models appear with a `(modlens vision)` entry when a compatible upstream route is available.
125
147
 
148
+ For ordinary web questions, ask for current sources or use the native `web_search`
149
+ tool; the bundle routes it through ModSearch. For a JavaScript, login, Cloudflare,
150
+ Turnstile, or other anti-bot page, the router provides the Ego Browser sequence:
151
+ `ego_space_open` -> `ego_navigate` -> `ego_page_info`/`ego_captcha` ->
152
+ `ego_snapshot`/`ego_screenshot`. When `humanCheck=true`, the current work package
153
+ pauses and the user completes the check in the Agent Browser observation window.
154
+ The router never fabricates a successful verification. Use `/router web` to inspect
155
+ the bundled versions and whether the Host services are detected.
156
+
126
157
  ### Update
127
158
 
128
159
  To install the newest version visible on npm:
@@ -133,10 +164,10 @@ pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersi
133
164
  ```
134
165
 
135
166
  For a reproducible deployment, replace `$routerVersion` with a concrete version
136
- that you have verified with `npm view` (for example `0.4.12`):
167
+ that you have verified with `npm view` (for example `0.4.14`):
137
168
 
138
169
  ```powershell
139
- pnpm dsh plugin --profile web add @ljwei-stak/model-router-galgame@0.4.12
170
+ pnpm dsh plugin --profile web add @ljwei-stak/model-router-galgame@0.4.14
140
171
  ```
141
172
 
142
173
  You can also ask pnpm to update an already-installed package within its declared
@@ -159,10 +190,11 @@ cd F:\DeepSeek_harness\DSH-Desktop
159
190
  pnpm dsh plugin --profile web remove @liustack/modlens
160
191
  $routerVersion = npm view @ljwei-stak/model-router-galgame version --registry=https://registry.npmjs.org/
161
192
  pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
162
- pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens"
193
+ pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens|dsh-approval-gate|modsearch|ego-browser"
163
194
  ```
164
195
 
165
- The dump should show one `modlens` row and one `model-router-galgame` row. If the
196
+ The dump should show one row for each of `modlens`, `dsh-approval-gate`, `modsearch`,
197
+ `ego-browser`, and `model-router-galgame`. If the
166
198
  error is `EADDRINUSE` on port 3080, or `task-board ledger is already owned by
167
199
  process ...`, an old DSH process is still running; close that process before
168
200
  starting another instance, or choose another port:
@@ -199,35 +231,331 @@ If no model is available, native model selection remains intact and the conversa
199
231
  - `/router mode collective`
200
232
  - `/router mode single`
201
233
  - `/router plan`
234
+ - `/router safety`: show the approval adapter version, current stage, task count, active route, and risk candidate.
235
+ - `/router web`: show ModSearch/Ego Browser versions, service detection, and the current web strategy.
202
236
 
203
237
  `collective` is the default. The plugin exposes task labels, scores, assignments, costs, and fallback records; it never exposes private model chain-of-thought.
204
238
 
239
+ ### Approval and multi-task safety
240
+
241
+ The bundle loads `dsh-approval-gate` together with the router. To enable its learning-based auto-approval mode, select the target plugin's `auto-approve` permission preset in the profile; the default `ask` policy continues to use the native Harness approval UI. The router does not decide approval outcomes. It only adds safety context before `approval/request` enters the waterfall:
242
+
243
+ - Complex collective tasks with at least three work packages carry `risk_candidate=bulk`, and each stage is audited separately.
244
+ - Deletion, credential, remote, system, and bulk risks remain human-only in `dsh-approval-gate`.
245
+ - Flash timeout, errors, unparseable output, or failed similarity checks fail safe to a human review.
246
+ - Single-session mode keeps the native model and approval behavior.
247
+
248
+ Audit, learning, and snapshots use the target project's directory: `%DSH_HOME%\auto-approve\` (or `%USERPROFILE%\.dsh\auto-approve\` when `DSH_HOME` is unset), including `events.jsonl`, `audit.log`, `allowlist.json`, `learning.json`, and `snapshots\`. Run `/router safety` to inspect the bridge state.
249
+
250
+ ### Web and anti-bot pages
251
+
252
+ ModSearch supplies ordinary web search and page reading, including source URLs and
253
+ fetch warnings. Pages that require login state, JavaScript rendering, or a visible
254
+ human check use Ego Browser. The model should open a browser space, navigate to the
255
+ URL, inspect `ego_page_info` or `ego_captcha`, and then collect evidence with
256
+ `ego_snapshot`, `ego_read_element`, `ego_screenshot`, or `ego_http(browser)`. Use
257
+ `ego_click`, `ego_fill`, and `ego_wait*` only when interaction is required. A
258
+ `humanCheck=true` result pauses the current work package until the user completes
259
+ the check in the Agent Browser window. Captcha and Cloudflare checks are never
260
+ bypassed or represented as successful without user confirmation.
261
+
205
262
  ## Mathematical routing model
206
263
 
207
- For a request `x`, the router derives a task type `t`, complexity band `c`, work-package set `I`, and candidate set `M`. Quality comes from a LiveBench task-category score (then overall, then the experimental baseline); prices come from user overrides or the baseline catalog.
264
+ This section describes the implementation in the form of the research framework in
265
+ `RESEARCH_PAPER_FRAMEWORK.md`. The production router and the offline experiment
266
+ plugin share the same objective, quality floors, cost model, and fallback semantics.
267
+ The production implementation adds Pareto pruning and bounded global search so that
268
+ the result is not merely a sequence of unrelated local choices.
269
+
270
+ ### 1. Problem definition
271
+
272
+ For a request `x`, the router constructs:
273
+
274
+ ```text
275
+ t task type: general, code, math, research, writing, summarization, vision
276
+ c complexity band: simple, balanced, or complex
277
+ I ordered work-package set
278
+ M discovered provider/model routes
279
+ F(i) quality floor for work package i
280
+ B optional per-request budget in USD
281
+ ```
282
+
283
+ The assignment is $\pi: I \to M$. The primary objective is to maximize multi-objective
284
+ utility while satisfying quality constraints. When a budget is configured, it is a
285
+ hard secondary constraint:
286
+
287
+ $$
288
+ \begin{aligned}
289
+ \text{maximize}\quad & \sum_{i \in I} U(i,\pi(i)) \\
290
+ \text{subject to}\quad & Q(i,\pi(i)) \ge F(i), && \forall i \in I \\
291
+ & \sum_{i \in I} \mathrm{Cost}(i,\pi(i)) \le B
292
+ \end{aligned}
293
+ $$
294
+
295
+ If no model satisfies a particular floor, the router chooses the highest-quality
296
+ available fallback and records `constraintRelaxed: true`; it never silently claims
297
+ that an unavailable constraint was satisfied.
298
+
299
+ ### 2. Request analysis and work-package construction
300
+
301
+ Task classification is signal based and deterministic. The classifier counts explicit
302
+ markers for code, mathematics, research, writing, summarization, and vision, then
303
+ uses the strongest signal as the primary type while retaining all detected types for
304
+ complex-task decomposition.
305
+
306
+ Complexity is a bounded score assembled from text length, list/requirement density,
307
+ domain markers, code/reasoning markers, and vision markers. The bands are:
208
308
 
209
- For work package `i` and model `m`:
309
+ $$
310
+ \begin{aligned}
311
+ \text{simple:}\quad & 0.00 \le \mathrm{complexity} < 0.34 \\
312
+ \text{balanced:}\quad & 0.34 \le \mathrm{complexity} < 0.66 \\
313
+ \text{complex:}\quad & 0.66 \le \mathrm{complexity} \le 1.00
314
+ \end{aligned}
315
+ $$
316
+
317
+ Simple and balanced requests use one execution package. A complex request is expanded
318
+ into a small DAG-like sequence:
210
319
 
211
320
  ```text
212
- U(i,m) = wq(c) Q(i,m) + wc(c) C(m) + wl(c) (1 - L(m))
213
- + ws(c) S(i,m) - wr(c) R(m)
214
- - lambda * 1[m is already used]
215
- - kappa * max(0, F(i) - Q(i,m))
321
+ analysis -> domain execution packages -> optional verification -> synthesis
216
322
  ```
217
323
 
218
- `F(i)` is the quality floor. Candidates satisfying `Q(i,m) >= F(i)` form the feasible set. A budget `B` is a secondary hard constraint: if the first utility assignment exceeds `B`, lower-criticality stages are replaced by the cheapest feasible candidates until the budget is met or no valid replacement remains. Synthesis prefers DeepSeek V4 Pro and falls back deterministically when unavailable.
324
+ Every package has an id, type, purpose, criticality, quality floor, and `dependsOn`
325
+ list. The default floors are `0.75`, `0.78`, and `0.82` for simple, balanced, and
326
+ complex work. A complex synthesis package has a minimum floor of `0.84`; critical
327
+ non-synthesis packages receive a small additional floor based on criticality.
328
+
329
+ ### 3. Model quality, specialty, cost, and risk
330
+
331
+ For route `m` and task type `t`, quality is resolved in this order:
332
+
333
+ $$
334
+ Q(m,t)=
335
+ \begin{cases}
336
+ \text{LiveBench category score}, & \text{when available};\\
337
+ \text{LiveBench overall score}, & \text{otherwise};\\
338
+ \text{catalog quality baseline}, & \text{otherwise}
339
+ \end{cases}
340
+ $$
341
+
342
+ Specialty `S(m,t)` is `1.0` for an explicit catalog specialty, `0.58` for a general
343
+ task, and a deterministic partial match for related domains. Risk `R(m)` and latency
344
+ `L(m)` are normalized catalog values; user pricing overrides only affect cost.
345
+
346
+ With input/output prices in USD per one million tokens, cache-aware cost is:
347
+
348
+ $$
349
+ \mathrm{Cost}(i,m)=
350
+ \frac{(n_{in}-n_{cache\_read}-n_{cache\_write})p_{in}
351
+ +n_{cache\_read}p_{cache\_read}
352
+ +n_{cache\_write}p_{cache\_write}
353
+ +n_{out}p_{out}}{10^6}
354
+ $$
355
+
356
+ The cache ratios are clamped to `[0,1]` and write ratio cannot overlap the read ratio.
357
+ If no cache ratio is configured, ordinary input pricing is used.
358
+
359
+ ### 4. Multi-objective utility
360
+
361
+ The implementation uses the normalized cost score
362
+ $C_{\mathrm{norm}}=1-p_{\mathrm{effective}}/p_{\max}$, so a lower price receives a
363
+ larger utility contribution. For a
364
+ work package `i` and candidate `m`:
365
+
366
+ $$
367
+ \begin{aligned}
368
+ U(i,m)={}&w_q(c)Q(i,m)+w_c(c)C_{\mathrm{norm}}(m)+w_l(c)(1-L(m))\\
369
+ &+w_s(c)S(i,m)-w_r(c)R(m)\\
370
+ &-\lambda\,\mathbb{1}[m\text{ already used}]
371
+ -\kappa\max(0,F(i)-Q(i,m))\\
372
+ &+\mathrm{synthesis\_bonus}(i,m)
373
+ \end{aligned}
374
+ $$
375
+
376
+ The default weight vectors are:
377
+
378
+ | Complexity | Quality | Cost | Latency | Specialty | Risk |
379
+ |---|---:|---:|---:|---:|---:|
380
+ | simple | 0.30 | 0.50 | 0.14 | 0.04 | 0.02 |
381
+ | balanced | 0.45 | 0.30 | 0.10 | 0.10 | 0.05 |
382
+ | complex | 0.55 | 0.16 | 0.06 | 0.16 | 0.07 |
383
+
384
+ For synthesis, the quality-oriented vector is `0.70/0.10/0.04/0.10/0.06`, and
385
+ DeepSeek V4 Pro receives a small deterministic preference bonus when present. The
386
+ bonus is soft: if that route is unavailable, the normal feasible ranking remains in
387
+ force. Reusing a route costs `0.08` utility; changing routes across a dependency
388
+ boundary costs `0.015` in the global assignment search.
389
+
390
+ ### 5. Production algorithm: Pareto-pruned constrained beam assignment
391
+
392
+ The current Host router is a bounded global solver with five stages.
393
+
394
+ #### 5.1 Candidate discovery and quality filtering
395
+
396
+ For each work package, routes below its quality floor are removed when at least one
397
+ qualified route exists. If none exists, at most the three highest-quality routes are
398
+ retained and the package is marked as relaxed. This makes constraint failure visible
399
+ and bounds the work on large model catalogs.
400
+
401
+ #### 5.2 Pareto pruning
402
+
403
+ Candidate `a` dominates candidate `b` for the same package when it is no worse in all
404
+ five dimensions and strictly better in at least one:
405
+
406
+ $$
407
+ Q(a)\ge Q(b),\quad \mathrm{Cost}(a)\le\mathrm{Cost}(b),\quad L(a)\le L(b),\quad
408
+ S(a)\ge S(b),\quad R(a)\le R(b)
409
+ $$
410
+
411
+ Dominated candidates cannot improve quality, cost, latency, specialty, or risk. The
412
+ router keeps the Pareto frontier plus three anchors: the cheapest candidate, the
413
+ highest-utility candidate, and the highest-quality candidate. The per-package pool is
414
+ limited to 12 routes.
415
+
416
+ #### 5.3 Dependency-aware beam search
417
+
418
+ Each beam state stores the partial assignment, route selected for every completed
419
+ package, total cost, utility, number of dependency handoffs, and accumulated quality
420
+ shortfall. States are expanded in package order. A child receives the candidate
421
+ utility minus `0.015` for every dependency edge that crosses to a different route.
422
+ The beam width is 256. Ties are resolved by quality shortfall, utility, cost,
423
+ handoffs, and finally lexical provider/model order, making repeated plans stable.
424
+
425
+ The search first minimizes constraint violations, then quality shortfall, and then
426
+ maximizes utility. With a budget, suffix minimum-cost bounds prune partial states that
427
+ cannot possibly fit the remaining budget.
428
+
429
+ #### 5.4 Budget strategy
430
+
431
+ The router evaluates three plans:
432
+
433
+ 1. an unconstrained utility plan;
434
+ 2. a utility plan that must fit `B`;
435
+ 3. when (2) is infeasible, a minimum-cost plan that still preserves every available
436
+ quality floor.
219
437
 
220
- The algorithm is wired into the Host request path: `index.mjs` calls `buildPlan` in collective mode and executes the planned stages; single-session mode preserves the explicitly selected model instead of applying the collective override. `router.test.mjs` covers complexity, mixed-domain decomposition, LiveBench and price overrides, and budget fallback; `collaboration.test.mjs` covers multi-stage execution and final synthesis.
438
+ If no floor-preserving plan exists, the least-cost best-quality fallback is returned,
439
+ `budgetExceeded` and/or `constraintRelaxed` are exposed in the audit record, and the
440
+ UI explains why the target could not be met. This is a global replacement strategy,
441
+ not a greedy “replace the last stage” rule.
221
442
 
222
- With user prices `p_in(m), p_out(m)` and estimated token counts:
443
+ #### 5.5 Production pseudocode
223
444
 
224
445
  ```text
225
- Cost(i,m) = (n_in(i) * p_in(m) + n_out(i) * p_out(m)) / 10^6
226
- TotalCost = sum_i Cost(i, assign(i))
227
- Saving = max(0, 1 - TotalCost / BaselineStrongCost)
446
+ BuildPlan(x, M, B):
447
+ (t, c, I) <- AnalyzeRequest(x)
448
+ for i in I:
449
+ P_i <- FeasibleCandidates(i, M)
450
+ P_i <- ParetoPrune(P_i) + {cheapest, best-utility, best-quality}
451
+ plan <- BeamAssign(I, P, B = infinity)
452
+ if B > 0:
453
+ budgetPlan <- BeamAssign(I, P, B)
454
+ plan <- budgetPlan if feasible
455
+ else BeamAssign(I, P, minimize total cost)
456
+ return auditable assignments, costs, floors, handoffs, and fallback flags
228
457
  ```
229
458
 
230
- Higher complexity increases the weight of quality, specialty, and criticality constraints; simple requests favor low cost and latency. The implementation is a bounded quality-constrained greedy solver with `O(|I||M| log |M|)` plan generation, suitable for interactive desktop use.
459
+ ### 6. Experiment algorithms
460
+
461
+ The `experiment-plugin` contains standalone implementations used by the six paper
462
+ experiments. They are intentionally deterministic and use the same model schema as
463
+ the Host router.
464
+
465
+ **QCG-Router (quality-constrained greedy / Pareto variant)**
466
+
467
+ QCG evaluates every model in $O(\lvert M\rvert)$, predicts quality from the baseline score plus a
468
+ specialty bonus, removes candidates below `F(i)`, computes the five-objective utility,
469
+ and selects the first Pareto/utility candidate. If the feasible set is empty, it
470
+ returns the highest-quality fallback with `constraintRelaxed: true`.
471
+
472
+ **AMO-Router (adaptive multi-objective routing)**
473
+
474
+ AMO starts from the paper's complexity-specific weights. After observing actual cost
475
+ and quality it computes:
476
+
477
+ $$
478
+ e_{cost}=\mathrm{clamp}\!\left(\frac{\mathrm{actual\_cost}-\mathrm{target\_cost}}
479
+ {\max(\mathrm{target\_cost},\varepsilon)}\right),\qquad
480
+ v_q=\max(0,\mathrm{quality\_floor}-\mathrm{actual\_quality})
481
+ $$
482
+
483
+ The feedback is exponentially smoothed (`0.10`). Positive cost error increases cost
484
+ pressure; a quality violation increases quality and specialty pressure. Weights are
485
+ projected back to the positive simplex after every update, so they remain finite,
486
+ positive, and sum to one. This fixes the sign ambiguity that could previously reduce
487
+ cost pressure when observed cost was too high.
488
+
489
+ **DAG-Assign (dependency-aware task allocation)**
490
+
491
+ DAG-Assign uses Kahn topological sorting, rejects unknown edge endpoints and cycles,
492
+ and computes criticality as the number of unique descendants plus `100` for a
493
+ synthesis node. For every node it keeps the QCG Pareto candidates, then runs a bounded
494
+ beam assignment with dependency handoff penalties, a synthesis quality bonus, and a
495
+ criticality bonus. Budget pruning uses suffix lower bounds; if the budget is
496
+ impossible, the result explicitly reports `budgetFeasible: false` instead of silently
497
+ assigning a below-floor model.
498
+
499
+ ### 7. Complexity and correctness properties
500
+
501
+ Let $N=\lvert M\rvert$, $K\le 12$ be the retained candidate pool,
502
+ $P=\lvert I\rvert$, and $W=256$ be
503
+ the beam width. The current implementation has the following bounded worst-case
504
+ costs:
505
+
506
+ | Component | Time complexity | Space complexity |
507
+ |---|---:|---:|
508
+ | Candidate scoring | $O(PN)$ | $O(PN)$ |
509
+ | Pairwise Pareto pruning | $O(PN^2)$ | $O(PN)$ |
510
+ | Beam assignment | $O(PWK)$ | $O(WK+P)$ |
511
+ | DAG topological sort | $O(\lvert V\rvert+\lvert E\rvert)$ | $O(\lvert V\rvert+\lvert E\rvert)$ |
512
+
513
+ The constants are small for desktop catalogs, and all loops are bounded by the
514
+ discovered routes, 12 candidates per package, and beam width 256.
515
+
516
+ The following invariants are enforced and exposed in the result:
517
+
518
+ 1. **Quality guarantee**: if a qualified candidate exists for a package, every normal
519
+ assignment considered by the solver satisfies $Q\ge F$.
520
+ 2. **Budget guarantee**: a plan marked `budgetFeasible: true` has estimated total cost
521
+ no greater than `B`, subject to the configured token and price estimates.
522
+ 3. **Dependency guarantee**: every collaboration stage is emitted in topological
523
+ order, and handoff count is recorded.
524
+ 4. **Determinism**: equal scores use stable cost and route-id tie breakers; repeated
525
+ input/catalog/settings produce the same plan.
526
+ 5. **Graceful degradation**: no models, failed providers, stale LiveBench data, and
527
+ unsatisfied floors are represented as explicit fallback metadata rather than
528
+ blocking the native Harness request path.
529
+
530
+ The beam solver is deliberately bounded. It provides an auditable, deterministic
531
+ near-optimal heuristic for interactive desktop routing, not a formal global-optimum
532
+ guarantee for arbitrary DAGs. A larger beam improves search coverage at the cost of
533
+ latency; Pareto pruning and suffix lower bounds keep the default `W = 256` practical.
534
+
535
+ ### 8. Cost and audit outputs
536
+
537
+ For every plan the router reports:
538
+
539
+ $$
540
+ \begin{aligned}
541
+ \mathrm{TotalCost}&=\sum_{i\in I}\mathrm{Cost}(i,\mathrm{assign}(i)),\\
542
+ \mathrm{BaselineCost}&=\text{cost of the strongest available model per package},\\
543
+ \mathrm{EstimatedSaving}&=\max\!\left(0,1-\frac{\mathrm{TotalCost}}{\mathrm{BaselineCost}}\right)
544
+ \end{aligned}
545
+ $$
546
+
547
+ The plan also contains per-stage token estimates, cache read/write tokens, predicted
548
+ quality, quality floor, provider/model, Pareto-pruned count, beam width, handoff count,
549
+ budget feasibility, and whether constraints were relaxed. `/router plan` and the GAL
550
+ analysis panel display these audit fields; neither exposes private model chain of
551
+ thought.
552
+
553
+ The algorithm is wired into the Host request path: `index.mjs` calls `buildPlan` in
554
+ collective mode and executes the planned stages. Single-session mode preserves the
555
+ explicitly selected model instead of applying the collective override. The regression
556
+ suite covers complexity, mixed-domain decomposition, LiveBench and price overrides,
557
+ budget behavior, Pareto pruning, AMO feedback direction, DAG ordering, multi-stage
558
+ execution, and final synthesis.
231
559
 
232
560
  ## OpenCode Zen settings
233
561
 
package/README.zh.md CHANGED
@@ -15,6 +15,8 @@
15
15
  - **GAL 视窗**:新会话自动形成存档;历史记录保留实际 provider/model,名牌、颜色和立绘随当前模型变化。ERNIE、文心一言和百度 provider/model 标识统一显示 `ERNIE娘` 与 `ernie1.png`。路由分析显示的是可审计摘要,不是模型私有思维链。
16
16
  - **Markdown/KaTeX**:复用 Harness 的 `MarkdownText`,支持标题、列表、表格、引用、代码、链接和数学公式;宽表格、代码块和公式在对话框内滚动,玩家输入保持纯文本。
17
17
  - **附件与多模态**:图片使用原生多模态管线,Markdown/TXT/JSON/代码文件提取为文本;PDF/DOCX 等二进制文件保留解析状态,不会静默伪造内容。
18
+ - **多任务安全审批适配**:内置 `dsh-approval-gate@0.5.0` 适配。复杂集体任务的每次沙箱升级都会附带工作包、阶段、路由和任务数量上下文;批量任务标记为 `bulk` 风险候选,由审批插件执行 Flash 判定、硬风险人工确认、学习白名单、审计和快照。单独会话不会被路由器强制切换。
19
+ - **联网与反爬窗口**:内置 `@liustack/modsearch@5.10.1` 接管原生 `web_search`,并提供 `read_page`/`x_search`;内置 `dsh-ego-browser@0.8.0` 提供真实 Chrome 窗口、语义树、截图、点击、登录态和验证码检测。ModSearch 抓取失败或遇到动态/反爬页面时,路由指导模型切换到 Ego Browser;检测到人机验证会暂停并让用户在观察窗完成。
18
20
  - **OpenCode Zen**:官方站点覆盖会自动恢复模型目录所需的 `/zen`、`/zen/v1` 端点;自定义网关不受影响。
19
21
  - **更新与桌面端**:插件设置页提供 GitHub Release 检查和一键更新;客户端过期时更新完整客户端及内置插件,否则只更新插件。网页端无法写入本机文件时会打开 Releases 页面。
20
22
 
@@ -29,7 +31,7 @@
29
31
 
30
32
  ### 推荐方式:安装已发布的 npm 包
31
33
 
32
- 这个公开包已经把官方 `@liustack/modlens@3.25.4` 作为依赖和 DSH bundle 一起打包。无需安装 Docker、Python、8000 端口服务,也无需再单独安装 ModLens。
34
+ 这个公开包会从 npm registry 安装官方 `@liustack/modlens@3.25.4`、`dsh-approval-gate@0.5.0`、`@liustack/modsearch@5.10.1` `dsh-ego-browser@0.8.0`,同时带上 Ego Browser 实际运行所需的 `@deepseek-ai/dsh-tools@0.1.0-rc.8`、`schemastery@3.18.0`。无需克隆 Ego Browser GitHub 仓库,也无需单独安装这些依赖。
33
35
 
34
36
  1. 在 PowerShell 中进入 DSH Desktop 目录:
35
37
 
@@ -74,7 +76,7 @@ pnpm dsh plugin --profile web add --registry=https://registry.npmjs.org "@ljwei-
74
76
  4. 检查路由器和它的 ModLens 依赖是否已加入 profile:
75
77
 
76
78
  ```powershell
77
- pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens"
79
+ pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens|dsh-approval-gate|modsearch|ego-browser"
78
80
  ```
79
81
 
80
82
  输出中应包含:
@@ -82,6 +84,9 @@ pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modle
82
84
  ```text
83
85
  @ljwei-stak/model-router-galgame
84
86
  @liustack/modlens
87
+ dsh-approval-gate
88
+ @liustack/modsearch
89
+ dsh-ego-browser
85
90
  ```
86
91
 
87
92
  安装本插件后不要再单独添加 `@liustack/modlens`。本插件已经包含官方 ModLens
@@ -93,6 +98,23 @@ pnpm dsh plugin --profile web remove @liustack/modlens
93
98
  pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
94
99
  ```
95
100
 
101
+ `modsearch` 和 `dsh-ego-browser` 也不要在同一 profile 中重复安装。重复条目会造成
102
+ 搜索 provider 或浏览器工具重复注册:
103
+
104
+ ```powershell
105
+ pnpm dsh plugin --profile web remove @liustack/modsearch
106
+ pnpm dsh plugin --profile web remove dsh-ego-browser
107
+ pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
108
+ ```
109
+
110
+ 同理,不要在同一个 profile 中再次单独添加 `dsh-approval-gate`。如果以前单独安装过,
111
+ 先删除旧条目再安装本插件,避免重复 loader:
112
+
113
+ ```powershell
114
+ pnpm dsh plugin --profile web remove dsh-approval-gate
115
+ pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
116
+ ```
117
+
96
118
  5. 关闭已经运行的 DSH,再启动对应 profile:
97
119
 
98
120
  ```powershell
@@ -129,10 +151,10 @@ pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersi
129
151
  ```
130
152
 
131
153
  如果需要可复现部署,请把 `$routerVersion` 替换为通过 `npm view` 确认过的具体版本号
132
- (例如 `0.4.12`):
154
+ (例如 `0.4.13`):
133
155
 
134
156
  ```powershell
135
- pnpm dsh plugin --profile web add @ljwei-stak/model-router-galgame@0.4.12
157
+ pnpm dsh plugin --profile web add @ljwei-stak/model-router-galgame@0.4.13
136
158
  ```
137
159
 
138
160
  已经安装过插件时,也可以让 pnpm 在当前版本范围内更新:
@@ -154,10 +176,10 @@ cd F:\DeepSeek_harness\DSH-Desktop
154
176
  pnpm dsh plugin --profile web remove @liustack/modlens
155
177
  $routerVersion = npm view @ljwei-stak/model-router-galgame version --registry=https://registry.npmjs.org/
156
178
  pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
157
- pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens"
179
+ pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens|dsh-approval-gate"
158
180
  ```
159
181
 
160
- 输出应只显示一条 `modlens` 行和一条 `model-router-galgame` 行。如果报
182
+ 输出应只显示一条 `modlens` 行、一条 `dsh-approval-gate` 行和一条 `model-router-galgame` 行。如果报
161
183
  `EADDRINUSE` 且端口为 3080,或报 `task-board ledger is already owned by process ...`,
162
184
  说明旧的 DSH 进程仍在运行;先关闭旧进程,或换一个端口启动:
163
185
 
@@ -193,35 +215,303 @@ pnpm dsh plugin --profile desktop remove @ljwei-stak/model-router-galgame
193
215
  - `/router mode collective`
194
216
  - `/router mode single`
195
217
  - `/router plan`
218
+ - `/router safety`:查看审批适配版本、当前阶段、任务数量、活动路由和风险候选。
219
+ - `/router web`:查看 ModSearch/Ego Browser 版本、服务探测状态和当前联网策略。
196
220
 
197
221
  默认模式为 `collective`。系统只公开任务分类、评分、分配、费用和回退记录,不输出任何模型私有思维链。
198
222
 
223
+ ### 审批与多任务安全策略
224
+
225
+ 安装后,profile 的 bundle 会同时加载 `dsh-approval-gate`。要启用其自动审批学习模式,
226
+ 请在 profile 的权限预设中选择目标插件提供的 `auto-approve`;默认 `ask` 策略仍然由
227
+ Harness 原生审批界面处理。路由器不会改变审批结论,只会在 `approval/request` 进入瀑布
228
+ 前补充安全上下文:
229
+
230
+ - 复杂集体任务(至少 3 个工作包)标记 `risk_candidate=bulk`,每个阶段单独审计。
231
+ - 删除、凭据、远程、系统和批量风险由 `dsh-approval-gate` 永久要求人工确认。
232
+ - Flash 超时、异常、输出无法解析或同类验证失败时,按 fail-safe 转人工。
233
+ - 单独会话保留原生模型和审批行为,不会被集体路由计划覆盖。
234
+
235
+ 审计、学习和快照沿用目标项目的目录:`%DSH_HOME%\auto-approve\`(未设置
236
+ `DSH_HOME` 时为 `%USERPROFILE%\.dsh\auto-approve\`),包括 `events.jsonl`、
237
+ `audit.log`、`allowlist.json`、`learning.json` 和 `snapshots\`。如需确认桥接状态,
238
+ 在会话中运行 `/router safety`。
239
+
240
+ ### 联网和反爬页面
241
+
242
+ 普通搜索和页面阅读由 ModSearch 处理,结果带有来源 URL、不确定性和抓取警告。需要
243
+ 登录态、JavaScript 动态渲染、验证码或 Cloudflare/Turnstile 的页面由 Ego Browser
244
+ 接管。模型应按以下顺序工作:`ego_space_open` → `ego_navigate` → `ego_page_info`/
245
+ `ego_captcha` → `ego_snapshot`/`ego_screenshot`;需要交互时使用 `ego_click`、
246
+ `ego_fill` 或 `ego_wait*`。检测到 `humanCheck=true` 后,当前工作包会暂停,用户在
247
+ “Agent 浏览器”观察窗完成验证,确认后再继续。系统不会尝试绕过验证码或伪造结果。
248
+
249
+ 在 Web profile 中,Ego Browser 会注册 `/api/ego/*` 观察窗路由;若安装了
250
+ `dsh-better-sidebar`,观察窗显示为“Agent 浏览器”侧边栏 Tab,否则显示浮动观察窗。
251
+
199
252
  ## 数学路由模型
200
253
 
201
- 对请求 `x`,系统先得到任务类型 `t`、复杂度档位 `c`、工作包集合 `I` 和候选模型集合 `M`。每个候选的质量来自 LiveBench 的任务分类分数(其次为 overall,最后回退实验基线),价格来自用户覆盖或实验目录。
254
+ 本节按照 `RESEARCH_PAPER_FRAMEWORK.md` 的论文框架,完整说明当前 `0.4.14`
255
+ 实现。生产路由器与离线实验插件共用质量下限、费用模型、目标函数和回退语义;
256
+ 生产实现进一步加入 Pareto 剪枝与有界全局搜索,因此不再是互相独立的逐阶段局部贪心。
257
+
258
+ ### 1. 问题定义
202
259
 
203
- 单个工作包 `i` 分配给模型 `m` 的效用为:
260
+ 给定用户请求 `x`,系统构造:
204
261
 
205
262
  ```text
206
- U(i,m) = wq(c) Q(i,m) + wc(c) C(m) + wl(c) (1 - L(m))
207
- + ws(c) S(i,m) - wr(c) R(m)
208
- - lambda * 1[m is already used]
209
- - kappa * max(0, F(i) - Q(i,m))
263
+ t 任务类型:general、code、math、research、writing、summarization、vision
264
+ c 复杂度档位:simple、balanced、complex
265
+ I 有序工作包集合
266
+ M 已发现的 provider/model 路由集合
267
+ F(i) 工作包 i 的质量下限
268
+ B 可选的单请求 USD 预算
210
269
  ```
211
270
 
212
- 其中 `F(i)` 是工作包质量下限;只有满足 `Q(i,m) >= F(i)` 的候选才优先进入可行集合。复杂任务还设置预算 `B`,若初始效用分配超预算,算法按关键度从低到高尝试替换为更便宜且仍满足质量下限的模型。最终汇总阶段默认优先 DeepSeek V4 Pro,若不可用则按候选排序回退。
271
+ 分配函数为 $\pi: I \to M$。首要目标是在满足质量约束的前提下最大化多目标效用;
272
+ 设置预算时,预算是第二层硬约束:
273
+
274
+ $$
275
+ \begin{aligned}
276
+ \text{最大化}\quad & \sum_{i \in I} U(i,\pi(i)) \\
277
+ \text{约束}\quad & Q(i,\pi(i)) \ge F(i), && \forall i \in I \\
278
+ & \sum_{i \in I} \mathrm{Cost}(i,\pi(i)) \le B
279
+ \end{aligned}
280
+ $$
281
+
282
+ 如果某个工作包没有任何模型达到质量下限,系统会选择可用候选中质量最高的回退模型,
283
+ 并写入 `constraintRelaxed: true`;不会把未满足的约束伪装成已满足。
213
284
 
214
- 该算法已经接入 Host 请求路径:`index.mjs` 在集体模式调用 `buildPlan`,并按计划执行各阶段;单独模式保留用户指定模型,不经过集体路由覆盖。`router.test.mjs` 覆盖复杂度、混合业务拆分、LiveBench 与价格覆盖及预算回退,`collaboration.test.mjs` 覆盖多阶段执行和最终整合。
285
+ ### 2. 请求分析与工作包构造
215
286
 
216
- 阶段费用使用用户价格 `p_in(m), p_out(m)` 和 token 估计:
287
+ 任务分类是确定性的信号计数过程。系统分别统计代码、数学、研究、写作、摘要和视觉
288
+ 关键词,以信号最多的类型作为主类型,同时保留所有检测到的类型用于复杂任务拆分。
289
+
290
+ 复杂度分数由文本长度、条目/要求密度、领域关键词、代码/推理标记和视觉标记组成,
291
+ 并限制在 `[0,1]`:
292
+
293
+ $$
294
+ \begin{aligned}
295
+ \text{simple:}\quad & 0.00 \le \mathrm{complexity} < 0.34 \\
296
+ \text{balanced:}\quad & 0.34 \le \mathrm{complexity} < 0.66 \\
297
+ \text{complex:}\quad & 0.66 \le \mathrm{complexity} \le 1.00
298
+ \end{aligned}
299
+ $$
300
+
301
+ 简单和均衡请求使用一个执行工作包。复杂请求展开为一个小型 DAG 序列:
217
302
 
218
303
  ```text
219
- Cost(i,m) = (n_in(i) * p_in(m) + n_out(i) * p_out(m)) / 10^6
220
- TotalCost = sum_i Cost(i, assign(i))
221
- Saving = max(0, 1 - TotalCost / BaselineStrongCost)
304
+ analysis -> 领域执行工作包 -> 可选 verification -> synthesis
222
305
  ```
223
306
 
224
- 复杂度越高,质量、专长和关键度约束越重要;简单请求则更多使用低价、低延迟模型。该实现是有界的质量约束贪心求解器,复杂度约为 `O(|I||M| log |M|)`,适合桌面端实时生成计划。
307
+ 每个工作包都具有 id、类型、用途、关键度、质量下限和 `dependsOn` 依赖列表。默认
308
+ 质量下限为:simple `0.75`、balanced `0.78`、complex `0.82`。复杂任务的 synthesis
309
+ 工作包最低为 `0.84`;关键度较高的非 synthesis 工作包会在基础下限上获得小幅增量。
310
+
311
+ ### 3. 模型质量、专长、费用与风险
312
+
313
+ 对于模型路由 `m` 和任务类型 `t`,质量按以下顺序解析:
314
+
315
+ $$
316
+ Q(m,t)=
317
+ \begin{cases}
318
+ \text{LiveBench 分类分数}, & \text{可用时};\\
319
+ \text{LiveBench overall 分数}, & \text{否则};\\
320
+ \text{仓库内实验基线分数}, & \text{否则}
321
+ \end{cases}
322
+ $$
323
+
324
+ 专长 `S(m,t)`:模型明确声明该专长时为 `1.0`;general 任务为 `0.58`;研究/写作等
325
+ 相关方向使用确定性的部分匹配。风险 `R(m)` 与延迟 `L(m)` 使用目录归一化值;用户
326
+ 价格覆盖只改变费用,不会伪造质量分数。
327
+
328
+ 输入/输出价格单位为 USD/百万 token,支持 prompt cache 的费用公式为:
329
+
330
+ $$
331
+ \mathrm{Cost}(i,m)=
332
+ \frac{(n_{in}-n_{cache\_read}-n_{cache\_write})p_{in}
333
+ +n_{cache\_read}p_{cache\_read}
334
+ +n_{cache\_write}p_{cache\_write}
335
+ +n_{out}p_{out}}{10^6}
336
+ $$
337
+
338
+ 缓存读写比例会被限制在 `[0,1]`,写入比例不会与读取比例重叠;没有配置缓存比例时,
339
+ 全部输入按普通输入价格计费。
340
+
341
+ ### 4. 多目标效用函数
342
+
343
+ 代码使用归一化费用效用
344
+ $C_{\mathrm{norm}}=1-p_{\mathrm{effective}}/p_{\max}$,因此
345
+ 实际价格越低,成本目标贡献越高。单个工作包 `i` 选择模型 `m` 的效用为:
346
+
347
+ $$
348
+ \begin{aligned}
349
+ U(i,m)={}&w_q(c)Q(i,m)+w_c(c)C_{\mathrm{norm}}(m)+w_l(c)(1-L(m))\\
350
+ &+w_s(c)S(i,m)-w_r(c)R(m)\\
351
+ &-\lambda\,\mathbb{1}[m\text{ 已经使用}]
352
+ -\kappa\max(0,F(i)-Q(i,m))\\
353
+ &+\mathrm{synthesis\_bonus}(i,m)
354
+ \end{aligned}
355
+ $$
356
+
357
+ 默认权重为:
358
+
359
+ | 复杂度 | 质量 | 成本 | 延迟 | 专长 | 风险 |
360
+ |---|---:|---:|---:|---:|---:|
361
+ | simple | 0.30 | 0.50 | 0.14 | 0.04 | 0.02 |
362
+ | balanced | 0.45 | 0.30 | 0.10 | 0.10 | 0.05 |
363
+ | complex | 0.55 | 0.16 | 0.06 | 0.16 | 0.07 |
364
+
365
+ synthesis 使用质量优先的 `0.70/0.10/0.04/0.10/0.06` 权重;存在 DeepSeek V4 Pro
366
+ 时只增加一个小的确定性偏好项,并非硬编码强制选择,不可用时仍按可行候选排序回退。
367
+ 重复使用同一路由扣除 `0.08` 效用;依赖边跨越不同模型时,在全局分配中每条边扣除
368
+ `0.015`,用于抑制不必要的上下文交接。
369
+
370
+ ### 5. 生产算法:Pareto 剪枝的质量约束 Beam Assignment
371
+
372
+ 当前 Host 路由器由五个步骤组成。
373
+
374
+ #### 5.1 候选发现与质量过滤
375
+
376
+ 对每个工作包,若至少存在一个达到质量下限的模型,就删除所有低于下限的候选;若一个
377
+ 都不存在,则保留质量最高的至多三个候选,并标记该工作包需要放宽约束。这样既保证正常
378
+ 情况下的质量硬约束,也让约束失败可见,并限制大模型目录下的计算量。
379
+
380
+ #### 5.2 Pareto 剪枝
381
+
382
+ 对于同一个工作包,候选 `a` 支配候选 `b` 的条件是:五个维度全部不差,且至少一个维度
383
+ 严格更好:
384
+
385
+ $$
386
+ Q(a)\ge Q(b),\quad \mathrm{Cost}(a)\le\mathrm{Cost}(b),\quad L(a)\le L(b),\quad
387
+ S(a)\ge S(b),\quad R(a)\le R(b)
388
+ $$
389
+
390
+ 被支配的模型不可能同时改善质量、费用、延迟、专长或风险,因此可以安全删除。系统保留
391
+ Pareto 前沿,并额外保留三个锚点:最低费用、最高综合效用和最高质量候选;每个工作包
392
+ 最终至多保留 12 条路由。
393
+
394
+ #### 5.3 依赖感知的 Beam Search
395
+
396
+ 每个 Beam 状态保存:部分分配、已完成工作包的路由映射、累计费用、累计效用、依赖交接
397
+ 次数和质量缺口。算法按工作包顺序扩展状态;如果当前模型与依赖工作包模型不同,就按
398
+ 每条依赖边扣除 `0.015`。Beam 宽度为 256。并列状态按质量缺口、综合效用、费用、交接
399
+ 次数和 provider/model 字典序稳定决胜,因此相同输入会得到相同方案。
400
+
401
+ 搜索排序优先减少质量约束违规,再减少质量缺口,最后最大化效用。预算搜索使用“后缀最低
402
+ 费用”下界,提前剪掉即使后续全部使用最便宜模型也无法满足预算的部分状态。
403
+
404
+ #### 5.4 预算策略
405
+
406
+ 系统依次评估三种方案:
407
+
408
+ 1. 不设预算的效用最优方案;
409
+ 2. 必须满足 `B` 的效用方案;
410
+ 3. 如果第 2 项不可行,则求仍保持所有可用质量下限的最低费用方案。
411
+
412
+ 如果连保持质量下限的方案都不存在,就返回“质量最高的低成本回退”,并在审计结果中明确
413
+ 写入 `budgetExceeded` 和/或 `constraintRelaxed`。这是全局组合替换,不是简单地只替换
414
+ 最后一个阶段。
415
+
416
+ #### 5.5 生产伪代码
417
+
418
+ ```text
419
+ BuildPlan(x, M, B):
420
+ (t, c, I) <- AnalyzeRequest(x)
421
+ for i in I:
422
+ P_i <- FeasibleCandidates(i, M)
423
+ P_i <- ParetoPrune(P_i) + {最低费用、最高效用、最高质量}
424
+ plan <- BeamAssign(I, P, B = infinity)
425
+ if B > 0:
426
+ budgetPlan <- BeamAssign(I, P, B)
427
+ plan <- budgetPlan if feasible
428
+ else BeamAssign(I, P, minimize total cost)
429
+ return assignments、费用、质量下限、交接次数和回退标记
430
+ ```
431
+
432
+ ### 6. 实验插件中的三种算法
433
+
434
+ `experiment-plugin` 提供论文六项实验使用的独立算法实现。它们使用与 Host 相同的模型
435
+ 字段,并采用固定随机种子保证离线结果可复现。
436
+
437
+ **QCG-Router(质量约束贪心 / Pareto 变体)**
438
+
439
+ QCG 对每个模型进行质量、费用、延迟、专长和风险评估;质量预测为基线分数加专长奖励,
440
+ 复杂任务有小幅复杂度扣减。算法先过滤低于 `F(i)` 的候选,再从 Pareto 前沿中选择效用
441
+ 最高者。若没有可行候选,则返回质量最高的回退并设置 `constraintRelaxed: true`。
442
+
443
+ **AMO-Router(自适应多目标路由)**
444
+
445
+ AMO 从论文规定的三组复杂度权重开始。得到真实费用和质量后计算:
446
+
447
+ $$
448
+ e_{cost}=\mathrm{clamp}\!\left(\frac{\mathrm{actual\_cost}-\mathrm{target\_cost}}
449
+ {\max(\mathrm{target\_cost},\varepsilon)}\right),\qquad
450
+ v_q=\max(0,\mathrm{quality\_floor}-\mathrm{actual\_quality})
451
+ $$
452
+
453
+ 反馈使用 `0.10` 的指数平滑。实际费用高于目标时提高成本目标权重;质量违反时提高质量
454
+ 和专长权重。每次更新后投影回正权重单纯形,保证五个权重有限、为正且总和为 1。这样修正
455
+ 了旧实现中“费用超标反而降低成本压力”的符号问题。
456
+
457
+ **DAG-Assign(依赖感知任务分配)**
458
+
459
+ DAG-Assign 使用 Kahn 拓扑排序,拒绝未知节点边和环;关键度定义为唯一后继数量,并为
460
+ synthesis 节点额外加 `100`。每个节点保留 QCG Pareto 候选,然后以 Beam 宽度 256 做
461
+ 依赖感知分配,加入依赖交接惩罚、synthesis 质量奖励和关键度奖励。预算剪枝使用后缀最低
462
+ 费用;如果预算不可能满足,结果显式报告 `budgetFeasible: false`,而不是静默分配低于质量
463
+ 下限的模型。
464
+
465
+ ### 7. 复杂度与正确性性质
466
+
467
+ 令 $N=\lvert M\rvert$、$K\le 12$ 为剪枝后的候选数、
468
+ $P=\lvert I\rvert$、$W=256$ 为 Beam 宽度,
469
+ 当前实现的有界最坏情况复杂度为:
470
+
471
+ | 组件 | 时间复杂度 | 空间复杂度 |
472
+ |---|---:|---:|
473
+ | 候选评分 | $O(PN)$ | $O(PN)$ |
474
+ | 两两 Pareto 剪枝 | $O(PN^2)$ | $O(PN)$ |
475
+ | Beam 分配 | $O(PWK)$ | $O(WK+P)$ |
476
+ | DAG 拓扑排序 | $O(\lvert V\rvert+\lvert E\rvert)$ | $O(\lvert V\rvert+\lvert E\rvert)$ |
477
+
478
+ 桌面端模型目录通常较小,且所有循环都受已发现路由数、每包 12 个候选和 Beam 宽度 256
479
+ 限制,适合交互式生成计划。
480
+
481
+ 系统强制并在结果中公开以下不变量:
482
+
483
+ 1. **质量保证**:只要某个工作包存在合格候选,正常分配中所有候选都满足 $Q\ge F$。
484
+ 2. **预算保证**:标记 `budgetFeasible: true` 的方案,其估算总费用不超过 `B`(以配置的
485
+ token 和价格估算为准)。
486
+ 3. **依赖保证**:协作阶段按拓扑顺序输出,并记录模型交接次数。
487
+ 4. **确定性**:相同分数按费用和路由 id 稳定决胜;相同输入、目录和设置产生相同方案。
488
+ 5. **优雅降级**:没有模型、provider 失败、LiveBench 过期或质量下限不可满足时,都通过
489
+ 显式回退元数据表达,不阻塞 Harness 原生请求路径。
490
+
491
+ Beam 求解器是有界的。它为桌面端交互式路由提供可审计、确定性的近似最优启发式,
492
+ 并不声称对任意 DAG 都能形式化保证全局最优。增大 Beam 可以提高搜索覆盖率,但会增加
493
+ 决策延迟;Pareto 剪枝和后缀费用下界使默认 `W = 256` 保持可用。
494
+
495
+ ### 8. 费用与审计输出
496
+
497
+ 每个计划输出:
498
+
499
+ $$
500
+ \begin{aligned}
501
+ \mathrm{TotalCost}&=\sum_{i\in I}\mathrm{Cost}(i,\mathrm{assign}(i)),\\
502
+ \mathrm{BaselineCost}&=\text{每个工作包使用当前可用最高质量模型的费用},\\
503
+ \mathrm{EstimatedSaving}&=\max\!\left(0,1-\frac{\mathrm{TotalCost}}{\mathrm{BaselineCost}}\right)
504
+ \end{aligned}
505
+ $$
506
+
507
+ 同时输出每阶段 token 估计、缓存读写 token、预测质量、质量下限、provider/model、Pareto
508
+ 剪枝数量、Beam 宽度、交接次数、预算可行性及约束是否放宽。`/router plan` 和 GAL 路由
509
+ 分析面板展示这些可审计字段,但不会展示模型私有思维链。
510
+
511
+ 算法已接入 Host 请求路径:集体模式由 `index.mjs` 调用 `buildPlan` 并按计划执行;单独
512
+ 模式保留用户明确选择的模型,不被集体路由覆盖。回归测试覆盖复杂度、混合业务拆分、
513
+ LiveBench/价格覆盖、预算行为、Pareto 剪枝、AMO 反馈方向、DAG 顺序、多阶段执行和最终
514
+ 整合。
225
515
 
226
516
  ## OpenCode Zen 设置
227
517
 
package/cordis.patch.yml CHANGED
@@ -3,11 +3,26 @@
3
3
  - insert:
4
4
  - id: modlens
5
5
  name: '@liustack/modlens'
6
+ # ModSearch replaces the native web_search provider and adds read_page/x_search.
7
+ - id: modsearch
8
+ name: '@liustack/modsearch'
9
+ # Ego Browser supplies visible Chromium tools for JS-heavy and anti-bot pages.
10
+ - id: ego-browser
11
+ name: 'dsh-ego-browser'
12
+ # The router annotates multi-task approval requests; dsh-approval-gate
13
+ # owns the Flash decision, human handoff, audit and snapshot UI.
14
+ - id: dsh-approval-gate
15
+ name: 'dsh-approval-gate'
6
16
  # The package dependency is transitive from the profile's point of view,
7
17
  # so this bundle entry is needed when the router is installed by itself.
8
18
  - id: model-router-galgame
9
19
  name: '@ljwei-stak/model-router-galgame'
10
20
 
21
+ # Route DSH's native web_search seam through ModSearch's engine chain.
22
+ - id: web
23
+ config:
24
+ searchProvider: modsearch
25
+
11
26
  # ModLens consumes the durable image blocks on the Host pre-step. Leave the
12
27
  # describe-image fallback available for explicit use, but do not rewrite every
13
28
  # text-model upload into a markdown tool reference before ModLens can read it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ljwei-stak/model-router-galgame",
3
- "version": "0.4.12",
3
+ "version": "0.4.14",
4
4
  "type": "module",
5
5
  "description": "DeepSeek Harness plugin: adaptive model routing, cost analysis and GAL view",
6
6
  "repository": {
@@ -48,7 +48,12 @@
48
48
  "node": ">=22.19"
49
49
  },
50
50
  "dependencies": {
51
- "@liustack/modlens": "3.25.4"
51
+ "dsh-approval-gate": "0.5.0",
52
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.8",
53
+ "@liustack/modsearch": "5.10.1",
54
+ "dsh-ego-browser": "0.8.0",
55
+ "@liustack/modlens": "3.25.4",
56
+ "schemastery": "3.18.0"
52
57
  },
53
58
  "peerDependencies": {
54
59
  "@deepseek-ai/dsh-settings": "^0.1.1-rc.1",