dsh-plugin-t-expert 0.3.84 → 0.3.89
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/README.md +29 -56
- package/THIRD-PARTY-NOTICES +6 -31
- package/data/experts/engineering/engineering-deepseek-harness-project-expert.md +5 -5
- package/lib/bootstrap.js +11 -33
- package/lib/catalog.js +1 -1
- package/lib/client.js +20830 -21811
- package/lib/i18n.js +0 -4
- package/lib/index.js +173 -862
- package/lib/remote-schemas.js +5 -104
- package/lib/remote.js +7 -174
- package/lib/schedule.js +99 -26
- package/lib/skill.js +11 -12
- package/package.json +3 -12
- package/vendor/third-party-licenses/README.md +0 -1
- package/data/t-team.config.json +0 -1558
- package/data/team-profiles.py +0 -367
- package/data/teams.json +0 -791
- package/data/teams.resolved.json +0 -1594
- package/lib/command.js +0 -251
- package/lib/plan-check.js +0 -166
- package/lib/squads.js +0 -446
- package/lib/teams/assignee-contract.js +0 -47
- package/lib/teams/capabilities.js +0 -106
- package/lib/teams/command.js +0 -147
- package/lib/teams/event-types.js +0 -12
- package/lib/teams/events.js +0 -60
- package/lib/teams/harness-compat.js +0 -561
- package/lib/teams/index.js +0 -463
- package/lib/teams/members.js +0 -619
- package/lib/teams/profiles.js +0 -572
- package/lib/teams/quality-gates.js +0 -905
- package/lib/teams/scheduler.js +0 -642
- package/lib/teams/snapshot.js +0 -184
- package/lib/teams/state.js +0 -946
- package/lib/teams/tool-names.js +0 -11
- package/lib/teams/tools.js +0 -2346
- package/lib/teams/types.js +0 -22
- package/lib/teams/web-routes.js +0 -86
- package/skills/dsh-harness-languages/SKILL.md +0 -175
- package/skills/dsh-harness-project/SKILL.md +0 -236
- package/vendor/dsh-agent-teams/LICENSE +0 -21
|
@@ -1,561 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { SubagentError } from '@deepseek-ai/dsh-subagent';
|
|
4
|
-
/**
|
|
5
|
-
* Exact protocol exported by dsh-subagent/internal in Alpha.5 and rc.1.
|
|
6
|
-
* That subpath does not exist in Alpha.2, so importing it statically prevents
|
|
7
|
-
* the plugin from loading there. This small adapter uses the same process-
|
|
8
|
-
* stable symbol and call signature as upstream queueHostSubagentPrompt.
|
|
9
|
-
* Source: packages/subagent/subagent/src/internal.ts at dsh-v0.1.2-rc.1.
|
|
10
|
-
*/
|
|
11
|
-
const hostPromptQueue = Symbol.for('dsh.subagent.queuePrompt');
|
|
12
|
-
// 0.1.5 keeps queueHostSubagentPrompt but replaces its symbol with this
|
|
13
|
-
// delivery-mode-aware implementation (packages/subagent/subagent/src/internal.ts).
|
|
14
|
-
const hostPromptDeliver = Symbol.for('dsh.subagent.deliverPrompt');
|
|
15
|
-
/**
|
|
16
|
-
* 全局共识符号("约定俗成的键")。
|
|
17
|
-
*
|
|
18
|
-
* 为什么默认就用它:上游**刻意**用 `Symbol.for`(全局符号注册表)而不是模块内 `Symbol()` ——
|
|
19
|
-
* 同一个实现会同时存在于"打包过的运行时入口"与"未打包的 internal 子路径"两处,只有全局注册表
|
|
20
|
-
* 才能让两边对上暗号(见上游 internal.ts 的注释)。所以我们按同一约定对接,不需要 import
|
|
21
|
-
* 任何东西就能认得那两个键。
|
|
22
|
-
*
|
|
23
|
-
* 但它**不是权威**:符号串改了、或者上游换成非全局符号,这里就找不到了(B-1 会响亮拒绝)。
|
|
24
|
-
* 权威在 `@deepseek-ai/dsh-subagent/internal` 的导出里,由 `resolveHarnessAuthority()` 动态取
|
|
25
|
-
* (见该函数的说明)——这里只是它的**兜底默认值**。
|
|
26
|
-
*/
|
|
27
|
-
const GLOBAL_SYMBOLS = { deliver: hostPromptDeliver, queue: hostPromptQueue };
|
|
28
|
-
/**
|
|
29
|
-
* 当前生效的符号键:默认是上面那两个全局符号,解析出权威符号后换成权威那份。
|
|
30
|
-
* 所有读 `ctx.subagents[键]` 的地方都必须走 `routeKey()` / `activeSymbols`,不许再直接引用
|
|
31
|
-
* 全局符号 —— 否则"换了键"只改了自检、运行期还按旧键找。
|
|
32
|
-
*/
|
|
33
|
-
const activeSymbols = { ...GLOBAL_SYMBOLS };
|
|
34
|
-
/** 路由 → 运行期真正要读的属性键(字符串方法名,或 symbol)。 */
|
|
35
|
-
function routeKey(route) {
|
|
36
|
-
return route.symbol === undefined ? route.key : activeSymbols[route.symbol];
|
|
37
|
-
}
|
|
38
|
-
/** 路由 → 人类可读标签(符号路由现算,换了键时标签跟着变,不会说谎)。 */
|
|
39
|
-
function routeLabel(route) {
|
|
40
|
-
return route.symbol === undefined ? route.label : `subagents[${String(activeSymbols[route.symbol])}]`;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* 宿主投递契约的**期望形状**(B-1/B-2)。
|
|
44
|
-
*
|
|
45
|
-
* 为什么要把"我们反推出来的口径"写成一张表:本文件对接的是**未文档化内部实现** ——
|
|
46
|
-
* 上游改个形参个数、换个转发形态,TypeScript 看不见、tsc 也拦不住,症状只会是
|
|
47
|
-
* "队长叫不醒成员"或"共享服务被按错的参数调用"。表在这里,运行期就能据此做签名断言,
|
|
48
|
-
* 自检也拿它当单一真源(见 tools/verify.mjs 的 [13f] 与 [19] 的端到端那两条)。
|
|
49
|
-
*
|
|
50
|
-
* `arity` 两档,区别是**我们有多确信**:
|
|
51
|
-
* · 数字 = 内部符号函数(`Symbol.for(...)`),形参个数是逐字核对上游源码得到的。上游加必填
|
|
52
|
-
* 参数、调整顺序、改转发形态都会让 `Function.length` 变 —— 那正是要被拦下来的。
|
|
53
|
-
* · null = 公开方法(`followup` / `sendMessage`):名字是公开的,但实现允许少声明形参
|
|
54
|
-
* (短实现、转发实现、自检桩),所以只校验"可调用",不校验个数。
|
|
55
|
-
*
|
|
56
|
-
* `role` 说明这条路能拿来干什么:`queue` 才是"宿主写给某个子代理的一轮"(FIFO 投递),
|
|
57
|
-
* `steer` 是 steer 语义 —— **绝不能拿它顶替投递**(见 `queueMemberPrompt` 的说明)。
|
|
58
|
-
*/
|
|
59
|
-
export const HARNESS_ROUTES = [
|
|
60
|
-
{ key: 'followup', label: 'subagents.followup', arity: null, role: 'queue' },
|
|
61
|
-
{ symbol: 'deliver', label: 'subagents[deliverPrompt symbol]', arity: 6, role: 'queue' },
|
|
62
|
-
{ symbol: 'queue', label: 'subagents[queuePrompt symbol]', arity: 5, role: 'queue' },
|
|
63
|
-
{ key: 'sendMessage', label: 'subagents.sendMessage', arity: null, role: 'steer' },
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* 逐条路由的签名问题(空数组 = 形状与实测一致)。**只读**,不改任何东西、也不抛错。
|
|
68
|
-
* @param runtime - `ctx.subagents` 服务对象。
|
|
69
|
-
* @returns 人类可读的问题清单。
|
|
70
|
-
*/
|
|
71
|
-
function deliverySignatureProblems(runtime) {
|
|
72
|
-
const problems = [];
|
|
73
|
-
for (const route of HARNESS_ROUTES) {
|
|
74
|
-
// 只对内部符号函数断言:公开方法的形参个数不是契约的一部分(见 HARNESS_ROUTES 的说明)。
|
|
75
|
-
if (route.arity === null)
|
|
76
|
-
continue;
|
|
77
|
-
const fn = runtime?.[routeKey(route)];
|
|
78
|
-
if (typeof fn !== 'function')
|
|
79
|
-
continue;
|
|
80
|
-
if (fn.length !== route.arity) {
|
|
81
|
-
problems.push(`${routeLabel(route)} declares ${fn.length} parameters, expected ${route.arity}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return problems;
|
|
85
|
-
}
|
|
86
|
-
function boundary(runtime) {
|
|
87
|
-
return runtime;
|
|
88
|
-
}
|
|
89
|
-
function unsupported(detail) {
|
|
90
|
-
throw new Error(`t-team: unsupported Harness subagent contract (${detail}); use an explicitly tested Harness version and a coherent dependency installation`);
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* 这个兼容层**实测过**的 Harness 版本(A-1/E-1)。
|
|
94
|
-
*
|
|
95
|
-
* 为什么要写出来:`guardSubagentDelivery` 会覆写共享的 `ctx.subagents` 服务,用的两个
|
|
96
|
-
* `Symbol.for` 与调用签名都来自**未文档化的上游内部实现**。上游一旦改动,症状是"团队功能
|
|
97
|
-
* 突然不可用(引擎降级)",没有这张表就只能靠猜。升级 Harness 后**必须**把这里改成
|
|
98
|
-
* "已实测通过"再发布,未知版本会走 `unsupported()` 明确报错(而不是半死)。
|
|
99
|
-
*/
|
|
100
|
-
export const TESTED_HARNESS_VERSIONS = ['0.1.5-rc.1', '0.1.5-rc.2'];
|
|
101
|
-
/** 单个属性能否被安全覆写:可写数据属性、有 setter 的访问器,或对象可扩展时的新属性。 */
|
|
102
|
-
function canAssign(object, key) {
|
|
103
|
-
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
104
|
-
if (descriptor === undefined)
|
|
105
|
-
return Object.isExtensible(object);
|
|
106
|
-
if ('value' in descriptor)
|
|
107
|
-
return descriptor.writable === true;
|
|
108
|
-
return typeof descriptor.set === 'function';
|
|
109
|
-
}
|
|
110
|
-
/** 覆写前先确认**每一个**目标属性都可写,否则响亮失败(避免 strict mode 下抛裸 TypeError)。 */
|
|
111
|
-
function assertAssignable(object, keys) {
|
|
112
|
-
const blocked = keys.filter((key) => !canAssign(object, key));
|
|
113
|
-
if (blocked.length > 0) {
|
|
114
|
-
const names = blocked.map((key) => (typeof key === 'symbol' ? key.toString() : key)).join(', ');
|
|
115
|
-
return unsupported(`subagent service properties are not writable (${names}); upstream may have frozen or accessor-ized the service`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
/** Read child-owned history, excluding any descriptor inherited from a parent. */
|
|
119
|
-
export function sessionOwnEvents(session) {
|
|
120
|
-
const current = session;
|
|
121
|
-
if (typeof current.ownEvents === 'function')
|
|
122
|
-
return current.ownEvents.call(session);
|
|
123
|
-
const legacy = session;
|
|
124
|
-
if (!Array.isArray(legacy.events))
|
|
125
|
-
return unsupported('missing ownEvents/legacy session log');
|
|
126
|
-
return legacy.events.slice(legacy.header.seedLength ?? 0);
|
|
127
|
-
}
|
|
128
|
-
/** Install before the first request, including cold resume, with HMR cleanup. */
|
|
129
|
-
export function installContinuableMemberSetup(ctx, setup) {
|
|
130
|
-
const runtime = boundary(ctx.subagents);
|
|
131
|
-
if (typeof runtime.registerContinuableSetup === 'function') {
|
|
132
|
-
// Upstream owns this registration with this.ctx.effect. Cordis resolves
|
|
133
|
-
// that ctx to the accessing plugin, so its disposal revokes installations
|
|
134
|
-
// even while the subagents service and child Agents remain live.
|
|
135
|
-
runtime.registerContinuableSetup.call(ctx.subagents, (childCtx) => {
|
|
136
|
-
if (childCtx.agent === undefined)
|
|
137
|
-
return unsupported('legacy setup lacks child Agent');
|
|
138
|
-
return setup(childCtx, childCtx.agent);
|
|
139
|
-
});
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
if ((typeof runtime[activeSymbols.queue] !== 'function' && typeof runtime[activeSymbols.deliver] !== 'function') || typeof runtime.sendMessage !== 'function') {
|
|
143
|
-
return unsupported('missing continuable setup and modern host queue');
|
|
144
|
-
}
|
|
145
|
-
const installed = new WeakSet();
|
|
146
|
-
const active = new Set();
|
|
147
|
-
ctx.effect(() => {
|
|
148
|
-
const stop = ctx.on('agent/session-start', ({ agent }) => {
|
|
149
|
-
if (installed.has(agent))
|
|
150
|
-
return;
|
|
151
|
-
// Deliberately synchronous: awaiting here loses the first-request race.
|
|
152
|
-
let teardown;
|
|
153
|
-
try {
|
|
154
|
-
teardown = setup(agent.ctx, agent);
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
// session-start is a notification: Harness logs a thrown listener and
|
|
158
|
-
// still admits the first prompt. Reject request assembly explicitly so
|
|
159
|
-
// a malformed saved route cannot silently execute on a default model.
|
|
160
|
-
const failure = new Error(`t-team: member initialization failed: ${String(error)}`, { cause: error });
|
|
161
|
-
ctx.logger.warn(failure.message);
|
|
162
|
-
teardown = agent.ctx.on('agent/request', () => { throw failure; });
|
|
163
|
-
}
|
|
164
|
-
installed.add(agent);
|
|
165
|
-
let disposed = false;
|
|
166
|
-
const dispose = () => {
|
|
167
|
-
if (disposed)
|
|
168
|
-
return;
|
|
169
|
-
disposed = true;
|
|
170
|
-
active.delete(dispose);
|
|
171
|
-
installed.delete(agent);
|
|
172
|
-
teardown();
|
|
173
|
-
};
|
|
174
|
-
active.add(dispose);
|
|
175
|
-
// Listeners contributed to agent.ctx already follow its lifetime. Also
|
|
176
|
-
// release our bookkeeping and remove them if this plugin is reloaded.
|
|
177
|
-
try {
|
|
178
|
-
agent.ctx.effect(() => dispose, 't-team: child compatibility setup');
|
|
179
|
-
}
|
|
180
|
-
catch (error) {
|
|
181
|
-
dispose();
|
|
182
|
-
throw error;
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
return () => {
|
|
186
|
-
stop();
|
|
187
|
-
for (const dispose of [...active])
|
|
188
|
-
dispose();
|
|
189
|
-
};
|
|
190
|
-
}, 't-team: member lifecycle compatibility');
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Queue a distinct host-authored turn; never substitute model-message steer.
|
|
194
|
-
*
|
|
195
|
-
* B-1:候选路由按 `HARNESS_ROUTES` 的顺序取第一条**形状对得上**的;内部符号函数的形参个数
|
|
196
|
-
* 与实测不符时**直接跳过它**,而不是拿错的参数表去调 ——
|
|
197
|
-
* 前者是"这条投递走不了(响亮)",后者是"按错顺序调共享服务(静默)",后者更糟。
|
|
198
|
-
*/
|
|
199
|
-
export async function queueMemberPrompt(runtime, parent, childId, content, signal, retried = false) {
|
|
200
|
-
const host = boundary(runtime);
|
|
201
|
-
const source = { kind: 'plugin', plugin: 't-team' };
|
|
202
|
-
const skipped = [];
|
|
203
|
-
// ⓪ 官方适配函数优先:`@deepseek-ai/dsh-subagent/internal` 导出的 queueHostSubagentPrompt 就是
|
|
204
|
-
// 「投递给某个子代理一轮」的正式写法(上游内部也是 `runtime[deliverPrompt](…, 'queue')`)。
|
|
205
|
-
// 走它等于把**反推出来的调用形态**换成上游自己维护的那一份:上游改投递模式时,我们跟着受益。
|
|
206
|
-
// 前置条件与它自己一样:运行时上那个符号方法得在(否则它内部会 TypeError)。
|
|
207
|
-
const adapter = authorityState.adapter;
|
|
208
|
-
if (typeof adapter === 'function'
|
|
209
|
-
&& typeof host[activeSymbols.deliver] === 'function'
|
|
210
|
-
&& (authorityState.adapterArity === undefined || authorityState.adapterArity === HARNESS_ADAPTER_ARITY)) {
|
|
211
|
-
return adapter(host, parent, childId, content, source, signal);
|
|
212
|
-
}
|
|
213
|
-
for (const route of HARNESS_ROUTES) {
|
|
214
|
-
if (route.role !== 'queue')
|
|
215
|
-
continue;
|
|
216
|
-
const fn = host[routeKey(route)];
|
|
217
|
-
if (typeof fn !== 'function')
|
|
218
|
-
continue;
|
|
219
|
-
if (route.arity !== null && fn.length !== route.arity) {
|
|
220
|
-
skipped.push(`${routeLabel(route)} declares ${fn.length} parameters, expected ${route.arity}`);
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
// 公开的 `followup` 收 options 包;两个内部符号收扁平参数(deliver 还多一个投递模式)。
|
|
224
|
-
if (route.key === 'followup') {
|
|
225
|
-
return fn.call(runtime, parent, childId, content, { source, signal });
|
|
226
|
-
}
|
|
227
|
-
if (route.symbol === 'deliver') {
|
|
228
|
-
return fn.call(runtime, parent, childId, content, source, signal, 'queue');
|
|
229
|
-
}
|
|
230
|
-
return fn.call(runtime, parent, childId, content, source, signal);
|
|
231
|
-
}
|
|
232
|
-
// 都不可用:如果权威解析还没回来,**等它一次再重试**(投递是异步的,等得起)。
|
|
233
|
-
// 这一等救的是"符号口径变了、而首挂又早于解析完成"的窗口;只有失败路径才会走到这里。
|
|
234
|
-
if (!retried && !authorityState.settled) {
|
|
235
|
-
await resolveHarnessAuthority();
|
|
236
|
-
return queueMemberPrompt(runtime, parent, childId, content, signal, true);
|
|
237
|
-
}
|
|
238
|
-
return unsupported(skipped.length > 0
|
|
239
|
-
? `no usable host FIFO delivery route (${skipped.join('; ')})`
|
|
240
|
-
: 'missing host FIFO delivery');
|
|
241
|
-
}
|
|
242
|
-
/** Guard all resumable delivery paths, preserving the native service receiver. */
|
|
243
|
-
export function guardSubagentDelivery(ctx, isRetired) {
|
|
244
|
-
const runtime = ctx.subagents;
|
|
245
|
-
const host = boundary(runtime);
|
|
246
|
-
const legacy = host.followup;
|
|
247
|
-
const queue = host[activeSymbols.queue];
|
|
248
|
-
const deliver = host[activeSymbols.deliver];
|
|
249
|
-
const send = host.sendMessage;
|
|
250
|
-
if (typeof legacy !== 'function' && ((typeof queue !== 'function' && typeof deliver !== 'function') || typeof send !== 'function')) {
|
|
251
|
-
return unsupported('cannot install complete retired-member guard');
|
|
252
|
-
}
|
|
253
|
-
// B-1:**签名断言**,且刻意放在可写性探测之前 —— 先确认"我们理解这个函数",再谈"能不能改它"。
|
|
254
|
-
//
|
|
255
|
-
// 为什么这里用**硬失败**(降级引擎)而不是像调用路径那样跳过:下面包的是**共享的**
|
|
256
|
-
// `ctx.subagents` 服务,四个入口一个不少地换成我们的包装。只要有一条路由的形参口径不明,
|
|
257
|
-
// 包装层就可能按错的参数顺序转发 —— 受害的不是"我们这一条投递",而是所有走这个服务对象的
|
|
258
|
-
// 投递,而且完全静默。宁可这一刻响一声(引擎降级、原因进日志与提示段),也不装一个签名不明的守卫。
|
|
259
|
-
const signatureProblems = deliverySignatureProblems(host);
|
|
260
|
-
if (signatureProblems.length > 0) {
|
|
261
|
-
return unsupported(`undocumented delivery signature changed (${signatureProblems.join('; ')}); re-verify this adapter against the installed @deepseek-ai/dsh-subagent and extend TESTED_HARNESS_VERSIONS`);
|
|
262
|
-
}
|
|
263
|
-
// A-1/E-1:**先探测可写性再动手**。过去直接赋值,若上游把服务冻结(`Object.freeze`)
|
|
264
|
-
// 或改成只有 getter 的访问器,strict mode 下这里会抛一个与"版本不支持"毫无关系的裸
|
|
265
|
-
// TypeError;现在会得到明确原因。同时这也是一次"契约变了"的早期信号。
|
|
266
|
-
const targets = [
|
|
267
|
-
...(typeof legacy === 'function' ? ['followup'] : []),
|
|
268
|
-
...(typeof queue === 'function' ? [activeSymbols.queue] : []),
|
|
269
|
-
...(typeof deliver === 'function' ? [activeSymbols.deliver] : []),
|
|
270
|
-
...(typeof send === 'function' ? ['sendMessage'] : []),
|
|
271
|
-
];
|
|
272
|
-
assertAssignable(host, targets);
|
|
273
|
-
ctx.effect(() => {
|
|
274
|
-
const descriptors = new Map([
|
|
275
|
-
['followup', Object.getOwnPropertyDescriptor(host, 'followup')],
|
|
276
|
-
[activeSymbols.queue, Object.getOwnPropertyDescriptor(host, activeSymbols.queue)],
|
|
277
|
-
[activeSymbols.deliver, Object.getOwnPropertyDescriptor(host, activeSymbols.deliver)],
|
|
278
|
-
['sendMessage', Object.getOwnPropertyDescriptor(host, 'sendMessage')],
|
|
279
|
-
]);
|
|
280
|
-
let active = true;
|
|
281
|
-
const check = async (sender, targetId) => {
|
|
282
|
-
if (active && await isRetired(sender, targetId)) {
|
|
283
|
-
throw new SubagentError(`T Team member "${targetId}" was retired and cannot be resumed`, 'NOT_RESUMABLE');
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
const guardedLegacy = async (parent, childId, content, options) => {
|
|
287
|
-
await check(parent, childId);
|
|
288
|
-
return legacy.call(runtime, parent, childId, content, options);
|
|
289
|
-
};
|
|
290
|
-
const guardedQueue = async (parent, childId, content, source, signal) => {
|
|
291
|
-
await check(parent, childId);
|
|
292
|
-
return queue.call(runtime, parent, childId, content, source, signal);
|
|
293
|
-
};
|
|
294
|
-
const guardedSend = async (sender, targetId, content, options) => {
|
|
295
|
-
await check(sender, targetId);
|
|
296
|
-
return send.call(runtime, sender, targetId, content, options);
|
|
297
|
-
};
|
|
298
|
-
const guardedDeliver = async (parent, childId, content, source, signal, delivery) => {
|
|
299
|
-
await check(parent, childId);
|
|
300
|
-
return deliver.call(runtime, parent, childId, content, source, signal, delivery);
|
|
301
|
-
};
|
|
302
|
-
if (typeof legacy === 'function')
|
|
303
|
-
host.followup = guardedLegacy;
|
|
304
|
-
if (typeof queue === 'function')
|
|
305
|
-
host[activeSymbols.queue] = guardedQueue;
|
|
306
|
-
if (typeof deliver === 'function')
|
|
307
|
-
host[activeSymbols.deliver] = guardedDeliver;
|
|
308
|
-
if (typeof send === 'function')
|
|
309
|
-
host.sendMessage = guardedSend;
|
|
310
|
-
// Cordis wraps method reads in fresh Proxies. Compare the actual own
|
|
311
|
-
// descriptor to restore only our contribution, including prototype methods.
|
|
312
|
-
const restore = (key, installed) => {
|
|
313
|
-
if (Object.getOwnPropertyDescriptor(host, key)?.value !== installed)
|
|
314
|
-
return;
|
|
315
|
-
const original = descriptors.get(key);
|
|
316
|
-
if (original === undefined)
|
|
317
|
-
Reflect.deleteProperty(host, key);
|
|
318
|
-
else
|
|
319
|
-
Object.defineProperty(host, key, original);
|
|
320
|
-
};
|
|
321
|
-
return () => {
|
|
322
|
-
active = false;
|
|
323
|
-
if (typeof legacy === 'function')
|
|
324
|
-
restore('followup', guardedLegacy);
|
|
325
|
-
if (typeof queue === 'function')
|
|
326
|
-
restore(activeSymbols.queue, guardedQueue);
|
|
327
|
-
if (typeof deliver === 'function')
|
|
328
|
-
restore(activeSymbols.deliver, guardedDeliver);
|
|
329
|
-
if (typeof send === 'function')
|
|
330
|
-
restore('sendMessage', guardedSend);
|
|
331
|
-
};
|
|
332
|
-
}, 't-team: retired member guard');
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// ---------------------------------------------------------------- B-3:权威契约解析(动态 import)
|
|
336
|
-
|
|
337
|
-
const HARNESS_INTERNAL_SUBPATH = '@deepseek-ai/dsh-subagent/internal';
|
|
338
|
-
/**
|
|
339
|
-
* `queueHostSubagentPrompt(runtime, parent, childId, content, source, signal)` 的形参个数(6)。
|
|
340
|
-
* 与 HARNESS_ROUTES 的 arity 同一性质:逐字核对上游导出面得到的;对不上就不用它(见下)。
|
|
341
|
-
*/
|
|
342
|
-
const HARNESS_ADAPTER_ARITY = 6;
|
|
343
|
-
/**
|
|
344
|
-
* 权威契约的当前状态(`resolveHarnessAuthority()` 的落点)。
|
|
345
|
-
* 初值 = "还没解析":一切按全局共识符号走(见 GLOBAL_SYMBOLS)。
|
|
346
|
-
*/
|
|
347
|
-
let authorityState = { settled: false, ok: false, changed: false, adapter: undefined, adapterArity: undefined, error: '' };
|
|
348
|
-
let authorityPromise;
|
|
349
|
-
const defaultImporter = (specifier) => import(specifier);
|
|
350
|
-
/**
|
|
351
|
-
* 解析宿主 `@deepseek-ai/dsh-subagent/internal` 这个**未文档化但已导出**的子路径,拿到两样东西:
|
|
352
|
-
* · `deliverSubagentPrompt` —— 权威的投递符号(我们按 `Symbol.for` 猜的那个键的**真身**);
|
|
353
|
-
* · `queueHostSubagentPrompt` —— 官方适配函数(上游自己就是 `runtime[deliverPrompt](…, 'queue')`)。
|
|
354
|
-
*
|
|
355
|
-
* 为什么用动态而不是静态 import:那个子路径在 Alpha.2 上**不存在**,静态 import 会让插件在那些
|
|
356
|
-
* 版本上整个加载不起来(见文件顶部说明)。动态 import 失败只是少一条兜底路径,不影响加载。
|
|
357
|
-
*
|
|
358
|
-
* 为什么值得做:全局符号是"约定",这个子路径是"**导出面**"(带类型声明)。上游把符号串改掉、
|
|
359
|
-
* 或换成非全局符号时,全局符号就找不到了 —— B-1 会因此响亮拒绝装守卫、引擎降级;而这里能拿到
|
|
360
|
-
* 真身,于是还能continue工作:`activeSymbols` 就地更换,所有读取点自动跟着走。
|
|
361
|
-
*
|
|
362
|
-
* 失败语义:解析不到(老宿主 / 安装树取不到 / 导出面变了)**不算错误** —— 退回全局共识符号,
|
|
363
|
-
* 报告里如实写 `authority.error`,绝不猜。
|
|
364
|
-
*
|
|
365
|
-
* @param options - `importer` 可注入解析器。自检用它喂一个"换了键"的假模块来验证这条路径,
|
|
366
|
-
* 验完再喂回默认解析器还原(见 tools/verify.mjs 的 [13g])。
|
|
367
|
-
* @returns 权威契约报告 `{ ok, deliver, adapter, adapterArity, changed, error }`。
|
|
368
|
-
*/
|
|
369
|
-
export function resolveHarnessAuthority(options = {}) {
|
|
370
|
-
const injected = typeof options.importer === 'function' ? options.importer : undefined;
|
|
371
|
-
if (injected !== undefined)
|
|
372
|
-
return loadHarnessAuthority(injected).then(applyHarnessAuthority);
|
|
373
|
-
return (authorityPromise ??= loadHarnessAuthority(defaultImporter).then(applyHarnessAuthority));
|
|
374
|
-
}
|
|
375
|
-
async function loadHarnessAuthority(importer) {
|
|
376
|
-
const report = { ok: false, deliver: undefined, adapter: undefined, adapterArity: undefined, changed: false, error: '' };
|
|
377
|
-
try {
|
|
378
|
-
const mod = await importer(HARNESS_INTERNAL_SUBPATH);
|
|
379
|
-
const deliver = mod?.deliverSubagentPrompt;
|
|
380
|
-
if (typeof deliver === 'symbol') {
|
|
381
|
-
report.ok = true;
|
|
382
|
-
report.deliver = deliver;
|
|
383
|
-
// "换了键"= 权威符号与全局共识符号不是同一个对象(rc.1/rc.2 上两者相等,什么都不用变)。
|
|
384
|
-
report.changed = deliver !== GLOBAL_SYMBOLS.deliver;
|
|
385
|
-
}
|
|
386
|
-
if (typeof mod?.queueHostSubagentPrompt === 'function') {
|
|
387
|
-
report.adapter = mod.queueHostSubagentPrompt;
|
|
388
|
-
report.adapterArity = mod.queueHostSubagentPrompt.length;
|
|
389
|
-
}
|
|
390
|
-
if (!report.ok)
|
|
391
|
-
report.error = 'internal subpath resolved but exports no delivery symbol';
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
report.error = error instanceof Error ? error.message : String(error);
|
|
395
|
-
}
|
|
396
|
-
return report;
|
|
397
|
-
}
|
|
398
|
-
/** 把解析结果落到模块状态(`activeSymbols.deliver` / `authorityState`)。 */
|
|
399
|
-
function applyHarnessAuthority(report) {
|
|
400
|
-
authorityState = {
|
|
401
|
-
settled: true,
|
|
402
|
-
ok: report.ok,
|
|
403
|
-
changed: report.changed,
|
|
404
|
-
adapter: report.adapter,
|
|
405
|
-
adapterArity: report.adapterArity,
|
|
406
|
-
error: report.error,
|
|
407
|
-
};
|
|
408
|
-
if (report.ok)
|
|
409
|
-
activeSymbols.deliver = report.deliver;
|
|
410
|
-
return report;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// ---------------------------------------------------------------- B-2:运行期软闸门
|
|
414
|
-
|
|
415
|
-
const HARNESS_PACKAGE = '@deepseek-ai/dsh-subagent';
|
|
416
|
-
/** 版本探测结果缓存(一份安装在一个进程里不会变;解析不到就记空串,绝不重试、也绝不猜)。 */
|
|
417
|
-
let detectedVersion;
|
|
418
|
-
/**
|
|
419
|
-
* 读宿主 `@deepseek-ai/dsh-subagent` 的版本(best-effort;读不到返回空串)。
|
|
420
|
-
*
|
|
421
|
-
* 为什么读它:本文件对接的是**未文档化内部实现**,实测矩阵只能人工维护(见
|
|
422
|
-
* `TESTED_HARNESS_VERSIONS`)。把"宿主到底是哪一版"记下来,出问题时才能一句话说清是不是
|
|
423
|
-
* 版本没在矩阵里,而不是让维护者从 `deliverPrompt` 的形参个数往回猜。
|
|
424
|
-
*
|
|
425
|
-
* 用**本文件自己的位置**做解析基准(`createRequire(import.meta.url)`):这个文件既然能静态
|
|
426
|
-
* import 到 `@deepseek-ai/dsh-subagent`,就一定能 resolve 到它的 `package.json`。解析失败不算
|
|
427
|
-
* 错误(某些封装安装树里可能取不到),报告里会写 `version: ''`,由形状探测兜底。
|
|
428
|
-
* @returns 形如 `0.1.5-rc.2` 的版本串,或空串。
|
|
429
|
-
*/
|
|
430
|
-
function detectHarnessVersion() {
|
|
431
|
-
if (detectedVersion !== undefined)
|
|
432
|
-
return detectedVersion;
|
|
433
|
-
detectedVersion = '';
|
|
434
|
-
try {
|
|
435
|
-
const require = createRequire(import.meta.url);
|
|
436
|
-
const manifest = JSON.parse(readFileSync(require.resolve(`${HARNESS_PACKAGE}/package.json`), 'utf8'));
|
|
437
|
-
if (typeof manifest?.version === 'string')
|
|
438
|
-
detectedVersion = manifest.version;
|
|
439
|
-
}
|
|
440
|
-
catch {
|
|
441
|
-
/* 读不到就只按形状判断;报告里的 reason 会如实写 version-unknown */
|
|
442
|
-
}
|
|
443
|
-
return detectedVersion;
|
|
444
|
-
}
|
|
445
|
-
/** 当前会真正被 `queueMemberPrompt` 用的那条投递路由(没有可用路由时返回 `none`)。 */
|
|
446
|
-
function usableDeliveryRoute(runtime) {
|
|
447
|
-
const adapter = authorityState.adapter;
|
|
448
|
-
if (typeof adapter === 'function' && typeof runtime?.[activeSymbols.deliver] === 'function') {
|
|
449
|
-
return `@deepseek-ai/dsh-subagent/internal queueHostSubagentPrompt`;
|
|
450
|
-
}
|
|
451
|
-
for (const route of HARNESS_ROUTES) {
|
|
452
|
-
if (route.role !== 'queue')
|
|
453
|
-
continue;
|
|
454
|
-
const fn = runtime?.[routeKey(route)];
|
|
455
|
-
if (typeof fn !== 'function')
|
|
456
|
-
continue;
|
|
457
|
-
if (route.arity !== null && fn.length !== route.arity)
|
|
458
|
-
continue;
|
|
459
|
-
return routeLabel(route);
|
|
460
|
-
}
|
|
461
|
-
return 'none';
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* 宿主 subagent 契约体检(B-2 的探测半边)—— **纯读、绝不抛错**:它是诊断,不是门禁。
|
|
465
|
-
*
|
|
466
|
-
* 和 B-1 的分工:B-1 是"装守卫那一刻的硬断言"(签名不符就不装,见 `guardSubagentDelivery`),
|
|
467
|
-
* 这里则给"引擎一切正常、但宿主是未实测版本"这种**没有症状的状态**留一个可读的结论 ——
|
|
468
|
-
* 上游改契约最常见的结局不是立刻崩,而是某天"队长叫不醒成员",那时只有这条结论能解释原因。
|
|
469
|
-
*
|
|
470
|
-
* @param ctx - 插件上下文(只读 `ctx.subagents`;服务缺席也算一种结果,不抛错)。
|
|
471
|
-
* @param options - `version` 可覆盖版本探测(自检用:用确定的值验证判定规则,不依赖本机装了什么)。
|
|
472
|
-
* @returns 契约报告:`version` / `tested` / `reason` / `drift` / `route` / `routes` / `signature`。
|
|
473
|
-
*/
|
|
474
|
-
export function probeHarnessContract(ctx, options = {}) {
|
|
475
|
-
const runtime = ctx?.subagents;
|
|
476
|
-
const routes = HARNESS_ROUTES.map((route) => {
|
|
477
|
-
const fn = runtime?.[routeKey(route)];
|
|
478
|
-
const present = typeof fn === 'function';
|
|
479
|
-
return {
|
|
480
|
-
label: routeLabel(route),
|
|
481
|
-
role: route.role,
|
|
482
|
-
present,
|
|
483
|
-
arity: present ? fn.length : undefined,
|
|
484
|
-
expected: route.arity === null ? undefined : route.arity,
|
|
485
|
-
ok: present && (route.arity === null || fn.length === route.arity),
|
|
486
|
-
};
|
|
487
|
-
});
|
|
488
|
-
const drift = [...deliverySignatureProblems(runtime)];
|
|
489
|
-
// 官方适配函数也是契约的一部分:形参个数对不上就**不用它**(调用路径见 queueMemberPrompt),
|
|
490
|
-
// 并且要出现在报告里 —— 否则"我们悄悄退回了自己拼参数的调用形态"这件事没人知道。
|
|
491
|
-
if (typeof authorityState.adapter === 'function'
|
|
492
|
-
&& authorityState.adapterArity !== undefined
|
|
493
|
-
&& authorityState.adapterArity !== HARNESS_ADAPTER_ARITY) {
|
|
494
|
-
drift.push(`${HARNESS_INTERNAL_SUBPATH} queueHostSubagentPrompt declares ${authorityState.adapterArity} parameters, expected ${HARNESS_ADAPTER_ARITY}`);
|
|
495
|
-
}
|
|
496
|
-
const version = typeof options.version === 'string' ? options.version : detectHarnessVersion();
|
|
497
|
-
const reason = drift.length > 0
|
|
498
|
-
? 'contract-drift'
|
|
499
|
-
: authorityState.changed
|
|
500
|
-
? 'symbol-renamed'
|
|
501
|
-
: version === ''
|
|
502
|
-
? 'version-unknown'
|
|
503
|
-
: TESTED_HARNESS_VERSIONS.includes(version) ? '' : 'untested-version';
|
|
504
|
-
return {
|
|
505
|
-
version,
|
|
506
|
-
tested: reason === '',
|
|
507
|
-
reason,
|
|
508
|
-
drift,
|
|
509
|
-
route: usableDeliveryRoute(runtime),
|
|
510
|
-
// 权威解析(B-3)的状态:`changed` 为真表示宿主的投递符号**换了口径**、已自动改用真身。
|
|
511
|
-
authority: {
|
|
512
|
-
settled: authorityState.settled,
|
|
513
|
-
ok: authorityState.ok,
|
|
514
|
-
changed: authorityState.changed,
|
|
515
|
-
adapter: typeof authorityState.adapter === 'function',
|
|
516
|
-
error: authorityState.error,
|
|
517
|
-
},
|
|
518
|
-
routes,
|
|
519
|
-
signature: `${version}|${reason}|${drift.join('; ')}`,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* 契约报告 → 一句**可执行**的诊断(供日志与提示段共用)。
|
|
524
|
-
* @param report - `probeHarnessContract()` 的结果。
|
|
525
|
-
* @returns 英文单行描述(与 `unsupported()` 的消息同语言,日志里成一条线)。
|
|
526
|
-
*/
|
|
527
|
-
export function harnessContractWarning(report) {
|
|
528
|
-
const where = report.route === 'none' ? 'no usable delivery route' : `delivery goes through ${report.route}`;
|
|
529
|
-
if (report.reason === 'contract-drift') {
|
|
530
|
-
return `subagent contract drifted from the tested shape (${report.drift.join('; ')}); ${where}. Waking members / blocking retired members can now misbehave — re-verify lib/teams/harness-compat.js against the installed @deepseek-ai/dsh-subagent and extend TESTED_HARNESS_VERSIONS.`;
|
|
531
|
-
}
|
|
532
|
-
if (report.reason === 'symbol-renamed') {
|
|
533
|
-
return `@deepseek-ai/dsh-subagent no longer keys host delivery with the documented global symbol; switched to the authoritative symbol exported by ${HARNESS_INTERNAL_SUBPATH} (${where}). Team features keep working — re-verify this adapter against the installed host and extend TESTED_HARNESS_VERSIONS so the switch stops being silent.`;
|
|
534
|
-
}
|
|
535
|
-
if (report.reason === 'version-unknown') {
|
|
536
|
-
return `could not resolve the installed @deepseek-ai/dsh-subagent version; shape matches the tested contract, ${where}. If team features misbehave after a Harness upgrade, check this line first.`;
|
|
537
|
-
}
|
|
538
|
-
return `@deepseek-ai/dsh-subagent ${report.version} is not in the tested matrix (${TESTED_HARNESS_VERSIONS.join(' / ')}); shape matches the tested contract, ${where}. Team features run on a best-effort contract — re-verify and extend TESTED_HARNESS_VERSIONS.`;
|
|
539
|
-
}
|
|
540
|
-
/** 已经报过的契约签名:同一份签名只报一次,避免每次挂载/热重载都刷同一条日志。 */
|
|
541
|
-
const reportedContracts = new Set();
|
|
542
|
-
/**
|
|
543
|
-
* 体检 + **同签名只报一次**的告警(B-2 的出声半边)。
|
|
544
|
-
*
|
|
545
|
-
* 为什么按签名去重而不是"一个布尔量只报一次":宿主换了版本、或形状变了,`signature` 就变了 ——
|
|
546
|
-
* 那是一次**新的**事实,必须再出声("报过一次就永远闭嘴"正是要避免的另一种静默)。
|
|
547
|
-
* 已实测(版本在矩阵内且无漂移)时**完全不出声**:常态不该占日志,也不该占提示词预算。
|
|
548
|
-
*
|
|
549
|
-
* @param ctx - 插件上下文。
|
|
550
|
-
* @param logger - 宿主 logger(缺席时静默降级 —— 不写 console:桌面/Web 里 console 无人可见)。
|
|
551
|
-
* @param options - 同 `probeHarnessContract`。
|
|
552
|
-
* @returns 契约报告(调用方可以直接拿去渲染状态行)。
|
|
553
|
-
*/
|
|
554
|
-
export function reportHarnessContract(ctx, logger, options = {}) {
|
|
555
|
-
const report = probeHarnessContract(ctx, options);
|
|
556
|
-
if (report.tested || reportedContracts.has(report.signature))
|
|
557
|
-
return report;
|
|
558
|
-
reportedContracts.add(report.signature);
|
|
559
|
-
logger?.warn?.(`[t-team] ${harnessContractWarning(report)}`);
|
|
560
|
-
return report;
|
|
561
|
-
}
|