@yangdcm/dsh-expert-team 1.3.15 → 1.3.17
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 +113 -0
- package/README.en.md +32 -27
- package/README.md +32 -27
- package/client.js +133 -22
- package/lib/artifact-redirect-watch.js +118 -0
- package/lib/command.js +316 -37
- package/lib/effort-preflight.js +135 -0
- package/package.json +2 -2
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话模型的 **reasoningEffort 预检**(2026-09-15,**只告警,绝不阻断**)。
|
|
3
|
+
*
|
|
4
|
+
* ── 为什么需要它(一次真实故障的定性)────────────────────────────────────────
|
|
5
|
+
* 用户报 `model "deepseek-flash" does not support reasoning effort "low"`。定性结论:
|
|
6
|
+
* · **不是本插件的错,也不是宿主缺 low**;
|
|
7
|
+
* · 是用户 `~/.dsh/settings.yaml` 里会话默认路由(命名空间 `agent-default-model`)的
|
|
8
|
+
* `deepseek-flash` 条目**漏写 `reasoningEfforts`** ⇒ 宿主能力表里该模型**只剩 `off`**
|
|
9
|
+
* ⇒ **任何显式 effort 都被拒**(`dsh-llm` 的 `resolveCallWithInfo`:
|
|
10
|
+
* `reasoning === undefined` 时,只要传了 `reasoningEffort` 就抛 `UNSUPPORTED_REASONING_EFFORT`)。
|
|
11
|
+
* · 而本 preset 里 **4 个角色声明 `low`、8 个声明 `high`** ⇒ 在这个路由下 **12 个角色全会失败**。
|
|
12
|
+
* "只有 low 报错"是假象:**先派谁先报谁**。
|
|
13
|
+
* · 宿主在**任何网络 I/O 之前**就抛(`dsh-tool-subagent` 的 preflight → `llm.resolveCallConfig`)。
|
|
14
|
+
*
|
|
15
|
+
* ⇒ 插件**无法**在派工前改变宿主行为(派工由宿主 `tool-subagent` + LLM 运行时执行),
|
|
16
|
+
* 能做的是**提前告警**:在会话路由能力表与 preset 声明对不上时,打**一行**可操作的提示。
|
|
17
|
+
* 这也是本仓唯一诚实的做法 —— 静默失败最贵。
|
|
18
|
+
*
|
|
19
|
+
* ── 纪律 ─────────────────────────────────────────────────────────────────
|
|
20
|
+
* · **只告警,不阻断、不改 preset 的 effort 分档**(那是设计意图);
|
|
21
|
+
* · 任何异常/读不到 ⇒ **静默跳过**(fail-open),绝不让插件加载失败、绝不刷屏;
|
|
22
|
+
* · 一次加载**最多一行**(进程内去重),服务晚挂则**就绪后重探**(同 `scheduleOptionalPluginCheck`)。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** 宿主合法 effort 值域(`dsh-llm` 的 `LlmReasoningEffortId`)。 */
|
|
26
|
+
export const EFFORT_DOMAIN = Object.freeze(['off', 'low', 'high', 'max']);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 从 preset 源文件里提取**声明了哪些 effort、各有几个角色**(纯函数,按行正则,不引 YAML 解析器)。
|
|
30
|
+
* 只认 `reasoningEffort: <值>` 这种行;顺序按出现次数降序、值去重。
|
|
31
|
+
* @returns {{efforts:string[], counts:Record<string,number>}}
|
|
32
|
+
*/
|
|
33
|
+
export function declaredEffortsFromPresetSource(src) {
|
|
34
|
+
const counts = Object.create(null);
|
|
35
|
+
const re = /^\s*(?:-\s*)?reasoningEffort:\s*['"]?([A-Za-z]+)['"]?\s*$/gm;
|
|
36
|
+
let m;
|
|
37
|
+
while ((m = re.exec(String(src || ''))) !== null) {
|
|
38
|
+
const v = String(m[1]).toLowerCase();
|
|
39
|
+
if (!v) continue;
|
|
40
|
+
counts[v] = (counts[v] || 0) + 1;
|
|
41
|
+
}
|
|
42
|
+
const efforts = Object.keys(counts).sort((a, b) => counts[b] - counts[a]);
|
|
43
|
+
return { efforts, counts };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 判定"当前会话模型能不能承接 preset 声明的 effort"(纯函数,便于单测)。
|
|
48
|
+
* @param {object} input
|
|
49
|
+
* @param {{provider:string,model:string}|null} input.selection - `agentDefaultModel.currentSelection()`
|
|
50
|
+
* @param {string[]} input.required - preset 声明的 effort 值
|
|
51
|
+
* @param {object|null} input.info - `llm.resolveModelInfo(provider, model)` 的结果
|
|
52
|
+
* @param {Error|null} [input.infoError] - 读模型信息时抛的错(有值 ⇒ 我们不假装知道)
|
|
53
|
+
* @returns {{status:string, warn:boolean, message:string, available:string[], missing:string[]}}
|
|
54
|
+
*/
|
|
55
|
+
export function effortPreflightPlan({ selection, required, info, infoError } = {}) {
|
|
56
|
+
const req = Array.isArray(required) ? required.filter(Boolean) : [];
|
|
57
|
+
const sel = selection && selection.provider && selection.model ? selection : null;
|
|
58
|
+
if (!sel) return { status: 'no-route', warn: false, message: '', available: [], missing: req };
|
|
59
|
+
if (!req.length) return { status: 'no-preset-efforts', warn: false, message: '', available: [], missing: [] };
|
|
60
|
+
const where = `${sel.provider}/${sel.model}`;
|
|
61
|
+
if (infoError || !info) {
|
|
62
|
+
// 读不到能力表 ⇒ **不假装知道**(也不打扰):模型可能还没注册/适配器未就绪。
|
|
63
|
+
return { status: 'unknown', warn: false, message: '', available: [], missing: [] };
|
|
64
|
+
}
|
|
65
|
+
const reasoning = info.reasoning;
|
|
66
|
+
const available = Array.isArray(reasoning && reasoning.efforts) ? reasoning.efforts.map((e) => String(e && e.id)).filter(Boolean) : [];
|
|
67
|
+
const set = new Set(available);
|
|
68
|
+
const missing = req.filter((r) => !set.has(String(r).toLowerCase()));
|
|
69
|
+
if (!reasoning) {
|
|
70
|
+
// 宿主语义:reasoning === undefined ⇒ **任何** reasoningEffort 都会被拒。
|
|
71
|
+
return {
|
|
72
|
+
status: 'no-reasoning-support',
|
|
73
|
+
warn: true,
|
|
74
|
+
available,
|
|
75
|
+
missing: req,
|
|
76
|
+
message: `[expert-team] 当前会话模型 ${where} 未声明任何 reasoningEfforts(宿主视为"不支持 reasoning effort")⇒ preset 里带 effort 的角色派工会被拒(UNSUPPORTED_REASONING_EFFORT)。修法:在 ~/.dsh/settings.yaml 的 agent-default-model 条目给该模型补 reasoningEfforts(${EFFORT_DOMAIN.join('/')}),或把会话切到官方路由。`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (missing.length) {
|
|
80
|
+
return {
|
|
81
|
+
status: 'missing-efforts',
|
|
82
|
+
warn: true,
|
|
83
|
+
available,
|
|
84
|
+
missing,
|
|
85
|
+
message: `[expert-team] 当前会话模型 ${where} 只声明了 reasoningEfforts=[${available.join(',') || '(空)'}],而 preset 里声明的 [${missing.join(',')}] 不在其中 ⇒ 这些角色的派工会被宿主拒绝(UNSUPPORTED_REASONING_EFFORT;宿主在任何网络 I/O 之前就拒)。修法:在 ~/.dsh/settings.yaml 的 agent-default-model 条目补 reasoningEfforts(${EFFORT_DOMAIN.join('/')}),或把会话切到官方路由。`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { status: 'ok', warn: false, message: '', available, missing: [] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 默认的有界重探节奏(服务晚挂时用;与 `OPTIONAL_PROBE_DELAYS_MS` 同一套纪律)。 */
|
|
92
|
+
export const EFFORT_PROBE_DELAYS_MS = Object.freeze([250, 1000, 3000]);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 接线:读 preset 声明 → 读当前路由与模型能力 → 下单(不阻断)→ 结论在**最后一轮**才播报。
|
|
96
|
+
* deps 全部注入,便于用假 ctx 单测。
|
|
97
|
+
* @param {object} deps
|
|
98
|
+
* @param {() => Promise<string>} [deps.readPresetSource] - 读 preset 源文本(读不到 ⇒ 静默跳过)
|
|
99
|
+
* @param {(ctx:object)=>{provider:string,model:string}|null} [deps.readSelection]
|
|
100
|
+
* @param {(provider:string, model:string)=>Promise<object>} [deps.readModelInfo]
|
|
101
|
+
* @param {(line:string)=>void} [deps.warn] - 播报出口(默认 console.warn)
|
|
102
|
+
* @param {(type:string,payload:object)=>void} [deps.onEvent]
|
|
103
|
+
*/
|
|
104
|
+
export function createEffortPreflight(deps = {}) {
|
|
105
|
+
const { readPresetSource, readSelection, readModelInfo, onEvent } = deps;
|
|
106
|
+
const say = typeof deps.warn === 'function' ? deps.warn : (line) => console.warn(line);
|
|
107
|
+
const emit = typeof onEvent === 'function' ? onEvent : () => {};
|
|
108
|
+
let done = false;
|
|
109
|
+
return async function runEffortPreflight(ctx, { isLast = true } = {}) {
|
|
110
|
+
if (done) return { status: 'already-done', warn: false };
|
|
111
|
+
try {
|
|
112
|
+
const src = await readPresetSource();
|
|
113
|
+
const { efforts: required, counts } = declaredEffortsFromPresetSource(src);
|
|
114
|
+
const selection = readSelection(ctx);
|
|
115
|
+
if (!selection) { if (isLast) done = true; return { status: 'no-route', warn: false }; }
|
|
116
|
+
let info = null, infoError = null;
|
|
117
|
+
try { info = await readModelInfo(selection.provider, selection.model); } catch (e) { infoError = e; }
|
|
118
|
+
const plan = effortPreflightPlan({ selection, required, info, infoError });
|
|
119
|
+
if (isLast) done = true;
|
|
120
|
+
if (plan.warn) {
|
|
121
|
+
const detail = Object.keys(counts).length ? `(preset 里 ${Object.entries(counts).map(([k, v]) => `${v} 个用 ${k}`).join('、')})` : '';
|
|
122
|
+
try { say(detail ? plan.message.replace('。修法:', `${detail}。修法:`) : plan.message); } catch { /* 打印失败也不能影响加载 */ }
|
|
123
|
+
emit('effort-preflight-warn', { status: plan.status, provider: selection.provider, model: selection.model, available: plan.available, missing: plan.missing });
|
|
124
|
+
}
|
|
125
|
+
return plan;
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// 探测自身出错 ⇒ 静默(fail-open),但留一条事件供排查
|
|
128
|
+
try { emit('effort-preflight-error', { error: String((e && e.message) || e) }); } catch { /* ignore */ }
|
|
129
|
+
if (isLast) done = true;
|
|
130
|
+
return { status: 'error', warn: false };
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** 仅供测试:复位"只播报一次"的开关(由 command.js 包一层,见 `_resetEffortPreflight`)。 */
|
|
135
|
+
export function _resetEffortPreflightState() { /* 开关在 createEffortPreflight 的闭包里,测试用新实例即可 */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yangdcm/dsh-expert-team",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.17",
|
|
4
4
|
"description": "dsh「专家团」bundle:一句自然语言自动组建/持久化一支 12 角色多智能体团队,共享工作区协议 + 阶段门控编排 + 结构化交接 + 质量门禁/自动调度,实现者直接改代码并产出持久工件;带 live 团队浮层(质量门禁/覆盖率/工件预览)。 · Role-based multi-agent expert team for DeepSeek Harness: one sentence in, a staged and gated team delivery out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"check:name": "node scripts/rename-package.mjs --check",
|
|
89
89
|
"test:regression": "node regression.test.mjs",
|
|
90
90
|
"test:e2e": "node e2e.test.mjs",
|
|
91
|
-
"test:all": "node bootstrap.test.mjs && node host-settings.test.mjs && node smoke.test.mjs && node regression.test.mjs && node e2e.test.mjs && node flow.test.mjs && node plan-decide.test.mjs && node plan-discard.test.mjs && node models-honesty.test.mjs && node evidence-gate.test.mjs && node sync-gate.test.mjs && node artifact-writer.test.mjs && node dag.test.mjs && node metrics.test.mjs && node broken-chain.test.mjs && node schema-warn.test.mjs && node template-copy.test.mjs && node task-binding.test.mjs && node role-merge.test.mjs && node wf-recovery.test.mjs && node panorama.test.mjs && node run-ownership.test.mjs && node role-identity.test.mjs && node command-parse.test.mjs && node member-registry.test.mjs && node schema-check.test.mjs && node run-health.test.mjs && node cordis-fs-port.test.mjs && node scaffold-fingerprint.test.mjs && node preset-lint.test.mjs && node stranded-tasks.test.mjs && node agent-scope-guard.test.mjs && node schema-warn-noise.test.mjs && node tool-card-status.test.mjs && node dag-status.test.mjs && node capacity-limits.test.mjs && node task-cas.test.mjs && node write-lock.test.mjs && node card-stall.test.mjs && node multi-session-and-confirm.test.mjs && node standing-rules.test.mjs && node scope-overlap.test.mjs && node task-cancel-protect.test.mjs && node wait-run.test.mjs && node rework-loop.test.mjs && node client-css-integrity.test.mjs && node interception.test.mjs && node mutation-catalog.test.mjs && node write-bypass-ratchet.test.mjs && node asi-hazards.test.mjs && node dispatch-contract.test.mjs && node closure-ledger.test.mjs && node rework-nature.test.mjs && node loop-guard.test.mjs && node vocab-consistency.test.mjs && node state-no-write.test.mjs && node routes-shared.test.mjs && node metrics-render.test.mjs && node metrics-collect.test.mjs && node first-runnable.test.mjs && node closing-budget.test.mjs && node scan-single-source.test.mjs && node tier.test.mjs && node dispatch-ledger.test.mjs && node tier-gate.test.mjs && node tier-badge.test.mjs && node validate-module.test.mjs && node settings.test.mjs && node settings-page.test.mjs && node policy.test.mjs && node phase-accounting.test.mjs && node log-parse-module.test.mjs && node command-parse-module.test.mjs && node authority.test.mjs && node write-tracer.test.mjs && node token-accounting.test.mjs && node lead-toolface.test.mjs && node settings-consumers.test.mjs && node settings-wiring.test.mjs && node state-perf-guard.test.mjs && node state-sections.test.mjs && node artifact-ownership.test.mjs && node subagent-bar.test.mjs && node r1-ownership-gate.test.mjs",
|
|
91
|
+
"test:all": "node bootstrap.test.mjs && node host-settings.test.mjs && node smoke.test.mjs && node regression.test.mjs && node e2e.test.mjs && node flow.test.mjs && node plan-decide.test.mjs && node plan-discard.test.mjs && node models-honesty.test.mjs && node evidence-gate.test.mjs && node sync-gate.test.mjs && node artifact-writer.test.mjs && node dag.test.mjs && node metrics.test.mjs && node broken-chain.test.mjs && node schema-warn.test.mjs && node template-copy.test.mjs && node task-binding.test.mjs && node role-merge.test.mjs && node wf-recovery.test.mjs && node panorama.test.mjs && node run-ownership.test.mjs && node role-identity.test.mjs && node command-parse.test.mjs && node member-registry.test.mjs && node schema-check.test.mjs && node run-health.test.mjs && node cordis-fs-port.test.mjs && node scaffold-fingerprint.test.mjs && node preset-lint.test.mjs && node stranded-tasks.test.mjs && node agent-scope-guard.test.mjs && node schema-warn-noise.test.mjs && node tool-card-status.test.mjs && node dag-status.test.mjs && node capacity-limits.test.mjs && node task-cas.test.mjs && node write-lock.test.mjs && node card-stall.test.mjs && node multi-session-and-confirm.test.mjs && node standing-rules.test.mjs && node scope-overlap.test.mjs && node task-cancel-protect.test.mjs && node wait-run.test.mjs && node rework-loop.test.mjs && node client-css-integrity.test.mjs && node interception.test.mjs && node mutation-catalog.test.mjs && node write-bypass-ratchet.test.mjs && node asi-hazards.test.mjs && node dispatch-contract.test.mjs && node closure-ledger.test.mjs && node rework-nature.test.mjs && node loop-guard.test.mjs && node vocab-consistency.test.mjs && node state-no-write.test.mjs && node routes-shared.test.mjs && node metrics-render.test.mjs && node metrics-collect.test.mjs && node first-runnable.test.mjs && node closing-budget.test.mjs && node scan-single-source.test.mjs && node tier.test.mjs && node dispatch-ledger.test.mjs && node tier-gate.test.mjs && node tier-badge.test.mjs && node validate-module.test.mjs && node settings.test.mjs && node settings-page.test.mjs && node policy.test.mjs && node phase-accounting.test.mjs && node log-parse-module.test.mjs && node command-parse-module.test.mjs && node authority.test.mjs && node write-tracer.test.mjs && node token-accounting.test.mjs && node lead-toolface.test.mjs && node settings-consumers.test.mjs && node settings-wiring.test.mjs && node state-perf-guard.test.mjs && node state-bounded.test.mjs && node state-sections.test.mjs && node artifact-ownership.test.mjs && node subagent-bar.test.mjs && node r1-ownership-gate.test.mjs && node artifact-redirect-watch.test.mjs && node effort-preflight.test.mjs",
|
|
92
92
|
"test:toolcard": "node tool-card-status.test.mjs",
|
|
93
93
|
"test:flow": "node flow.test.mjs",
|
|
94
94
|
"test:decide": "node plan-decide.test.mjs",
|