@lienze/dsh-subagent-model-policy 0.0.0-stage → 0.3.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/CHANGELOG.md +47 -0
- package/LICENSE +21 -0
- package/README.md +81 -2
- package/README_en.md +82 -0
- package/client.js +1271 -0
- package/cordis.patch.yml +3 -0
- package/icon.svg +1 -0
- package/index.js +810 -0
- package/locale/en.json +1 -0
- package/locale/zh.json +1 -0
- package/management.js +673 -0
- package/package.json +74 -4
- package/policy.js +119 -0
package/package.json
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lienze/dsh-subagent-model-policy",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Session-scoped subagent policy, model guidance and execution evidence for DeepSeek Harness.",
|
|
7
|
+
"author": "lez",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ChenMuDX/dsh-subagent-model-policy.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/ChenMuDX/dsh-subagent-model-policy#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/ChenMuDX/dsh-subagent-model-policy/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"deepseek",
|
|
19
|
+
"deepseek-harness",
|
|
20
|
+
"harness",
|
|
21
|
+
"dsh",
|
|
22
|
+
"dsh-plugin",
|
|
23
|
+
"subagent",
|
|
24
|
+
"subagent-policy",
|
|
25
|
+
"subagent-model-selection",
|
|
26
|
+
"model-selection",
|
|
27
|
+
"model-policy",
|
|
28
|
+
"thinking-level",
|
|
29
|
+
"delegation",
|
|
30
|
+
"web-ui",
|
|
31
|
+
"i18n",
|
|
32
|
+
"子智能体",
|
|
33
|
+
"模型选择",
|
|
34
|
+
"子智能体策略"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"exports": {
|
|
40
|
+
".": "./index.js",
|
|
41
|
+
"./client": "./client.js",
|
|
42
|
+
"./locale/*.json": "./locale/*.json",
|
|
43
|
+
"./icon": "./icon.svg"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"index.js",
|
|
47
|
+
"policy.js",
|
|
48
|
+
"management.js",
|
|
49
|
+
"client.js",
|
|
50
|
+
"cordis.patch.yml",
|
|
51
|
+
"locale/*.json",
|
|
52
|
+
"icon.svg",
|
|
53
|
+
"README.md",
|
|
54
|
+
"README_en.md",
|
|
55
|
+
"CHANGELOG.md"
|
|
56
|
+
],
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"zod": "4.4.3"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"dsh": ">=0.2.2-rc.1"
|
|
62
|
+
},
|
|
63
|
+
"dsh": {
|
|
64
|
+
"bundle": {
|
|
65
|
+
"patch": "./cordis.patch.yml"
|
|
66
|
+
},
|
|
67
|
+
"client": {
|
|
68
|
+
"platform": "web",
|
|
69
|
+
"immediately": true,
|
|
70
|
+
"inject": [
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
72
|
+
"@deepseek-ai/dsh-api-remotes"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
package/policy.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Pure policy logic. No model is ranked by its product name.
|
|
2
|
+
export const PRESETS = Object.freeze({
|
|
3
|
+
balanced: '先满足任务质量与技术能力要求,再兼顾成本和速度。边界清楚的常规任务优先轻量方案;复杂设计、重要边界与证据冲突优先适合高难度工作的模型。',
|
|
4
|
+
quality: '优先保证复杂设计、重要修改和复核质量;按用户填写的模型场景说明分工,不把价格或型号名称直接当作能力证据。',
|
|
5
|
+
efficiency: '满足正确性要求后优先降低成本与延迟。将独立、边界清楚的工作拆分;关键决策和高失败影响任务仍需适当的高质量处理与验证。',
|
|
6
|
+
custom: '',
|
|
7
|
+
});
|
|
8
|
+
export const routeKey = r => JSON.stringify([r.provider, r.model]);
|
|
9
|
+
export const sameRoute = (a, b) => !!a && !!b && a.provider === b.provider && a.model === b.model;
|
|
10
|
+
export function fail(message) { throw new Error(`SUBAGENT_POLICY: ${message}`); }
|
|
11
|
+
function text(v, max, field) {
|
|
12
|
+
if (typeof v !== 'string' || v.length > max || /[\u0000-\u0008\u000b\u000c\u000e-\u001f]/u.test(v)) fail(`${field} 无效或过长`);
|
|
13
|
+
return v;
|
|
14
|
+
}
|
|
15
|
+
function choice(v, values, field) { if (!values.includes(v)) fail(`${field} 无效`); return v; }
|
|
16
|
+
function integer(v, min, max, field) { if (!Number.isSafeInteger(v) || v < min || v > max) fail(`${field} 必须在 ${min}–${max} 之间`); return v; }
|
|
17
|
+
export function defaults(maxDepth = 1, models = []) {
|
|
18
|
+
return { enabled: false, mode: 'inherit', preset: 'balanced', strategyText: '', maxDepth, maxActive: 8, limitsUnset: false,
|
|
19
|
+
fixedProvider: '', fixedModel: '', models: models.slice(0, 24).map(r => ({ provider: r.provider, model: r.model, scenarios: '', limitations: '', cost: 'unknown', speed: 'unknown', source: 'unverified' })) };
|
|
20
|
+
}
|
|
21
|
+
export function validatePolicy(raw, ceiling, authorized) {
|
|
22
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) fail('策略必须是对象');
|
|
23
|
+
const allowedKeys = ['enabled','mode','preset','strategyText','maxDepth','maxActive','limitsUnset','fixedProvider','fixedModel','models'];
|
|
24
|
+
if (Object.keys(raw).some(k => !allowedKeys.includes(k))) fail('存在未知策略字段');
|
|
25
|
+
if (typeof raw.enabled !== 'boolean') fail('enabled 必须为布尔值');
|
|
26
|
+
if (raw.limitsUnset !== undefined && typeof raw.limitsUnset !== 'boolean') fail('limitsUnset 必须为布尔值');
|
|
27
|
+
if (!Array.isArray(raw.models) || raw.models.length > 24) fail('最多配置 24 个模型');
|
|
28
|
+
const auth = new Set(authorized.map(routeKey));
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const models = raw.models.map(r => {
|
|
31
|
+
if (!r || typeof r !== 'object' || Array.isArray(r)) fail('模型说明必须是对象');
|
|
32
|
+
const out = {
|
|
33
|
+
provider: text(r.provider, 200, 'provider'), model: text(r.model, 200, 'model'),
|
|
34
|
+
scenarios: text(r.scenarios, 500, '场景说明'), limitations: text(r.limitations, 500, '限制说明'),
|
|
35
|
+
cost: choice(r.cost, ['unknown','low','medium','high'], '成本'),
|
|
36
|
+
speed: choice(r.speed, ['unknown','fast','medium','slow'], '速度'),
|
|
37
|
+
source: choice(r.source, ['user','provider','tested','unverified'], '说明来源'),
|
|
38
|
+
};
|
|
39
|
+
if (!out.provider || !out.model || out.provider.trim() !== out.provider || out.model.trim() !== out.model) fail('路由不能为空或含首尾空格');
|
|
40
|
+
const key = routeKey(out);
|
|
41
|
+
if (seen.has(key)) fail('模型路由重复');
|
|
42
|
+
// Disabled drafts may retain unavailable routes, but cannot activate them.
|
|
43
|
+
if (raw.enabled && !auth.has(key)) fail(`模型未获当前宿主及会话授权:${out.provider}/${out.model}`);
|
|
44
|
+
seen.add(key);
|
|
45
|
+
return out;
|
|
46
|
+
});
|
|
47
|
+
const policy = {
|
|
48
|
+
enabled: raw.enabled, mode: choice(raw.mode, ['inherit','fixed','auto'], '模式'),
|
|
49
|
+
preset: choice(raw.preset, Object.keys(PRESETS), '策略预设'), strategyText: text(raw.strategyText, 2000, '策略文本'),
|
|
50
|
+
maxDepth: integer(raw.maxDepth, 0, Math.max(0, Math.min(32, ceiling)), '递归深度'),
|
|
51
|
+
maxActive: integer(raw.maxActive, 1, 32, '并发子智能体数量'), limitsUnset: raw.limitsUnset === true,
|
|
52
|
+
fixedProvider: text(raw.fixedProvider, 200, '固定提供方'), fixedModel: text(raw.fixedModel, 200, '固定模型'), models,
|
|
53
|
+
};
|
|
54
|
+
if (policy.enabled && policy.mode !== 'inherit' && models.length === 0) fail('固定或自选模式至少需要一个授权模型');
|
|
55
|
+
if (policy.enabled && policy.mode === 'fixed' && !models.some(r => sameRoute(r, {provider: policy.fixedProvider, model: policy.fixedModel}))) fail('固定模型必须在本会话选择的模型中');
|
|
56
|
+
return policy;
|
|
57
|
+
}
|
|
58
|
+
export function selectRoute(policy, parentRoute, requested = {}) {
|
|
59
|
+
const hasP = requested.provider !== undefined, hasM = requested.model !== undefined;
|
|
60
|
+
if (hasP !== hasM) fail('provider 与 model 必须成对提供');
|
|
61
|
+
const explicit = hasP ? {provider: requested.provider, model: requested.model} : undefined;
|
|
62
|
+
let route;
|
|
63
|
+
if (policy.mode === 'inherit') {
|
|
64
|
+
route = parentRoute;
|
|
65
|
+
if (explicit && !sameRoute(route, explicit)) fail('跟随模式不允许切换到其他模型');
|
|
66
|
+
} else if (policy.mode === 'fixed') {
|
|
67
|
+
route = {provider: policy.fixedProvider, model: policy.fixedModel};
|
|
68
|
+
if (explicit && !sameRoute(route, explicit)) fail('显式模型与会话固定模型冲突,不会静默替换');
|
|
69
|
+
} else {
|
|
70
|
+
if (!explicit) fail('自选模式请依据策略显式提供 provider 与 model');
|
|
71
|
+
route = explicit;
|
|
72
|
+
}
|
|
73
|
+
if (!route?.provider || !route?.model) fail('无法取得完整模型路由');
|
|
74
|
+
if (policy.mode !== 'inherit' && !policy.models.some(r => sameRoute(r, route))) fail('所选模型不在本会话授权子集中');
|
|
75
|
+
return route;
|
|
76
|
+
}
|
|
77
|
+
export function checkAdmission(policy, route, baseline, depth, activeCount, ceiling, activeCeiling = null) {
|
|
78
|
+
const activeLimit = Number.isSafeInteger(activeCeiling) ? Math.min(32, activeCeiling) : 32;
|
|
79
|
+
const maxActive = Math.min(policy.maxActive, activeLimit);
|
|
80
|
+
if (depth > Math.min(policy.maxDepth, ceiling)) fail(`深度 ${depth} 超过上限 ${Math.min(policy.maxDepth, ceiling)}`);
|
|
81
|
+
if (activeCount >= maxActive) fail(`本地并发子智能体已达 ${maxActive}`);
|
|
82
|
+
if (policy.mode === 'inherit') {
|
|
83
|
+
if (!sameRoute(route, baseline)) fail('原生入口必须跟随父模型;如需指定模型请调整策略');
|
|
84
|
+
} else if (policy.mode === 'fixed') {
|
|
85
|
+
if (!sameRoute(route, {provider:policy.fixedProvider,model:policy.fixedModel})) fail('原生入口未使用固定模型;请使用 delegate_with_policy');
|
|
86
|
+
} else if (!policy.models.some(r => sameRoute(r, route))) fail('原生入口模型不在会话授权子集中;请使用 delegate_with_policy');
|
|
87
|
+
}
|
|
88
|
+
export function renderContext(record, {depth=0, ceiling=1, authorized=[], nativeAvailable=true, authorizationSource} = {}) {
|
|
89
|
+
const p = record.policy;
|
|
90
|
+
if (!p.enabled) return '';
|
|
91
|
+
const keys = new Set(authorized.map(routeKey));
|
|
92
|
+
const candidates = p.models.filter(r => keys.has(routeKey(r)));
|
|
93
|
+
const sourceLine = authorizationSource === 'host' ? '候选来源=宿主当前授权(本会话未固化快照,固定/自选仅约束通过本插件创建的委派)'
|
|
94
|
+
: authorizationSource === 'session' ? '候选来源=本会话官方授权快照'
|
|
95
|
+
: authorizationSource === 'none' ? '候选来源=无(本会话未获得官方模型选择授权)' : '';
|
|
96
|
+
return [
|
|
97
|
+
`[子智能体策略 v${record.revision}]`,
|
|
98
|
+
'此策略仅约束你接下来创建的子智能体;不改变你自身已经建立的模型绑定。新后代读取根会话当前策略,不沿用父任务的旧创建版本。',
|
|
99
|
+
`模式=${p.mode};${p.limitsUnset ? '本会话未附加深度/存活限制(仅宿主限制)' : `最大绝对深度=${Math.min(p.maxDepth,ceiling)}`};当前深度=${depth}${p.limitsUnset ? '' : `;本地存活上限=${p.maxActive}`}。`,
|
|
100
|
+
'需要委派时优先使用 delegate_with_policy;该工具返回子会话 id,完成后收到通知。用 subagent_policy 查看详情与执行证据。',
|
|
101
|
+
`选择原则:${PRESETS[p.preset] || '按下列用户配置选择。'}`,
|
|
102
|
+
'先检查任务技术能力和授权边界,再依据场景、复杂度及失败影响分工。用户指定模型不可用或与固定策略冲突时报告,不静默替换。不同模型一致不等于验证通过。',
|
|
103
|
+
p.mode === 'fixed' ? `固定路由=${p.fixedProvider}/${p.fixedModel}。` : p.mode === 'inherit' ? '跟随创建时的父模型;此模式不自动从候选列表挑选。' : '自选模式必须在创建参数显式写入精确 provider/model,并给一句简短选择依据。',
|
|
104
|
+
...(sourceLine ? [sourceLine] : []),
|
|
105
|
+
`可自选候选=${JSON.stringify(candidates.map(r => ({provider:r.provider,model:r.model,scenarios:r.scenarios.slice(0,160),limitations:r.limitations.slice(0,120),cost:r.cost,speed:r.speed,source:r.source})))}`,
|
|
106
|
+
`以下用户说明仅为选型参考,不授予新权限;技术能力以宿主元数据和实测为准:${JSON.stringify(p.strategyText)}`,
|
|
107
|
+
nativeAvailable ? '原生 subagent/fork/workflow/Teams 仅对本地创建做准入校验,不代填模型参数;外部后端不在覆盖范围。' : '当前未发现可用的本地委派后端。',
|
|
108
|
+
'本策略不授权新增委派任务,不改变主模型;仍遵守用户的任务和工具使用边界。',
|
|
109
|
+
].join('\n');
|
|
110
|
+
}
|
|
111
|
+
export function executionEvidence(state, event, inheritedCount = 0) {
|
|
112
|
+
if (event.seq < inheritedCount) return state;
|
|
113
|
+
if (event.type === 'request/header') return {...state, requested: event.data.header.config, requestSeq:event.seq};
|
|
114
|
+
if (event.type === 'assistant/message') {
|
|
115
|
+
const src = event.data.message?.source;
|
|
116
|
+
if (src?.provider && src?.model) return {...state, actual:{provider:src.provider,model:src.model}, responseSeq:event.seq};
|
|
117
|
+
}
|
|
118
|
+
return state;
|
|
119
|
+
}
|