@ljwei-stak/dsh-model-route-bot 0.64.0 → 0.65.0
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 +694 -407
- package/README.i18n.yaml +2 -2
- package/README.md +209 -147
- package/README.zh.md +208 -146
- package/apps/integration-client/bot-attention.mjs +2 -2
- package/apps/integration-client/panels.jsx +239 -134
- package/apps/integration-client/product.css +36 -0
- package/apps/integration-client/routing-submissions.mjs +68 -0
- package/apps/integration-client/task-progress.mjs +46 -0
- package/apps/integration-client/tasks-panel.jsx +147 -0
- package/modules/bot-runtime/read-model.mjs +2 -2
- package/package.json +6 -87
- package/packages/model-router-plugin/README.md +39 -15
- package/packages/model-router-plugin/README.zh.md +40 -16
- package/packages/model-router-plugin/package.json +1 -1
- package/packages/orchestration/src/bot-routing-candidates.mjs +6 -6
- package/packages/orchestration/src/bot-routing-service.mjs +30 -5
- package/packages/orchestration/src/bot-routing.mjs +6 -4
- package/packages/orchestration/src/product-composition.mjs +18 -24
- package/packages/orchestration/src/task-journal.mjs +23 -0
- package/packages/orchestration/src/task-product.mjs +49 -0
- package/remote-computer-agent/build.mjs +48 -13
- package/CHANGELOG.md +0 -456
- package/docs/INSTALLATION_GUIDE.zh.md +0 -150
- package/docs/MANUAL_TASK_ASSIGNMENT.zh.md +0 -46
- package/docs/ROUTING_ADAPTIVE_LEARNING.zh.md +0 -153
- package/docs/ROUTING_ALGORITHM_OPTIMIZATION.zh.md +0 -91
- package/docs/ROUTING_DERIVATION_EXPERIMENTS.zh.md +0 -684
- package/docs/ROUTING_LIVEBENCH_DATA_SEARCH.zh.md +0 -74
- package/docs/ROUTING_LIVEBENCH_VALIDATION.zh.md +0 -154
- package/docs/ROUTING_MATURITY.zh.md +0 -76
- package/docs/ROUTING_RESEARCH.zh.md +0 -59
- package/docs/ROUTING_SELFTEST_STEPS.zh.md +0 -192
- package/docs/WORKBENCH_USER_GUIDE.zh.md +0 -293
- package/docs/archive/MIGRATION.md +0 -53
- package/docs/archive/README.md +0 -21
- package/docs/assets/candidate-pruning.svg +0 -80
- package/docs/assets/desktop-official-tools-0.9.0.png +0 -0
- package/docs/assets/router-only-0.12.0.png +0 -0
- package/docs/assets/routing-workflow.svg +0 -96
- package/docs/assets/workbench-budget-reservations.png +0 -0
- package/docs/assets/workbench-run-progress.png +0 -0
- package/docs/assets/workbench-run-timeout.png +0 -0
- package/docs/assets/workbench-subscription-usage.png +0 -0
- package/docs/assets/workbench-task-preview.jpg +0 -0
- package/docs/assets/workbench-usage.svg +0 -119
- package/docs/releases/0.20.0-validation.md +0 -26
- package/docs/releases/0.22.0-validation.md +0 -28
- package/docs/releases/0.23.0-validation.md +0 -30
- package/docs/releases/0.23.1-validation.md +0 -23
- package/docs/releases/0.24.0-validation.md +0 -41
- package/docs/releases/0.25.0-validation.md +0 -41
- package/docs/releases/0.25.1-validation.md +0 -36
- package/docs/releases/0.25.2-validation.md +0 -48
- package/docs/releases/0.25.3-validation.md +0 -50
- package/docs/releases/0.25.4-validation.md +0 -41
- package/docs/releases/0.25.5-validation.md +0 -40
- package/docs/releases/0.26.0-preview.0-validation.md +0 -23
- package/docs/releases/0.26.0-preview.1-f-difference.json +0 -6845
- package/docs/releases/0.26.0-preview.1-f-difference.md +0 -85
- package/docs/releases/0.26.0-preview.1-validation.md +0 -103
- package/docs/releases/0.63.0-validation.md +0 -51
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// One unresolved submission per connected product API. Keep only its reference in window memory;
|
|
2
|
+
// tasks, outputs, credentials and previews never enter this cache or browser storage.
|
|
3
|
+
const scopes = new WeakMap()
|
|
4
|
+
const same = (left, right) => left?.requestKey === right?.requestKey && left?.requesterBotId === right?.requesterBotId
|
|
5
|
+
const reference = value => {
|
|
6
|
+
for (const field of ['requestKey', 'requesterBotId']) {
|
|
7
|
+
if (typeof value?.[field] !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(value[field])) throw new TypeError('INVALID_ROUTING_SUBMISSION_REFERENCE')
|
|
8
|
+
}
|
|
9
|
+
return { requestKey: value.requestKey, requesterBotId: value.requesterBotId }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// This portable code is a lookup reference, never authority or a task to execute.
|
|
13
|
+
export function submissionCode(input) {
|
|
14
|
+
const value = reference(input)
|
|
15
|
+
return `dsh-route-v1:${value.requesterBotId}:${value.requestKey}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseCode(code) {
|
|
19
|
+
if (typeof code !== 'string' || code.length > 1024) throw new TypeError('INVALID_ROUTING_SUBMISSION_CODE')
|
|
20
|
+
const match = /^dsh-route-v1:([A-Za-z0-9_-]{1,128}):([A-Za-z0-9_-]{1,128})$/.exec(code.trim())
|
|
21
|
+
if (!match) throw new TypeError('INVALID_ROUTING_SUBMISSION_CODE')
|
|
22
|
+
return { requesterBotId: match[1], requestKey: match[2] }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function routingSubmissions(scope) {
|
|
26
|
+
if (!scope || !['object', 'function'].includes(typeof scope)) throw new TypeError('ROUTING_SUBMISSION_SCOPE_REQUIRED')
|
|
27
|
+
if (scopes.has(scope)) return scopes.get(scope)
|
|
28
|
+
let value = null
|
|
29
|
+
const listeners = new Set()
|
|
30
|
+
const publish = next => { value = next === null ? null : Object.freeze(next); for (const listener of [...listeners]) listener() }
|
|
31
|
+
const store = Object.freeze({
|
|
32
|
+
getSnapshot: () => value,
|
|
33
|
+
subscribe: listener => { listeners.add(listener); return () => listeners.delete(listener) },
|
|
34
|
+
start(input) {
|
|
35
|
+
const identity = reference(input)
|
|
36
|
+
if (value) return false
|
|
37
|
+
publish({ ...identity, state: 'submitting', planId: null, planState: null })
|
|
38
|
+
return true
|
|
39
|
+
},
|
|
40
|
+
restore(code) {
|
|
41
|
+
if (value) return false
|
|
42
|
+
publish({ ...parseCode(code), state: 'unknown', planId: null, planState: null })
|
|
43
|
+
return true
|
|
44
|
+
},
|
|
45
|
+
update(input, result) {
|
|
46
|
+
if (!same(value, input)) return false
|
|
47
|
+
if (!['unknown', 'not-found', 'confirmed'].includes(result.state)) throw new TypeError('INVALID_ROUTING_SUBMISSION_STATE')
|
|
48
|
+
// A delayed failed lookup cannot erase a receipt that already confirmed acceptance.
|
|
49
|
+
if (value.state === 'confirmed' && result.state !== 'confirmed') return false
|
|
50
|
+
publish({ ...reference(input), state: result.state,
|
|
51
|
+
planId: result.state === 'confirmed' ? result.planId : null,
|
|
52
|
+
planState: result.state === 'confirmed' ? result.planState ?? (value.state === 'confirmed' ? value.planState : null) : null })
|
|
53
|
+
return true
|
|
54
|
+
},
|
|
55
|
+
clear(input) {
|
|
56
|
+
if (!same(value, input) || value.state !== 'confirmed') return false
|
|
57
|
+
publish(null)
|
|
58
|
+
return true
|
|
59
|
+
},
|
|
60
|
+
dismissUnconfirmed(input) {
|
|
61
|
+
if (!same(value, input) || value.state !== 'not-found') return false
|
|
62
|
+
publish(null)
|
|
63
|
+
return true
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
scopes.set(scope, store)
|
|
67
|
+
return store
|
|
68
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { deriveBotAttention } from './bot-attention.mjs'
|
|
2
|
+
|
|
3
|
+
export const taskIdOf = row => row.taskId ?? row.id
|
|
4
|
+
export const taskKey = row => JSON.stringify([row.botId ?? '', taskIdOf(row)])
|
|
5
|
+
export const taskTitle = row => row.title || row.input?.task || taskIdOf(row) || '未命名任务'
|
|
6
|
+
export const TASK_FILTERS = Object.freeze([['all', '全部'], ['active', '进行中'], ['attention', '待处理'], ['finished', '已结束']])
|
|
7
|
+
const active = new Set(['queued', 'ready', 'running', 'admitting', 'cancelling'])
|
|
8
|
+
const ended = new Set(['succeeded', 'cancelled', 'skipped'])
|
|
9
|
+
|
|
10
|
+
/** Counts describe only the loaded, committed records. A truncated view says so in the UI. */
|
|
11
|
+
export function taskProgress(snapshot) {
|
|
12
|
+
const items = snapshot?.tasks?.status === 'available' && Array.isArray(snapshot.tasks.items) ? snapshot.tasks.items : []
|
|
13
|
+
const attention = deriveBotAttention({ ...snapshot, tasks: { items } }, { limit: Infinity })
|
|
14
|
+
const attentionKeys = new Set(attention.map(row => taskKey(row)))
|
|
15
|
+
const category = row => attentionKeys.has(taskKey(row)) ? 'attention' : active.has(row.state) ? 'active' : ended.has(row.state) ? 'finished' : 'other'
|
|
16
|
+
const bots = new Map((snapshot?.bots ?? []).map(bot => [bot.id, { botId: bot.id, name: bot.name, all: 0, active: 0, attention: 0, finished: 0, other: 0 }]))
|
|
17
|
+
const counts = { all: items.length, active: 0, attention: 0, finished: 0, other: 0 }
|
|
18
|
+
for (const row of items) {
|
|
19
|
+
const kind = category(row); counts[kind]++
|
|
20
|
+
if (!bots.has(row.botId)) bots.set(row.botId, { botId: row.botId, name: row.botId || '未取得归属', all: 0, active: 0, attention: 0, finished: 0, other: 0 })
|
|
21
|
+
const bot = bots.get(row.botId); bot.all++; bot[kind]++
|
|
22
|
+
}
|
|
23
|
+
return { items, attention, counts, bots: [...bots.values()], category }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function filterTasks(progress, { botId = '', status = 'all', query = '' } = {}) {
|
|
27
|
+
const names = new Map(progress.bots.map(bot => [bot.botId, bot.name]))
|
|
28
|
+
const text = query.trim().toLocaleLowerCase()
|
|
29
|
+
return progress.items.filter(row => (!botId || row.botId === botId) && (status === 'all' || progress.category(row) === status)
|
|
30
|
+
&& (!text || [taskTitle(row), row.botId, names.get(row.botId), taskIdOf(row), row.planId, row.operationId, row.attemptId]
|
|
31
|
+
.some(value => typeof value === 'string' && value.toLocaleLowerCase().includes(text))))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const TASK_EVENTS = Object.freeze({
|
|
35
|
+
queued: '任务已排队', dispatched: '开始执行', 'admission-prepared': '开始资金准入',
|
|
36
|
+
'admission-reference-prepared': '执行引用已保存', 'financial-admission-committed': '资金准入已提交',
|
|
37
|
+
'execution-linked-before-admission': '已关联原执行记录', settled: '本次尝试已结算',
|
|
38
|
+
'cancel-requested': '已请求停止,等待确认', 'cancel-requested-unconfirmed': '已请求停止,原操作仍待核对',
|
|
39
|
+
'plan-cancel-requested': '已请求停止整个计划', 'cancelled-before-dispatch': '启动前已停止',
|
|
40
|
+
'cancelled-while-waiting-budget': '等待预算期间已停止', 'dependency-blocked': '前置任务失败,尚未执行',
|
|
41
|
+
interrupted: '宿主中断,执行结果待核对', reconciled: '已核对原执行记录', blocked: '任务受阻',
|
|
42
|
+
deadline: '已到任务期限', 'budget-wait-expired': '预算等待已到期', 'retry-fenced': '重试已被拦截',
|
|
43
|
+
'admission-conflict': '资金准入记录冲突,需核对', 'dispatch-closed-before-entry': '启动前已阻止派发',
|
|
44
|
+
'admission-closed-not-dispatched': '已确认未派发', 'admission-recovered-cancelled': '恢复时确认启动前已停止',
|
|
45
|
+
'admission-recovered-not-dispatched': '恢复时确认原操作未派发',
|
|
46
|
+
})
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
import { TASK_STATES } from './api.mjs'
|
|
3
|
+
import { botAttentionSummary, remoteUncertain } from './bot-attention.mjs'
|
|
4
|
+
import { filterTasks, taskProgress, taskIdOf, taskKey, taskTitle, TASK_FILTERS, TASK_EVENTS } from './task-progress.mjs'
|
|
5
|
+
import { UiIcon } from './ui-icons.jsx'
|
|
6
|
+
|
|
7
|
+
const Tag = ({ children }) => <span className="bp-tag">{children}</span>
|
|
8
|
+
const date = value => value === null || value === undefined || !Number.isFinite(new Date(value).getTime()) ? '未取得' : new Date(value).toLocaleString('zh-CN')
|
|
9
|
+
const Value = ({ label, children }) => <div className="bp-value"><dt>{label}</dt><dd>{children ?? '未取得'}</dd></div>
|
|
10
|
+
const stopKnown = row => !row.result?.remote || row.result.remote.stopConfirmed === true
|
|
11
|
+
|
|
12
|
+
function TaskActions({ row, actions, dispatch, busy }) {
|
|
13
|
+
const pending = useRef(false)
|
|
14
|
+
const run = async action => {
|
|
15
|
+
if (busy || pending.current || !dispatch || !actions.includes(action)) return
|
|
16
|
+
pending.current = true
|
|
17
|
+
try { await dispatch(action, { botId: row.botId, taskId: taskIdOf(row), ...(action === 'task.cancel' ? {} : { requestKey: crypto.randomUUID() }) }) }
|
|
18
|
+
finally { pending.current = false }
|
|
19
|
+
}
|
|
20
|
+
return <>
|
|
21
|
+
{actions.includes('task.cancel') && !['cancelled', 'succeeded', 'failed', 'skipped'].includes(row.state) && <button disabled={busy} onClick={() => { void run('task.cancel') }}>停止</button>}
|
|
22
|
+
{actions.includes('task.retry') && ['failed', 'cancelled', 'waiting-budget'].includes(row.state) && <button disabled={busy || !stopKnown(row)} onClick={() => { if (stopKnown(row)) void run('task.retry') }}>重试</button>}
|
|
23
|
+
{actions.includes('task.reconcile') && row.state === 'needs-reconciliation' && <button disabled={busy} onClick={() => { void run('task.reconcile') }}>核对原收据</button>}
|
|
24
|
+
</>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function RemoteNotice({ row }) {
|
|
28
|
+
const remote = row.result?.remote
|
|
29
|
+
if (!remote) return null
|
|
30
|
+
if (!stopKnown(row)) return <small role="status">写入或停止尚未确认,请核对原操作;不会自动重发,也暂不能重试。</small>
|
|
31
|
+
if (remote.notStarted === true && remote.possiblyApplied !== true) return <small>原操作未启动。</small>
|
|
32
|
+
if (remote.status === 'succeeded') return <small>写入已确认。{row.state === 'cancelled' ? '取消未撤销文件变更。' : ''}</small>
|
|
33
|
+
return remote.possiblyApplied === true ? <small>操作已停止,文件可能已改变;重试不会撤销已有变更,请先核对产物。</small> : null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function TaskDetail({ task, inspect, busy, actions, dispatch, onClose, bot, openSession }) {
|
|
37
|
+
const [history, setHistory] = useState(null), [failure, setFailure] = useState(''), [loading, setLoading] = useState(false)
|
|
38
|
+
const [reload, setReload] = useState(0)
|
|
39
|
+
const ref = useRef(null)
|
|
40
|
+
const key = taskKey(task)
|
|
41
|
+
useEffect(() => { ref.current?.focus({ preventScroll: true }); ref.current?.scrollIntoView?.({ block: 'nearest' }) }, [key])
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const controller = new AbortController()
|
|
44
|
+
setHistory(null); setFailure(''); setLoading(false)
|
|
45
|
+
if (!inspect || !actions.includes('task.inspect')) return () => controller.abort()
|
|
46
|
+
setLoading(true)
|
|
47
|
+
Promise.resolve().then(() => { if (!controller.signal.aborted) return inspect({ botId: task.botId, taskId: taskIdOf(task) }, controller.signal) }).then(value => {
|
|
48
|
+
if (controller.signal.aborted) return
|
|
49
|
+
if (!value?.task || taskKey(value.task) !== key || !Array.isArray(value.attempts) || !Array.isArray(value.timeline?.items)
|
|
50
|
+
|| (task.updatedAt != null && (!Number.isFinite(value.task.updatedAt) || value.task.updatedAt < task.updatedAt))) throw new Error('返回记录与当前任务不一致,请刷新后再次读取。')
|
|
51
|
+
setHistory(value)
|
|
52
|
+
}).catch(cause => { if (!controller.signal.aborted) setFailure(cause.message || '任务时间线读取失败。') })
|
|
53
|
+
.finally(() => { if (!controller.signal.aborted) setLoading(false) })
|
|
54
|
+
return () => controller.abort()
|
|
55
|
+
}, [key, task.updatedAt, inspect, actions.includes('task.inspect'), reload])
|
|
56
|
+
const data = history?.task ?? task
|
|
57
|
+
return <article className="bp-card bp-task-detail" aria-label="任务详情" ref={ref} tabIndex={-1}>
|
|
58
|
+
<div className="bp-card-title"><div><h2>任务详情</h2><p>{taskTitle(task)}</p></div><button type="button" onClick={onClose}>关闭</button></div>
|
|
59
|
+
<div className="bp-actions"><Tag>{TASK_STATES[data.state] ?? data.state}</Tag><span>{bot?.name ?? task.botId ?? '未取得归属'}</span>
|
|
60
|
+
{bot?.sessionId && openSession && <button type="button" onClick={() => openSession(bot.sessionId)}>打开 Bot 私聊</button>}
|
|
61
|
+
<TaskActions row={data} actions={actions} dispatch={dispatch} busy={busy} /></div>
|
|
62
|
+
<RemoteNotice row={data} />
|
|
63
|
+
<dl className="bp-facts"><Value label="创建">{date(data.createdAt)}</Value><Value label="最近更新">{date(data.updatedAt)}</Value><Value label="期限">{date(data.deadlineAt)}</Value></dl>
|
|
64
|
+
<h3>任务时间线</h3>
|
|
65
|
+
{loading && <p role="status" className="bp-muted">正在读取原任务日志…</p>}
|
|
66
|
+
{failure && <p role="alert" className="bp-error">{failure}</p>}
|
|
67
|
+
{inspect && actions.includes('task.inspect') && <button type="button" disabled={loading} onClick={() => setReload(value => value + 1)}>重新读取时间线</button>}
|
|
68
|
+
{!actions.includes('task.inspect') && <p className="bp-muted">此宿主尚未提供任务时间线,可在下方查看现有收据依据。</p>}
|
|
69
|
+
{history && <>
|
|
70
|
+
<p className="bp-muted">{history.attempts.length} 次执行尝试 · {history.timeline.total} 条日志{history.timeline.truncated ? `,显示最近 ${history.timeline.items.length} 条` : ''}。停止请求需等待原操作确认。</p>
|
|
71
|
+
<ol className="bp-task-timeline">{history.timeline.items.map(event => <li key={event.seq}>
|
|
72
|
+
<span className="bp-timeline-dot" aria-hidden="true" /><div><strong>{TASK_EVENTS[event.kind] ?? '任务状态更新'}</strong>
|
|
73
|
+
<small><time>{date(event.at)}</time>{event.attemptNumber != null ? ` · 第 ${event.attemptNumber} 次尝试` : ''}{event.outcome ? ` · ${TASK_STATES[event.outcome] ?? (event.outcome === 'unknown' ? '结果未知' : event.outcome)}` : ''}</small>
|
|
74
|
+
{event.code && <small className="bp-code">{event.code}</small>}</div>
|
|
75
|
+
</li>)}</ol>
|
|
76
|
+
{history.timeline.items.length === 0 && <p className="bp-muted">原日志中暂无事件。</p>}
|
|
77
|
+
<details className="bp-technical"><summary>每次尝试的身份与时间</summary><ul className="bp-checklist">{history.attempts.map(attempt => <li key={attempt.attemptId}>
|
|
78
|
+
<strong>第 {attempt.number} 次 · {TASK_STATES[attempt.state] ?? attempt.state}</strong><small>开始 {date(attempt.startedAt)} · 结束 {date(attempt.finishedAt)}</small><small className="bp-code">{attempt.attemptId} · {attempt.operationId}</small>
|
|
79
|
+
</li>)}</ul></details>
|
|
80
|
+
</>}
|
|
81
|
+
<details className="bp-technical"><summary>原任务与操作编号</summary><dl className="bp-facts"><Value label="Task">{taskIdOf(data)}</Value><Value label="Package">{data.packageId}</Value><Value label="Attempt">{data.attemptId}</Value><Value label="Operation">{data.operationId}</Value><Value label="MR 运行">{data.legacyRunIds?.join('、')}</Value></dl></details>
|
|
82
|
+
<details className="bp-technical"><summary>执行与收据依据</summary><pre>{JSON.stringify({ attempts: history?.attempts ?? task.attempts, executionRef: task.executionRef, remoteReceipt: data.result?.remote, reconciliation: data.reconciliation, packages: task.packages }, null, 2)}</pre></details>
|
|
83
|
+
</article>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function TaskWorkspace({ snapshot, dispatch, busy, scope, inspect, openSession, routingCard }) {
|
|
87
|
+
const progress = useMemo(() => taskProgress(snapshot), [snapshot])
|
|
88
|
+
const [botId, setBotId] = useState(''), [status, setStatus] = useState('all'), [query, setQuery] = useState('')
|
|
89
|
+
const [expanded, setExpanded] = useState(false), [selection, setSelection] = useState(null)
|
|
90
|
+
useEffect(() => { setBotId(''); setStatus('all'); setQuery(''); setExpanded(false); setSelection(null) }, [scope])
|
|
91
|
+
const rows = filterTasks(progress, { botId, status, query })
|
|
92
|
+
const task = selection && selection.scope === scope ? progress.items.find(row => taskKey(row) === selection.key) : null
|
|
93
|
+
const actions = snapshot.availableActions ?? []
|
|
94
|
+
const select = row => setSelection({ scope, key: taskKey(row) })
|
|
95
|
+
const attention = expanded ? progress.attention : progress.attention.slice(0, 6)
|
|
96
|
+
const [showPlans, setShowPlans] = useState(false)
|
|
97
|
+
return <>
|
|
98
|
+
<section className="bp-card bp-task-overview" aria-label="多 Bot 任务总览">
|
|
99
|
+
<div className="bp-card-title"><div><h2>团队任务</h2><p className="bp-muted">按 Bot 查看进展,优先处理需要你介入的任务。</p></div><Tag>{progress.items.length} 条已加载</Tag></div>
|
|
100
|
+
{snapshot.tasks.truncated && <p role="status" className="bp-blocked">已加载 {progress.items.length} / {snapshot.tasks.total} 条任务,优先显示待处理与进行中的记录。下方数量与筛选仅针对已加载任务。</p>}
|
|
101
|
+
<div className="bp-task-counts" aria-label="按状态筛选任务">{TASK_FILTERS.map(([value, label]) => <button key={value} type="button" aria-pressed={status === value} onClick={() => setStatus(value)}><strong>{progress.counts[value]}</strong><span>{label}</span></button>)}</div>
|
|
102
|
+
<div className="bp-task-bots" aria-label="按 Bot 筛选任务"><button type="button" aria-pressed={!botId} onClick={() => setBotId('')}>全部 Bot</button>{progress.bots.map(bot => <button key={bot.botId ?? ''} type="button" aria-pressed={botId === bot.botId} onClick={() => setBotId(bot.botId ?? '')}>
|
|
103
|
+
<UiIcon name="bot" size={16} /><span>{bot.name}<small>{bot.active} 进行中 · {bot.attention} 待处理 · {bot.all} 条任务</small></span></button>)}</div>
|
|
104
|
+
</section>
|
|
105
|
+
{progress.attention.length > 0 && <section className="bp-card bp-attention" aria-label="需要你处理">
|
|
106
|
+
<div className="bp-card-title"><h2>需要你处理</h2><Tag>{botAttentionSummary(progress.attention)}</Tag></div>
|
|
107
|
+
<ul className="bp-attention-list">{attention.map(row => {
|
|
108
|
+
const original = progress.items.find(task => taskKey(task) === taskKey(row))
|
|
109
|
+
return <li key={taskKey(row)} className={`bp-attention-row bp-attention-${row.stage}`}><div><strong>{row.title}</strong><p className="bp-muted">{row.detail}</p>
|
|
110
|
+
{row.stage === 'approve' && <small>打开 Bot 私聊查看原审批请求。</small>}
|
|
111
|
+
{remoteUncertain(original) && <small>原操作仍待核对,请查看任务详情。</small>}</div><div className="bp-actions">
|
|
112
|
+
<button type="button" onClick={() => select(original)}>打开任务</button>
|
|
113
|
+
{row.stage === 'approve' && openSession && snapshot.bots?.find(bot => bot.id === row.botId)?.sessionId && <button type="button" onClick={() => openSession(snapshot.bots.find(bot => bot.id === row.botId).sessionId)}>打开审批会话</button>}
|
|
114
|
+
{row.stage === 'reconcile' && actions.includes('task.reconcile') && <TaskActions row={original} actions={['task.reconcile']} dispatch={dispatch} busy={busy} />}
|
|
115
|
+
{row.stage === 'failed' && actions.includes('task.retry') && <TaskActions row={original} actions={['task.retry']} dispatch={dispatch} busy={busy} />}
|
|
116
|
+
</div></li>
|
|
117
|
+
})}</ul>
|
|
118
|
+
{progress.attention.length > 6 && <button type="button" aria-expanded={expanded} onClick={() => setExpanded(value => !value)}>{expanded ? '收起待处理事项' : `查看全部 ${progress.attention.length} 项待处理`}</button>}
|
|
119
|
+
</section>}
|
|
120
|
+
{task && <TaskDetail key={taskKey(task)} task={task} inspect={inspect} busy={busy} actions={actions} dispatch={dispatch} onClose={() => setSelection(null)} bot={snapshot.bots?.find(bot => bot.id === task.botId)} openSession={openSession} />}
|
|
121
|
+
<section className="bp-card" aria-label="任务记录"><div className="bp-card-title"><h2>任务记录</h2><span className="bp-muted" role="status">显示 {rows.length} / {progress.items.length} 条</span></div>
|
|
122
|
+
<div className="bp-task-search"><label>搜索任务<input type="search" value={query} placeholder="任务名称、Bot 或任务 / 计划 / 操作编号" onChange={event => setQuery(event.target.value)} /></label>
|
|
123
|
+
{(query || botId || status !== 'all') && <button type="button" onClick={() => { setQuery(''); setBotId(''); setStatus('all') }}>清除筛选</button>}</div>
|
|
124
|
+
{!progress.items.length ? <p className="bp-empty">尚无持久任务。在 Main 私聊中提出目标后,可在这里查看工作包、执行尝试与收据。</p> : rows.length === 0 ? <p className="bp-empty">没有符合当前筛选的任务。可清除筛选查看其他记录。</p> : <div className="bp-table-wrap"><table>
|
|
125
|
+
<thead><tr><th>任务</th><th>Bot</th><th>状态</th><th>执行尝试</th><th>操作</th></tr></thead><tbody>{rows.map(row => <tr key={taskKey(row)}>
|
|
126
|
+
<td><button className="bp-text-button" onClick={() => select(row)}>{taskTitle(row)}</button><small>更新 {date(row.updatedAt)}</small></td>
|
|
127
|
+
<td>{snapshot.bots?.find(bot => bot.id === row.botId)?.name ?? row.botId ?? '未取得归属'}</td><td><Tag>{TASK_STATES[row.state] ?? row.state}</Tag>{row.blockedReason && <small>{row.blockedReason}</small>}<RemoteNotice row={row} /></td>
|
|
128
|
+
<td>{Number.isSafeInteger(row.attempts) ? row.attempts : row.attempts?.length ?? row.attemptCount ?? '未知'}</td><td className="bp-actions"><TaskActions row={row} actions={actions} dispatch={dispatch} busy={busy} /></td>
|
|
129
|
+
</tr>)}</tbody></table></div>}
|
|
130
|
+
</section>
|
|
131
|
+
{snapshot.tasks.plans?.length > 0 && <section className="bp-task-plans"><button type="button" aria-expanded={showPlans} onClick={() => setShowPlans(value => !value)}>{showPlans ? '收起协作计划' : `查看 ${snapshot.tasks.plans.length} 个协作计划`}</button>
|
|
132
|
+
{/* Dependency and cancellation evidence stays visible even when other plans are collapsed. */}
|
|
133
|
+
{snapshot.tasks.plans.filter(plan => showPlans || plan.needsReconciliation || !['succeeded', 'cancelled', 'failed'].includes(plan.state)).map((plan, index) => <article className="bp-card" key={plan.planId}>
|
|
134
|
+
<div className="bp-card-title"><div><h2>协作计划 {index + 1}</h2><p className="bp-muted">{plan.packages.length} 个工作包 · {plan.packages.filter(item => item.state === 'succeeded').length} 个已完成</p></div><div className="bp-actions"><Tag>{TASK_STATES[plan.state] ?? plan.state}</Tag>
|
|
135
|
+
{actions.includes('task.plan.cancel') && !['succeeded', 'cancelled'].includes(plan.state) && <button disabled={busy} onClick={() => dispatch('task.plan.cancel', { botId: plan.ownerBotId, planId: plan.planId })}>停止整个计划</button>}</div></div>
|
|
136
|
+
{plan.needsReconciliation && <p role="status">有工作包停止尚未确认,请核对原收据;计划尚未完成停止。</p>}
|
|
137
|
+
<ul className="bp-plan-list">{plan.packages.map((item, step) => {
|
|
138
|
+
const linked = progress.items.find(row => taskIdOf(row) === item.taskId && row.botId === item.targetBotId)
|
|
139
|
+
return <li key={item.id}><span className="bp-plan-step">{item.state === 'succeeded' ? <UiIcon name="check" size={13} /> : step + 1}</span><div className="bp-plan-copy">
|
|
140
|
+
{linked ? <button className="bp-text-button" onClick={() => select(linked)}>{taskTitle(linked)}</button> : <strong>{plan.plan?.packages?.find(pkg => pkg.id === item.id)?.objective ?? `工作包 ${step + 1}`}</strong>}
|
|
141
|
+
<small>{snapshot.bots?.find(bot => bot.id === item.targetBotId)?.name ?? item.targetBotId}{item.dependsOn.length > 0 && ` · 前置:${item.dependsOn.join('、')}`}{item.result?.code === 'DEPENDENCY_FAILED' && ' · 前置包失败,尚未执行'}</small></div><Tag>{TASK_STATES[item.state] ?? item.state}</Tag></li>
|
|
142
|
+
})}</ul><details className="bp-technical bp-plan-id"><summary>计划编号</summary><p className="bp-code">{plan.planId}</p></details>
|
|
143
|
+
</article>)}
|
|
144
|
+
</section>}
|
|
145
|
+
{routingCard}
|
|
146
|
+
</>
|
|
147
|
+
}
|
|
@@ -11,13 +11,13 @@ const missing = {
|
|
|
11
11
|
usage: blocked('宿主尚未提供统一费用与预占投影。', '接入唯一 MR 资金库的只读投影;已有 Token 用量仍可在 DS 用量页查看。'),
|
|
12
12
|
}
|
|
13
13
|
const ACTIONS = new Set([
|
|
14
|
-
'task.cancel', 'task.retry', 'task.reconcile', 'task.plan.submit', 'task.plan.cancel', 'usage.resolve',
|
|
14
|
+
'task.inspect', 'task.cancel', 'task.retry', 'task.reconcile', 'task.plan.submit', 'task.plan.cancel', 'usage.resolve',
|
|
15
15
|
'binding.save', 'funding.account.save', 'machine.secret', 'machine.pair', 'machine.bind', 'machine.probe',
|
|
16
16
|
'machine.revoke', 'machine.reconcile', 'machine.artifact', 'machine.desktop.view', 'machine.desktop.act',
|
|
17
17
|
'machine.desktop.open', 'machine.desktop.renew', 'machine.desktop.close',
|
|
18
18
|
'library.list', 'library.read', 'library.write', 'library.upload', 'library.download', 'library.remove', 'library.mkdir', 'library.search',
|
|
19
19
|
'routine.list', 'routine.run', 'routine.save', 'routine.remove', 'routine.history', 'routine.cancel', 'routine.resolve', 'roles.list',
|
|
20
|
-
'botRoutingInspect', 'botRoutingDecide', 'botRouting.dispatch', 'botRouting.save',
|
|
20
|
+
'botRoutingInspect', 'botRoutingDecide', 'botRouting.dispatch', 'botRouting.save', 'botRouting.status',
|
|
21
21
|
'botRuntimeStatus', 'botRuntimeMove', 'botRuntimeHistory', 'botRuntimeMessage',
|
|
22
22
|
])
|
|
23
23
|
const plain = value => value && typeof value === 'object' && !Array.isArray(value)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ljwei-stak/dsh-model-route-bot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DSH Bot workspace with integrated model routing, official tools and remote computers",
|
|
6
6
|
"repository": {
|
|
@@ -90,56 +90,18 @@
|
|
|
90
90
|
"README.md",
|
|
91
91
|
"README.zh.md",
|
|
92
92
|
"README.i18n.yaml",
|
|
93
|
-
"docs/WORKBENCH_USER_GUIDE.zh.md",
|
|
94
|
-
"docs/ROUTING_RESEARCH.zh.md",
|
|
95
|
-
"docs/ROUTING_DERIVATION_EXPERIMENTS.zh.md",
|
|
96
|
-
"docs/ROUTING_LIVEBENCH_VALIDATION.zh.md",
|
|
97
|
-
"docs/ROUTING_LIVEBENCH_DATA_SEARCH.zh.md",
|
|
98
|
-
"docs/ROUTING_SELFTEST_STEPS.zh.md",
|
|
99
|
-
"docs/ROUTING_ADAPTIVE_LEARNING.zh.md",
|
|
100
|
-
"docs/ROUTING_MATURITY.zh.md",
|
|
101
|
-
"docs/assets/workbench-task-preview.jpg",
|
|
102
|
-
"docs/assets/routing-workflow.svg",
|
|
103
|
-
"docs/assets/desktop-official-tools-0.9.0.png",
|
|
104
|
-
"docs/assets/workbench-usage.svg",
|
|
105
|
-
"docs/assets/candidate-pruning.svg",
|
|
106
|
-
"docs/INSTALLATION_GUIDE.zh.md",
|
|
107
|
-
"docs/archive/MIGRATION.md",
|
|
108
93
|
"LICENSE",
|
|
109
|
-
"CHANGELOG.md",
|
|
110
|
-
"docs/assets/router-only-0.12.0.png",
|
|
111
|
-
"docs/releases/0.20.0-validation.md",
|
|
112
94
|
".dsh-plugin/shared/reasoning-execution.mjs",
|
|
113
95
|
".dsh-plugin/shared/routing-evidence.mjs",
|
|
114
96
|
".dsh-plugin/shared/quality-gates.mjs",
|
|
115
|
-
"docs/ROUTING_ALGORITHM_OPTIMIZATION.zh.md",
|
|
116
97
|
".dsh-plugin/shared/package-assignments.mjs",
|
|
117
98
|
".dsh-plugin/shared/manual-plan.mjs",
|
|
118
99
|
".dsh-plugin/shared/run-jobs.mjs",
|
|
119
|
-
"docs/MANUAL_TASK_ASSIGNMENT.zh.md",
|
|
120
|
-
"docs/releases/0.22.0-validation.md",
|
|
121
|
-
"docs/releases/0.23.0-validation.md",
|
|
122
|
-
"docs/assets/workbench-run-progress.png",
|
|
123
|
-
"docs/releases/0.23.1-validation.md",
|
|
124
|
-
"docs/assets/workbench-run-timeout.png",
|
|
125
100
|
".dsh-plugin/shared/budget-reservations.mjs",
|
|
126
101
|
".dsh-plugin/shared/execution-lifecycle.mjs",
|
|
127
|
-
"docs/releases/0.24.0-validation.md",
|
|
128
|
-
"docs/assets/workbench-budget-reservations.png",
|
|
129
102
|
".dsh-plugin/shared/execution-accounting.mjs",
|
|
130
103
|
".dsh-plugin/shared/subscription-usage.mjs",
|
|
131
|
-
"docs/releases/0.25.0-validation.md",
|
|
132
|
-
"docs/releases/0.25.1-validation.md",
|
|
133
|
-
"docs/releases/0.25.2-validation.md",
|
|
134
|
-
"docs/releases/0.25.3-validation.md",
|
|
135
|
-
"docs/assets/workbench-subscription-usage.png",
|
|
136
104
|
".dsh-plugin/shared/zcode-npm-shims.mjs",
|
|
137
|
-
"docs/releases/0.25.4-validation.md",
|
|
138
|
-
"docs/releases/0.25.5-validation.md",
|
|
139
|
-
"docs/releases/0.26.0-preview.0-validation.md",
|
|
140
|
-
"docs/releases/0.26.0-preview.1-validation.md",
|
|
141
|
-
"docs/releases/0.26.0-preview.1-f-difference.json",
|
|
142
|
-
"docs/releases/0.26.0-preview.1-f-difference.md",
|
|
143
105
|
".dsh-plugin/shared/legacy-policy.mjs",
|
|
144
106
|
"modules/bot-runtime/index.mjs",
|
|
145
107
|
"modules/bot-runtime/read-model.mjs",
|
|
@@ -367,61 +329,18 @@
|
|
|
367
329
|
"remote-computer-agent/package-lock.json",
|
|
368
330
|
"modules/bot-runtime/schedule-compat.mjs",
|
|
369
331
|
"modules/bot-runtime/product-overlay/",
|
|
370
|
-
"docs/releases/0.63.0-validation.md",
|
|
371
332
|
".dsh-plugin/router-core.mjs",
|
|
372
333
|
".dsh-plugin/bot-product.mjs"
|
|
373
334
|
],
|
|
374
335
|
"scripts": {
|
|
375
336
|
"build:client": "node scripts/build-client.mjs",
|
|
337
|
+
"build:router-package": "node scripts/build-router-package.mjs",
|
|
376
338
|
"check:client": "node scripts/build-client.mjs --check",
|
|
377
|
-
"
|
|
378
|
-
"
|
|
379
|
-
"
|
|
380
|
-
"experiment:routing-regression": "node scripts/routing-regression-study.mjs",
|
|
381
|
-
"experiment:routing-summary": "node scripts/summarize-routing-search.mjs",
|
|
382
|
-
"experiment:routing-adaptive": "node scripts/adaptive-routing-study.mjs",
|
|
383
|
-
"audit:livebench": "node scripts/livebench-aggregate-audit.mjs",
|
|
339
|
+
"build:agent": "node remote-computer-agent/build.mjs",
|
|
340
|
+
"check:source": "node scripts/integration-product-source.mjs",
|
|
341
|
+
"pretest": "node scripts/prepare-product-tests.mjs",
|
|
384
342
|
"test": "node scripts/run-tests.mjs",
|
|
385
|
-
"prepack": "node scripts/prepack.mjs"
|
|
386
|
-
"pack:local": "npm pack --pack-destination dist",
|
|
387
|
-
"test:routing-algorithm": "node --test tests/effort-execution.test.mjs tests/routing-evidence.test.mjs tests/routing-query-effort.test.mjs tests/routing-search-frontier.test.mjs tests/quality-gates.test.mjs tests/algorithm-host-integration.test.mjs tests/quality-attempt-ledger.test.mjs",
|
|
388
|
-
"build:routing-evidence": "node scripts/build-routing-evidence.mjs",
|
|
389
|
-
"test:manual-assignment": "node --test tests/manual-package-execution.test.mjs tests/manual-plan.test.mjs tests/manual-assignment-ui.test.mjs tests/manual-assignment-host.test.mjs",
|
|
390
|
-
"check:readme": "node scripts/check-readme.mjs",
|
|
391
|
-
"integration:status": "node scripts/integration-progress.mjs status",
|
|
392
|
-
"integration:check": "node scripts/integration-progress.mjs check",
|
|
393
|
-
"integration:report": "node scripts/integration-progress.mjs report",
|
|
394
|
-
"test:integration": "node --test tests/integration-progress.test.mjs tests/integration-host-probe.test.mjs tests/integration-inventory.test.mjs tests/integration-documents.test.mjs tests/integration-combined-host.test.mjs tests/capability-authority.test.mjs tests/capability-bridge.test.mjs tests/bot-policy.test.mjs tests/legacy-policy.test.mjs tests/ds-lifecycle.test.mjs tests/ds-workspace-privacy.test.mjs tests/deferred-router-state.test.mjs tests/task-contract.test.mjs tests/task-reconciliation.test.mjs",
|
|
395
|
-
"test:cli-native": "node --test tests/cli-runtime-native.test.mjs tests/cli-native-peer-job.native.mjs tests/cli-native-peer-probe.native.mjs",
|
|
396
|
-
"test:product-refresh": "node --test tests/product-refresh-order.test.mjs",
|
|
397
|
-
"integration:probe-cli-native": "node scripts/integration-cli-native.mjs",
|
|
398
|
-
"integration:probe-remote-agent": "node scripts/integration-remote-agent.mjs",
|
|
399
|
-
"integration:probe-remote-agent-browser": "node scripts/integration-remote-agent-browser.mjs",
|
|
400
|
-
"integration:probe-remote-agent-desktop": "node scripts/integration-remote-agent-desktop.mjs",
|
|
401
|
-
"integration:probe-product-source": "node scripts/integration-product-source.mjs",
|
|
402
|
-
"integration:probe-product-client": "node scripts/integration-product-client.mjs --check",
|
|
403
|
-
"integration:probe-product-upstream": "node scripts/integration-product-upstream-tests.mjs",
|
|
404
|
-
"integration:probe-product-host": "node scripts/integration-product-host.mjs",
|
|
405
|
-
"integration:probe-cli-runtime": "node scripts/integration-cli-runtime.mjs",
|
|
406
|
-
"integration:probe-host": "node scripts/integration-host-probe.mjs",
|
|
407
|
-
"integration:probe-sqlite": "node scripts/integration-sqlite-probe.mjs",
|
|
408
|
-
"integration:inventory": "node scripts/integration-inventory.mjs",
|
|
409
|
-
"integration:upstream-proposal": "node scripts/integration-upstream-proposal.mjs",
|
|
410
|
-
"integration:upstream-include": "node scripts/integration-upstream-include.mjs",
|
|
411
|
-
"integration:probe-combined": "node scripts/integration-combined-host.mjs",
|
|
412
|
-
"integration:probe-linux-isolation": "node scripts/integration-linux-isolation.mjs",
|
|
413
|
-
"integration:probe-finance": "node scripts/integration-finance-probe.mjs",
|
|
414
|
-
"integration:probe-tasks": "node scripts/integration-task-probe.mjs",
|
|
415
|
-
"integration:probe-codex-profile": "node scripts/integration-codex-profile.mjs",
|
|
416
|
-
"integration:probe-product-default": "node scripts/integration-product-default.mjs",
|
|
417
|
-
"integration:probe-remote-machines": "node scripts/integration-remote-machines.mjs",
|
|
418
|
-
"integration:probe-cli-execution": "node scripts/integration-cli-execution.mjs",
|
|
419
|
-
"integration:probe-web-search-config": "node scripts/integration-web-search-config.mjs",
|
|
420
|
-
"integration:migrate-profile": "node scripts/integration-migrate-profile.mjs",
|
|
421
|
-
"integration:probe-migration-audit": "node scripts/integration-migration-audit.mjs",
|
|
422
|
-
"integration:probe-remote-container": "node scripts/integration-remote-container.mjs",
|
|
423
|
-
"integration:probe-cli-installed": "node scripts/integration-cli-installed.mjs",
|
|
424
|
-
"build:router-package": "node scripts/build-router-package.mjs"
|
|
343
|
+
"prepack": "node remote-computer-agent/build.mjs && node scripts/prepack.mjs"
|
|
425
344
|
},
|
|
426
345
|
"license": "MIT",
|
|
427
346
|
"engines": {
|
|
@@ -1,34 +1,58 @@
|
|
|
1
1
|
# DSH Model Router
|
|
2
2
|
|
|
3
|
-
[
|
|
3
|
+
English · [简体中文](README.zh.md)
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@ljwei-stak/dsh-model-router)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Task-oriented model routing inside the standard DeepSeek Harness interface.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Describe the result you want, choose suitable configured models or native CLI tools, review the plan, then follow execution and accounting from one workbench. This edition adds a **Model Router** sidebar entry and keeps the existing Harness workspace.
|
|
10
|
+
|
|
11
|
+
**Stable version: 0.65.0.** For Bot conversations, multi-Bot delegation, remote computers, private libraries, and routines, use the [full Bot edition](https://github.com/Alice-Marx/dsh-model-router/blob/main/README.md): **`@ljwei-stak/dsh-model-route-bot`**. Select one edition per profile.
|
|
10
12
|
|
|
11
13
|
## Install
|
|
12
14
|
|
|
13
|
-
In Harness **Plugins → Add plugin**, enter `@ljwei-stak/dsh-model-router@0.
|
|
15
|
+
1. In Harness **Plugins → Add plugin**, enter `@ljwei-stak/dsh-model-router@0.65.0`.
|
|
16
|
+
2. Enable it, quit Harness including its tray process, and restart.
|
|
17
|
+
3. Configure provider credentials/models in Harness **Models**, then open **Model Router** from the sidebar.
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
dsh plugin --profile desktop add @ljwei-stak/dsh-model-router@0.65.0
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Run your first task
|
|
24
|
+
|
|
25
|
+
1. In **Model configuration**, refresh exact routes and enter known prices/capabilities. Unknown information can remain blank.
|
|
26
|
+
2. In **Task & execution**, start with **Project check**, **Organize documentation**, or **Compare approaches**, then edit the task and acceptance criteria. **Undo template** restores the previous text.
|
|
27
|
+
3. Choose **Next: preview execution → Preview execution plan**. Inspect models/tools, workspace, permissions, and API cost or subscription usage estimates.
|
|
28
|
+
4. Confirm the required items to start execution. Review output, receipts, cancellation, and supported retries in **Execution history**.
|
|
29
|
+
|
|
30
|
+
The current application uses Chinese interface labels; these English descriptions explain the corresponding controls.
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
*Models, prices, usage, and paths in the screenshot are examples; it does not show a live model run.*
|
|
14
35
|
|
|
15
|
-
##
|
|
36
|
+
## Workbench features
|
|
16
37
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
38
|
+
| Page | What it provides |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| Task & execution / 任务与执行 | Single-route, dependency-based team, and exact-model planning; optional route comparison and manual package assignment; preview and confirmed execution. |
|
|
41
|
+
| Model configuration / 模型配置 | Exact route profiles, prices, quality evidence, billing preferences, and CLI model mappings. |
|
|
42
|
+
| Native tools / 官方工具 | Detection, supported install methods, login/readiness inspection, and interactive CLI terminals. |
|
|
43
|
+
| Budget & safety / 预算与安全 | Per-task estimates, API daily/monthly limits and reservations, separate subscription calls/tokens, review policy, and explicit feedback. |
|
|
44
|
+
| Execution history / 执行记录 | Results, receipts, attempt status, cancellation, supported retries, and reconciliation of interrupted/unknown effects. |
|
|
21
45
|
|
|
22
|
-
|
|
46
|
+
Route comparison and execution preview run locally without model calls. Confirmation starts the selected provider or supported tool. **Model routing is read-only**; editing requires **Team → Native-tool collaboration → Edit files**. Tool installation/login and readiness for a specific managed execution mode are separate checks. Vendor-native processes run as the current system user; a worktree organizes changes rather than restricting system access.
|
|
23
47
|
|
|
24
|
-
|
|
48
|
+
Draft task, mode, exact model, and explicit estimate survive page remounts in the **current window's memory**. Closing the app or reloading the client clears the draft. Close **Needs your attention** with **×** and restore it from the header; the device remembers this visibility preference. Failed-run reminders open the relevant record.
|
|
25
49
|
|
|
26
|
-
|
|
50
|
+
API money and reservations are separate from subscription calls/reported tokens. Missing usage remains unknown; provider bills remain authoritative. Switching a subscription attempt to API requires fresh admission and the relevant confirmation. Review remote effects before retrying an interrupted task.
|
|
27
51
|
|
|
28
52
|
## Switch editions
|
|
29
53
|
|
|
30
|
-
|
|
54
|
+
Remove the current package entry in plugin management, then add the full Bot package in the same Harness profile. Retain your data directories and configured Bot home. Both editions share the routing configuration; enable one at a time.
|
|
31
55
|
|
|
32
|
-
## License and
|
|
56
|
+
## License and support
|
|
33
57
|
|
|
34
|
-
[MIT](LICENSE)
|
|
58
|
+
[MIT license](https://github.com/Alice-Marx/dsh-model-router/blob/main/LICENSE) · [Full guide](https://github.com/Alice-Marx/dsh-model-router/blob/main/README.md) · [Issues](https://github.com/Alice-Marx/dsh-model-router/issues) · [Installation files](https://github.com/Alice-Marx/dsh-model-router/releases/latest)
|
|
@@ -1,34 +1,58 @@
|
|
|
1
1
|
# DSH 模型路由
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](README.md) · 简体中文
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@ljwei-stak/dsh-model-router)
|
|
6
6
|
|
|
7
|
-
DeepSeek Harness
|
|
7
|
+
**在 DeepSeek Harness 原有界面中,按任务选择模型并组织执行。**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
写清期望结果,选择合适的已配置模型或原生 CLI 工具,审查计划后启动,并在同一工作台跟踪执行和费用。此版本保留现有 Harness 工作区,在侧栏增加 **模型路由** 入口。
|
|
10
|
+
|
|
11
|
+
**当前正式版:0.65.0。** 如需 Bot 对话、多 Bot 分工、远程电脑、私有资料库和例行任务,请使用 [完整 Bot 版本](https://github.com/Alice-Marx/dsh-model-router/blob/main/README.zh.md):**`@ljwei-stak/dsh-model-route-bot`**。同一 profile 选择一种版本。
|
|
10
12
|
|
|
11
13
|
## 安装
|
|
12
14
|
|
|
13
|
-
在 Harness
|
|
15
|
+
1. 在 Harness **插件 → 添加插件** 输入 `@ljwei-stak/dsh-model-router@0.65.0`。
|
|
16
|
+
2. 启用后,连同托盘进程完全退出 Harness,再重新启动。
|
|
17
|
+
3. 在 Harness **模型** 中配置 Provider 凭据和模型,从侧栏打开 **模型路由**。
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
dsh plugin --profile desktop add @ljwei-stak/dsh-model-router@0.65.0
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 完成第一个任务
|
|
24
|
+
|
|
25
|
+
1. 在 **模型配置** 刷新准确路线,填写已知价格和能力,未知信息留空。
|
|
26
|
+
2. 在 **任务与执行** 选择 **项目检查**、**整理说明** 或 **方案比较** 模板,修改任务与验收标准。**撤销模板** 可恢复原文。
|
|
27
|
+
3. 点击 **下一步:预览执行 → 预览执行计划**,检查模型/工具、工作区、权限和 API 费用或订阅用量估算。
|
|
28
|
+
4. 确认所需项目后启动执行,在 **执行记录** 检查结果、收据、取消状态及受支持的重试。
|
|
29
|
+
|
|
30
|
+
当前应用界面使用中文,英文文档提供对应操作解释。
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
*截图中的模型、价格、用量和路径均为示例,不是实时模型执行结果。*
|
|
14
35
|
|
|
15
|
-
##
|
|
36
|
+
## 工作台功能
|
|
16
37
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
38
|
+
| 页面 | 可用能力 |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| 任务与执行 | 单路线、带依赖团队、指定模型;可选路线比较、工作包手动分配、预览与确认执行。 |
|
|
41
|
+
| 模型配置 | 准确路线档案、价格、质量证据、计费偏好与 CLI 模型映射。 |
|
|
42
|
+
| 官方工具 | 工具检测、受支持的安装方式、登录/就绪检查与 CLI 交互终端。 |
|
|
43
|
+
| 预算与安全 | 单次估算、API 日/月限额及预占、独立订阅调用/token、复核策略和显式反馈。 |
|
|
44
|
+
| 执行记录 | 结果、收据、执行尝试、取消、受支持的重试及中断/未知影响核对。 |
|
|
21
45
|
|
|
22
|
-
|
|
46
|
+
路线比较和执行预览在本地进行,不调用模型;确认才会启动所选供应商或受支持的工具。**模型路由为只读执行**;修改文件需选择 **团队分工 → 官方工具协作 → 编辑文件**。工具安装/登录与具体托管执行模式就绪分别检查。厂商原生进程使用当前系统用户权限;工作树用于整理改动,不限制系统访问。
|
|
23
47
|
|
|
24
|
-
|
|
48
|
+
页面重新挂载时,任务、模式、准确模型及显式估算保留在**当前窗口内存**,关闭应用或重载客户端后清除。**需要你处理** 面板可用 **×** 关闭,由顶部入口恢复,当前设备会记住显示偏好;失败提醒会打开对应记录。
|
|
25
49
|
|
|
26
|
-
|
|
50
|
+
API 金额/预占与订阅调用/已报告 token 分别核算;缺失用量保持未知,账单以供应商为准。订阅转 API 需要重新准入及相应确认;中断后先核对远程实际影响,再决定重试。
|
|
27
51
|
|
|
28
|
-
##
|
|
52
|
+
## 切换版本
|
|
29
53
|
|
|
30
|
-
|
|
54
|
+
在插件管理中移除当前包入口,再在同一 Harness profile 添加完整 Bot 包,保留数据目录并沿用已配置的 Bot home。两种版本共用路由配置,同一时间启用一种。
|
|
31
55
|
|
|
32
|
-
##
|
|
56
|
+
## 许可与支持
|
|
33
57
|
|
|
34
|
-
[MIT](LICENSE)
|
|
58
|
+
[MIT 许可证](https://github.com/Alice-Marx/dsh-model-router/blob/main/LICENSE) · [完整指南](https://github.com/Alice-Marx/dsh-model-router/blob/main/README.zh.md) · [问题反馈](https://github.com/Alice-Marx/dsh-model-router/issues) · [安装文件](https://github.com/Alice-Marx/dsh-model-router/releases/latest)
|