dsh-plugin-t-expert 0.1.1 → 0.2.2
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 +58 -33
- package/THIRD-PARTY-NOTICES +24 -13
- package/cordis.patch.yml +1 -1
- package/data/source.json +1 -1
- package/data/team-profiles.py +1 -1
- package/data/teams.json +1 -1
- package/data/zh/manual.json +1 -1
- package/lib/catalog.js +1 -1
- package/lib/client.js +111 -65
- package/lib/i18n.js +4 -4
- package/lib/index.js +124 -50
- package/lib/plan-check.js +3 -1
- package/lib/teams/capabilities.js +1 -1
- package/lib/teams/index.js +1 -1
- package/lib/teams/quality-gates.js +4 -5
- package/lib/teams/tools.js +6 -3
- package/package.json +10 -12
- package/vendor/dsh-agent-teams/LICENSE +21 -0
- package/vendor/dsh-agent-teams/VENDOR.md +53 -0
- package/vendor/third-party-licenses/README.md +22 -0
- package/vendor/third-party-licenses/agency-agents-zh.LICENSE +22 -0
- package/vendor/third-party-licenses/agency-agents.LICENSE +21 -0
package/lib/i18n.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
export const NS = "t-team";
|
|
4
4
|
|
|
5
5
|
const ZH = {
|
|
6
|
-
"error.rootMissing": "T专家 专家目录不存在或无法访问:\"{root}\"
|
|
6
|
+
"error.rootMissing": "T专家 专家目录不存在或无法访问:\"{root}\"。插件首次启动会自动把自带名册播种到该目录;若你手工移动过数据目录,请把 root 指到正确位置。",
|
|
7
7
|
"error.rootNotDir": "T专家 专家目录 \"{root}\" 不是目录",
|
|
8
|
-
"error.catalogEmpty": "在 root \"{root}\" 下没有发现任何专家(*.md
|
|
8
|
+
"error.catalogEmpty": "在 root \"{root}\" 下没有发现任何专家(*.md)。删掉这个空目录让插件下次启动重新播种,或把 root 改到正确的名册目录。",
|
|
9
9
|
"error.summonRequiresAgent": "当前上下文没有可用的 parent agent,无法召唤专家。",
|
|
10
10
|
"error.providerMissing": "找不到子代理 provider:{provider}",
|
|
11
11
|
"error.providerNoPersona": "provider {provider} 不支持 persona,无法注入专家人格。",
|
|
@@ -32,9 +32,9 @@ const ZH = {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
const EN = {
|
|
35
|
-
"error.rootMissing": "T Expert expert directory is missing or unreadable: \"{root}\".
|
|
35
|
+
"error.rootMissing": "T Expert expert directory is missing or unreadable: \"{root}\". The plugin seeds its bundled roster there on first start; point root at the right place if you moved the data directory.",
|
|
36
36
|
"error.rootNotDir": "T Expert expert root \"{root}\" is not a directory",
|
|
37
|
-
"error.catalogEmpty": "No experts (*.md) found under root \"{root}\".
|
|
37
|
+
"error.catalogEmpty": "No experts (*.md) found under root \"{root}\". Delete this empty directory so the plugin re-seeds on next start, or point root at the real roster.",
|
|
38
38
|
"error.summonRequiresAgent": "No parent agent in this context; cannot summon an expert.",
|
|
39
39
|
"error.providerMissing": "Subagent provider not found: {provider}",
|
|
40
40
|
"error.providerNoPersona": "Provider {provider} does not support persona injection.",
|
package/lib/index.js
CHANGED
|
@@ -34,9 +34,10 @@ import { createSquadService } from "./squads.js";
|
|
|
34
34
|
import { checkPlan } from "./plan-check.js";
|
|
35
35
|
import { findTeamByCaptain } from "./teams/state.js";
|
|
36
36
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// 安装 T专家
|
|
37
|
+
// 内置团队引擎:随插件一起发布的品牌化产物在 lib/teams/ 下(由 scripts/brand.mjs 从
|
|
38
|
+
// vendor/ 里的上游镜像改名生成),这里在 T专家 内部挂载它 ——
|
|
39
|
+
// 安装 T专家 即自带团队引擎与团队面板,不再需要单独安装引擎插件。
|
|
40
|
+
// 上游来源、版本与许可见 THIRD-PARTY-NOTICES 与 vendor/。
|
|
40
41
|
import * as teamsEngine from "./teams/index.js";
|
|
41
42
|
// 引擎自带的 /t 命令不注册(否则与 T专家 自己的 /t 撞名),但手势边界必须复用:
|
|
42
43
|
// 它把 `/t --profile <小队> <目标>` 这条用户消息翻译成队长协议指令。
|
|
@@ -48,7 +49,14 @@ import { haltTeamWork } from "./teams/tools.js";
|
|
|
48
49
|
export const name = NS;
|
|
49
50
|
export const inject = ["tools", "subagents", "systemPrompt", "settings", "commands"];
|
|
50
51
|
|
|
51
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* 插件配置。
|
|
54
|
+
*
|
|
55
|
+
* 规范要求「部署可能取不同值的参数都必须是可被 cordis.yml 覆盖的 Config 字段」,
|
|
56
|
+
* 所以召唤上限/并发、任务长度上限、简介截断长度,以及团队引擎的
|
|
57
|
+
* stateDir / memberProvider / memberModel / maxMembers 与引擎配置路径都在这里;
|
|
58
|
+
* 模块级常量只保留协议常量与固定清单。
|
|
59
|
+
*/
|
|
52
60
|
export const Config = schema.object({
|
|
53
61
|
root: schema.string().default(join(homedir(), ".t-team", "experts")),
|
|
54
62
|
zhRoot: schema.string().default(join(homedir(), ".t-team", "zh")),
|
|
@@ -56,6 +64,24 @@ export const Config = schema.object({
|
|
|
56
64
|
/** 留空 = 自动扫描 root 下所有含 .md 的分区目录(上游新增分区不必改配置/代码)。 */
|
|
57
65
|
divisions: schema.array(schema.string()).default([]),
|
|
58
66
|
maxDepth: schema.natural(),
|
|
67
|
+
/** 一次 summon_t_experts 最多召唤几位专家。 */
|
|
68
|
+
maxSummonBatch: schema.natural().min(1).default(8),
|
|
69
|
+
/** summon_t_experts 的并发度。 */
|
|
70
|
+
summonConcurrency: schema.natural().min(1).default(4),
|
|
71
|
+
/** 单个专家任务的最大字符数。 */
|
|
72
|
+
summonTaskMaxChars: schema.natural().min(1).default(8000),
|
|
73
|
+
/** list_t_experts 里简介的截断长度。 */
|
|
74
|
+
descriptionLimit: schema.natural().min(1).default(120),
|
|
75
|
+
/** 团队引擎配置(team-profiles.py 的产物);留空则取 root 同级目录下的 t-team.config.json。 */
|
|
76
|
+
engineConfig: schema.string(),
|
|
77
|
+
/** 引擎的团队状态目录(工作区内,相对路径)。 */
|
|
78
|
+
stateDir: schema.string().default(".agent-teams"),
|
|
79
|
+
/** 队员子代理使用的 provider 名。 */
|
|
80
|
+
memberProvider: schema.string().default("spawn"),
|
|
81
|
+
/** 队员默认模型;留空 = 跟随队长。 */
|
|
82
|
+
memberModel: schema.string(),
|
|
83
|
+
/** 一支团队最多几名成员。 */
|
|
84
|
+
maxMembers: schema.natural().min(1).default(8),
|
|
59
85
|
});
|
|
60
86
|
|
|
61
87
|
/** 供 remote 服务读取名册的服务名。 */
|
|
@@ -67,10 +93,6 @@ export const TEAM_SERVICE = "tTeamTeams";
|
|
|
67
93
|
/** 设置命名空间(= 插件名)。 */
|
|
68
94
|
export const SETTINGS_NAMESPACE = NS;
|
|
69
95
|
|
|
70
|
-
const SUMMON_EXPERTS_MAX = 8;
|
|
71
|
-
const SUMMON_EXPERTS_CONCURRENCY = 4;
|
|
72
|
-
const SUMMON_TASK_MAX_CHARS = 8000;
|
|
73
|
-
const DESCRIPTION_LIMIT = 120;
|
|
74
96
|
|
|
75
97
|
const settingsSchema = schema.object({
|
|
76
98
|
enabled: schema.array(schema.string()).default([]),
|
|
@@ -84,10 +106,12 @@ export function apply(ctx, config) {
|
|
|
84
106
|
// 必须在下面同步读取 t-team.config.json / teams.json 之前完成,否则首次安装会读到空配置。
|
|
85
107
|
const seeded = seedData({ root: config.root, zhRoot: config.zhRoot });
|
|
86
108
|
if (seeded.copied.length > 0) {
|
|
87
|
-
|
|
109
|
+
ctx.logger?.info?.(`[t-team] 已把包内名册快照播种到 ${dirname(config.root)}(${seeded.copied.join(", ")})`);
|
|
88
110
|
}
|
|
89
111
|
if (!seeded.ok) {
|
|
90
|
-
|
|
112
|
+
// 播种失败不当作致命:名册目录若因此缺失,第一次读名册会在 ensureReady() 里**响亮地**抛出
|
|
113
|
+
// (error.rootMissing),那才是最早可解析点。这里只保证失败一定会出现在日志里。
|
|
114
|
+
ctx.logger?.error?.(`[t-team] 名册快照播种失败,将按已有数据继续:${seeded.error}`);
|
|
91
115
|
}
|
|
92
116
|
|
|
93
117
|
// ---- 设置段(T专家 自己的命名空间,与其它插件不冲突)----
|
|
@@ -115,19 +139,28 @@ export function apply(ctx, config) {
|
|
|
115
139
|
let loadError = null;
|
|
116
140
|
|
|
117
141
|
/**
|
|
118
|
-
* 名册指纹:source.json
|
|
119
|
-
*
|
|
120
|
-
*
|
|
142
|
+
* 名册指纹:source.json、各分区目录、以及中文侧车文件的 mtime。
|
|
143
|
+
*
|
|
144
|
+
* 要点:运行期读的中文正文路径是 `zh/<分区>/<slug>.md`(不是 `manual-bodies/`,那是
|
|
145
|
+
* 数据层的**输入**,由同步脚本合并后写进 `zh/<分区>/`)。所以 `zhRoot` 与每个
|
|
146
|
+
* `zh/<分区>/` 目录都必须进指纹 —— 否则「给某位专家**新增**一篇中文正文」这条最常见的
|
|
147
|
+
* 补译操作不会触发重载:该专家的 personaPathZh 一直是 undefined,召唤时只能读英文。
|
|
148
|
+
* (已在 verify 里用临时数据目录做过行为验证。)
|
|
121
149
|
*/
|
|
122
150
|
async function fingerprint() {
|
|
123
151
|
const mtimeOf = (path) => stat(path).then((info) => info.mtimeMs).catch(() => 0);
|
|
124
152
|
const parts = await Promise.all([
|
|
125
153
|
mtimeOf(config.root),
|
|
154
|
+
mtimeOf(config.zhRoot),
|
|
155
|
+
mtimeOf(join(config.zhRoot, "manual-bodies")),
|
|
126
156
|
mtimeOf(join(dirname(config.root), "source.json")),
|
|
127
157
|
mtimeOf(join(config.zhRoot, "names.json")),
|
|
128
158
|
mtimeOf(join(config.zhRoot, "descriptions.json")),
|
|
129
159
|
mtimeOf(join(config.zhRoot, "divisions.json")),
|
|
160
|
+
mtimeOf(join(config.zhRoot, "manual.json")),
|
|
130
161
|
...effectiveDivisions.map((division) => mtimeOf(join(config.root, division))),
|
|
162
|
+
...effectiveDivisions.map((division) => mtimeOf(join(config.zhRoot, division))),
|
|
163
|
+
...effectiveDivisions.map((division) => mtimeOf(join(config.zhRoot, "manual-bodies", division))),
|
|
131
164
|
]);
|
|
132
165
|
return parts.join("|");
|
|
133
166
|
}
|
|
@@ -219,8 +252,8 @@ export function apply(ctx, config) {
|
|
|
219
252
|
const current = locale();
|
|
220
253
|
const taskText = String(task ?? "").trim();
|
|
221
254
|
if (taskText === "") throw new Error(t(current, "error.taskEmpty"));
|
|
222
|
-
if ([...taskText].length >
|
|
223
|
-
throw new Error(t(current, "error.taskTooLong", { limit:
|
|
255
|
+
if ([...taskText].length > config.summonTaskMaxChars) {
|
|
256
|
+
throw new Error(t(current, "error.taskTooLong", { limit: config.summonTaskMaxChars }));
|
|
224
257
|
}
|
|
225
258
|
if (exec.agent === undefined) throw new Error(t(current, "error.summonRequiresAgent"));
|
|
226
259
|
|
|
@@ -349,7 +382,7 @@ export function apply(ctx, config) {
|
|
|
349
382
|
slug: expert.slug,
|
|
350
383
|
name,
|
|
351
384
|
emoji: expert.emoji,
|
|
352
|
-
description: truncate(description,
|
|
385
|
+
description: truncate(description, config.descriptionLimit),
|
|
353
386
|
};
|
|
354
387
|
}),
|
|
355
388
|
}));
|
|
@@ -386,7 +419,7 @@ export function apply(ctx, config) {
|
|
|
386
419
|
|
|
387
420
|
ctx.tools.register(defineTool({
|
|
388
421
|
name: "summon_t_experts",
|
|
389
|
-
description:
|
|
422
|
+
description: `Summon multiple T专家 experts in parallel for one mission. At most ${config.maxSummonBatch} experts run with concurrency ${config.summonConcurrency}; successful answers are returned even when some fail.`,
|
|
390
423
|
parameters: {
|
|
391
424
|
experts: {
|
|
392
425
|
type: "array",
|
|
@@ -418,15 +451,15 @@ export function apply(ctx, config) {
|
|
|
418
451
|
const current = locale();
|
|
419
452
|
const specs = Array.isArray(args.experts) ? args.experts : [];
|
|
420
453
|
if (specs.length === 0) throw new Error(t(current, "error.specsEmpty"));
|
|
421
|
-
if (specs.length >
|
|
422
|
-
throw new Error(t(current, "error.specsTooMany", { limit:
|
|
454
|
+
if (specs.length > config.maxSummonBatch) {
|
|
455
|
+
throw new Error(t(current, "error.specsTooMany", { limit: config.maxSummonBatch }));
|
|
423
456
|
}
|
|
424
457
|
specs.forEach((spec, index) => {
|
|
425
458
|
if (spec === null || typeof spec !== "object" || typeof spec.expert !== "string" || typeof spec.task !== "string") {
|
|
426
459
|
throw new Error(t(current, "error.specInvalid", { index }));
|
|
427
460
|
}
|
|
428
461
|
});
|
|
429
|
-
const results = await mapPool(specs,
|
|
462
|
+
const results = await mapPool(specs, config.summonConcurrency, async (spec) => {
|
|
430
463
|
try {
|
|
431
464
|
const result = await runExpert(spec.expert, spec.task, exec);
|
|
432
465
|
return { expert: result.expert, ok: true, answer: result.answer };
|
|
@@ -443,42 +476,81 @@ export function apply(ctx, config) {
|
|
|
443
476
|
},
|
|
444
477
|
}));
|
|
445
478
|
|
|
446
|
-
// ---- 内置团队引擎(vendor
|
|
447
|
-
//
|
|
479
|
+
// ---- 内置团队引擎(vendor 自上游并品牌化;小队数据来自 team-profiles.py 生成的 JSON)----
|
|
480
|
+
// 配置路径与引擎标量都是 Config 字段(可从 cordis.yml 覆盖):标量以 Config 为准,
|
|
481
|
+
// 数据文件只提供生成物本身(profiles / memberMaxDepth)——小队目录是数据,不是可调参数。
|
|
448
482
|
const engineConfigDir = dirname(config.root);
|
|
449
|
-
const engineConfigPath =
|
|
450
|
-
.
|
|
451
|
-
|
|
483
|
+
const engineConfigPath = typeof config.engineConfig === "string" && config.engineConfig !== ""
|
|
484
|
+
? config.engineConfig
|
|
485
|
+
: ["t-team.config.json", "agent-teams.config.json"]
|
|
486
|
+
.map((file) => join(engineConfigDir, file))
|
|
487
|
+
.find((candidate) => existsSync(candidate)) ?? join(engineConfigDir, "t-team.config.json");
|
|
452
488
|
let engineConfig = {};
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
489
|
+
let engineConfigDetail = "";
|
|
490
|
+
// 配置错误要响亮:**文件存在但内容不合法**属于自带的错配,直接让插件加载失败,
|
|
491
|
+
// 而不是带着空配置把团队功能静默降级。文件不存在是"还没生成小队"(自定义 root 时合法),
|
|
492
|
+
// 这种情况明确降级,并把原因送到所有可见面(日志 + /t + 下面的 engine-status 提示段)。
|
|
493
|
+
if (existsSync(engineConfigPath)) {
|
|
494
|
+
let parsed;
|
|
495
|
+
try {
|
|
496
|
+
parsed = JSON.parse(readFileSync(engineConfigPath, "utf8"));
|
|
497
|
+
} catch (error) {
|
|
498
|
+
throw new Error(`团队引擎配置无法解析:${engineConfigPath}(${error instanceof Error ? error.message : String(error)})。它是 team-profiles.py 的产物,删掉后重跑一次即可重建。`);
|
|
499
|
+
}
|
|
500
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
501
|
+
throw new Error(`团队引擎配置必须是 JSON 对象:${engineConfigPath}`);
|
|
502
|
+
}
|
|
503
|
+
if (parsed.profiles !== undefined && (parsed.profiles === null || typeof parsed.profiles !== "object" || Array.isArray(parsed.profiles))) {
|
|
504
|
+
throw new Error(`团队引擎配置的 profiles 必须是对象:${engineConfigPath}`);
|
|
505
|
+
}
|
|
506
|
+
engineConfig = parsed;
|
|
507
|
+
} else {
|
|
508
|
+
engineConfigDetail = `找不到团队引擎配置 ${engineConfigPath}(跑一次 team-profiles.py 生成小队后即可用 /t 拉起团队)`;
|
|
458
509
|
}
|
|
459
510
|
// 引擎与名册解耦:引擎挂载失败(例如 Harness 子代理契约不匹配)时,
|
|
460
|
-
// T专家 的专家名册、@ 召唤、/t
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
511
|
+
// T专家 的专家名册、@ 召唤、/t 列表仍然可用,只是团队功能不可用。
|
|
512
|
+
// 这是**已声明的降级**,不是静默跳过:原因会写进日志、经 /t 报出,并进系统提示段。
|
|
513
|
+
const engineState = { ok: false, detail: engineConfigDetail };
|
|
514
|
+
if (engineConfigDetail === "") {
|
|
515
|
+
try {
|
|
516
|
+
ctx.plugin(teamsEngine, {
|
|
517
|
+
stateDir: config.stateDir,
|
|
518
|
+
memberProvider: config.memberProvider,
|
|
519
|
+
...(config.memberModel === undefined ? {} : { memberModel: config.memberModel }),
|
|
520
|
+
...(engineConfig.memberMaxDepth === undefined ? {} : { memberMaxDepth: engineConfig.memberMaxDepth }),
|
|
521
|
+
maxMembers: config.maxMembers,
|
|
522
|
+
// 引擎自带命令必须关掉:T专家 自己注册 /t(带小队别名解析),否则两个同名命令相撞。
|
|
523
|
+
// 手势边界另外复用(见下),它是队长协议真正被触发的地方。
|
|
524
|
+
slashCommand: false,
|
|
525
|
+
profiles: engineConfig.profiles ?? {},
|
|
526
|
+
});
|
|
527
|
+
// 复用引擎导出的手势边界:把 `/t --profile <小队> <目标>` 这条用户消息翻成队长协议指令。
|
|
528
|
+
installTTeamGestureBoundary(ctx, () => engineConfig.profiles ?? {});
|
|
529
|
+
engineState.ok = true;
|
|
530
|
+
} catch (error) {
|
|
531
|
+
engineState.detail = error instanceof Error ? error.message : String(error);
|
|
532
|
+
ctx.logger?.error?.(`[t-team] 团队引擎挂载失败(专家名册功能不受影响):${engineState.detail}`);
|
|
533
|
+
}
|
|
534
|
+
} else {
|
|
535
|
+
ctx.logger?.error?.(`[t-team] ${engineConfigDetail}`);
|
|
480
536
|
}
|
|
481
537
|
|
|
538
|
+
// 已声明的降级也要**对模型可见**:否则"团队功能没了"这件事只留在日志里,
|
|
539
|
+
// 用户问起来模型只能说不知道 —— 那正是规范禁止的"静默跳过缺失的引用对象"。
|
|
540
|
+
ctx.systemPrompt.section({
|
|
541
|
+
name: "t-team:engine-status",
|
|
542
|
+
order: 120,
|
|
543
|
+
text: (context) => {
|
|
544
|
+
if (context.agent?.session?.header?.parentSession !== undefined) return "";
|
|
545
|
+
if (engineState.ok === true) return "";
|
|
546
|
+
return [
|
|
547
|
+
"## T专家 团队引擎当前不可用",
|
|
548
|
+
`团队功能(/t 建队、t_team_* 工具)现在不可用。原因:${engineState.detail}`,
|
|
549
|
+
"专家名册、@ 召唤与 summon_t_expert 不受影响。用户需要团队功能时,请原样转述上面的原因,不要假装能建队。",
|
|
550
|
+
].join("\n");
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
|
|
482
554
|
// ---- 小队定义服务(设置页「小队」标签用;写回 teams.json 后自动重跑 team-profiles.py)----
|
|
483
555
|
const teamsFile = join(dirname(config.root), "teams.json");
|
|
484
556
|
const squads = createSquadService({
|
|
@@ -504,6 +576,8 @@ export function apply(ctx, config) {
|
|
|
504
576
|
teamId: team.teamId,
|
|
505
577
|
name: team.name,
|
|
506
578
|
workspace: team.workspace,
|
|
579
|
+
// 面板/设置页的「打开活动面板」要带 captainSessionId:活动面板是会话作用域的,
|
|
580
|
+
// 缺它就只能匹配当前会话的团队,别的会话(或历史归档)点按钮会静默无反应。
|
|
507
581
|
captainSessionId: team.captainSessionId,
|
|
508
582
|
phase: team.phase,
|
|
509
583
|
halted: team.halted === true,
|
package/lib/plan-check.js
CHANGED
|
@@ -77,7 +77,9 @@ export function checkPlan(team, tasks) {
|
|
|
77
77
|
const sim = { ...(team ?? {}), tasks: [...existing] };
|
|
78
78
|
const taken = new Set(existing.map((task) => task.id));
|
|
79
79
|
const labels = new Map(); // 本批 id → 模拟 id
|
|
80
|
-
|
|
80
|
+
// 引擎分配的是 `t${team.taskSeq + 1}` 并随后自增(不是"现有任务条数")。两者只在
|
|
81
|
+
// taskSeq === tasks.length 时才一致 —— 有任务被删除/取消过就会分叉,所以以 taskSeq 为准。
|
|
82
|
+
let counter = Number.isSafeInteger(team?.taskSeq) ? team.taskSeq : existing.length;
|
|
81
83
|
const report = [];
|
|
82
84
|
|
|
83
85
|
proposed.forEach((raw, index) => {
|
|
@@ -3,7 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { readTeamSync, readRetiredMemberIdsSync } from "./state.js";
|
|
4
4
|
import { MEMBER_TOOL_NAMES, TEAM_TOOL_NAMES } from "./tool-names.js";
|
|
5
5
|
export const TEAM_ACTIVATION_PROMPT = 'T Team (T专家 团队) provides multi-agent team collaboration. Apply these rules when the user requests it (including /t-team) or when continuing an existing team. Mentioning, quoting, discussing, or declining T Team alone is not a request to start work.';
|
|
6
|
-
export const TEAM_MEMBER_PROMPT = 'You are
|
|
6
|
+
export const TEAM_MEMBER_PROMPT = 'You are a T Team member. Follow your assigned member persona and task contract. Use t_team_claim_task, t_team_update_task, t_team_send_message and t_team_status for your own work. Include the current attempt_id in updates; report completion or failure to the captain. Do not create, approve, edit or resume a team. If your durable membership is unavailable, report that to the parent instead of creating a replacement.';
|
|
7
7
|
function stateRoot(agent, config) {
|
|
8
8
|
return join(agent.session.header.cwd ?? process.cwd(), config.stateDir);
|
|
9
9
|
}
|
package/lib/teams/index.js
CHANGED
|
@@ -91,7 +91,7 @@ export function usageSectionText(toolNames, profilesText = '') {
|
|
|
91
91
|
1. Inspect current team state when needed, using t_team_status. Continue existing work without duplicating its roster/tasks. Create only when no current team exists, with the user's goal as description and approval="required"; automatic approval requires an explicit request to run immediately. Staged plans never spawn or schedule work.
|
|
92
92
|
2. Add each needed role once; members inherit your model route unless another is requested/needed. A requested profile goes to create({profile}); it supplies its roster. Seed profiles also supply tasks; captain-planning profiles require your DAG. Do not duplicate either.
|
|
93
93
|
3. Build the complete smallest useful DAG while staged. Every task needs a subject; dependencies represent prerequisites. Give every required contributor a task or explicit message. Present the plan and end your turn for review; never approve in that planning turn. Approve only after a later explicit user approval or the Web action.
|
|
94
|
-
4. Respect Web approve/return/discard control messages. On return, ask what to change before editing; after the answer, use one atomic t_team_edit_plan batch (edit downstream references before removals), summarize and await review again. Never inspect or edit
|
|
94
|
+
4. Respect Web approve/return/discard control messages. On return, ask what to change before editing; after the answer, use one atomic t_team_edit_plan batch (edit downstream references before removals), summarize and await review again. Never inspect or edit the T Team state directory in the workspace or the plugin source code to revise plans. Discard does not authorize a replacement.
|
|
95
95
|
5. The scheduler dispatches ready tasks after approval. Delegate; do not duplicate slow work or send messages merely to start a stage. Handle reports/user work, then yield when waiting is all that remains: reports wake you automatically. Use status after a delivery or user request, never busy-poll or wait for unassigned members.
|
|
96
96
|
6. Tasks carry attempt_id capabilities. Use the current attempt_id; stale means ownership changed. Pause members only on explicit request; later guidance via send_message continues that same attempt. Retry, transfer or take over through reassign_task first; it revokes the old attempt and waits for quiescence. Prefer a member. Captain implementation/review takeover requires a user request. Every takeover is one ready task at a time, finished in this turn; never yield with captain-owned work open.
|
|
97
97
|
7. Quality kinds (requirements, implementation, verification, review, repair, integration) require objective + acceptance; implementation/repair also require inScope + verify. Derive paths/commands from the workspace/profile, never assume src/ or pnpm test. Review/requirements complete only with verdict=pass; needs_revision/reject fail with findings. Never approve your own implementation or ask for a deliberate failure.
|
|
@@ -119,7 +119,7 @@ function isDefaultExcluded(path) {
|
|
|
119
119
|
const base = segments[segments.length - 1] ?? '';
|
|
120
120
|
if (segments[0] === '.git' || segments[0] === '.dsh')
|
|
121
121
|
return true;
|
|
122
|
-
if (base === '.env' || base.startsWith('.env.'))
|
|
122
|
+
if (base === '.env' || (base.startsWith('.env.') && !['.example', '.sample', '.template', '.dist'].some(suffix => base.endsWith(suffix))))
|
|
123
123
|
return true;
|
|
124
124
|
if (segments.includes('secrets'))
|
|
125
125
|
return true;
|
|
@@ -253,9 +253,7 @@ export function validateCreateTask(team, input) {
|
|
|
253
253
|
continue;
|
|
254
254
|
if (!OPEN_STATUSES.includes(other.status))
|
|
255
255
|
continue;
|
|
256
|
-
if (
|
|
257
|
-
continue;
|
|
258
|
-
if (dependencies.includes(other.id))
|
|
256
|
+
if (dependencyClosureContains(team.tasks, dependencies, other.id))
|
|
259
257
|
continue;
|
|
260
258
|
const overlap = inScopeOverlap(input.inScope, other.inScope);
|
|
261
259
|
if (overlap.length > 0) {
|
|
@@ -280,7 +278,7 @@ export function validateCreateTask(team, input) {
|
|
|
280
278
|
}
|
|
281
279
|
}
|
|
282
280
|
const nextTeam = team.halted === true && input.resume === true
|
|
283
|
-
? { ...team, halted: false, haltedAt: undefined }
|
|
281
|
+
? { ...team, halted: false, haltedAt: undefined, escalated: false }
|
|
284
282
|
: team;
|
|
285
283
|
return {
|
|
286
284
|
ok: true,
|
|
@@ -571,6 +569,7 @@ export function resumeTeamState(team, reason) {
|
|
|
571
569
|
...team,
|
|
572
570
|
halted: false,
|
|
573
571
|
haltedAt: undefined,
|
|
572
|
+
escalated: false,
|
|
574
573
|
},
|
|
575
574
|
};
|
|
576
575
|
}
|
package/lib/teams/tools.js
CHANGED
|
@@ -710,7 +710,7 @@ export function registerTTeamTools(ctx, config) {
|
|
|
710
710
|
}));
|
|
711
711
|
ctx.tools.register(defineTool({
|
|
712
712
|
name: 't_team_edit_plan',
|
|
713
|
-
description: 'Atomically revise the current staged T Team plan without spawning members or scheduling tasks. Use this when the user continues chatting to change a plan that is waiting for approval. Submit dependent edits in order (update downstream dependencies or assignees, then remove tasks, then remove unused members). Never inspect or edit
|
|
713
|
+
description: 'Atomically revise the current staged T Team plan without spawning members or scheduling tasks. Use this when the user continues chatting to change a plan that is waiting for approval. Submit dependent edits in order (update downstream dependencies or assignees, then remove tasks, then remove unused members). Never inspect or edit the T Team state directory in the workspace or plugin source code to revise a plan.',
|
|
714
714
|
parameters: {
|
|
715
715
|
operations: {
|
|
716
716
|
type: 'array',
|
|
@@ -1005,7 +1005,7 @@ export function registerTTeamTools(ctx, config) {
|
|
|
1005
1005
|
const member = requireMember(fresh, args.name);
|
|
1006
1006
|
const requeued = [];
|
|
1007
1007
|
for (const task of fresh.tasks) {
|
|
1008
|
-
if (task.assignee !== member.name || task.status
|
|
1008
|
+
if (task.assignee !== member.name || TERMINAL_TASK_STATUSES.includes(task.status))
|
|
1009
1009
|
continue;
|
|
1010
1010
|
invalidateTaskAttempt(task);
|
|
1011
1011
|
task.reassigning = false;
|
|
@@ -1508,10 +1508,13 @@ export function registerTTeamTools(ctx, config) {
|
|
|
1508
1508
|
throw new Error(`task ${task.id} is owned by member "${task.assignee}"; call t_team_reassign_task with assignee="captain" before takeover`);
|
|
1509
1509
|
}
|
|
1510
1510
|
if (identity.kind === 'member') {
|
|
1511
|
+
if (task.reassigning === true) {
|
|
1512
|
+
throw new Error(`task ${task.id} is being reassigned; stop work and wait for the fresh assignment`);
|
|
1513
|
+
}
|
|
1511
1514
|
if (task.assignee !== identity.name) {
|
|
1512
1515
|
throw new Error(`task ${task.id} is assigned to "${task.assignee ?? 'nobody'}", not you`);
|
|
1513
1516
|
}
|
|
1514
|
-
if (task.attemptId !==
|
|
1517
|
+
if (task.attemptId !== args.attempt_id) {
|
|
1515
1518
|
throw new Error(`stale attempt for task ${task.id}: expected the current attempt_id; stop work and request fresh assignment`);
|
|
1516
1519
|
}
|
|
1517
1520
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-t-expert",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "T Expert — a 279-expert, 18-division roster with full Chinese translations, as a standalone DeepSeek Harness plugin, with a built-in multi-agent team engine (T Team).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,10 @@
|
|
|
45
45
|
"cordis.patch.yml",
|
|
46
46
|
"README.md",
|
|
47
47
|
"LICENSE",
|
|
48
|
-
"THIRD-PARTY-NOTICES"
|
|
48
|
+
"THIRD-PARTY-NOTICES",
|
|
49
|
+
"vendor/dsh-agent-teams/LICENSE",
|
|
50
|
+
"vendor/dsh-agent-teams/VENDOR.md",
|
|
51
|
+
"vendor/third-party-licenses"
|
|
49
52
|
],
|
|
50
53
|
"dsh": {
|
|
51
54
|
"bundle": {
|
|
@@ -53,7 +56,6 @@
|
|
|
53
56
|
},
|
|
54
57
|
"client": {
|
|
55
58
|
"inject": [
|
|
56
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
57
59
|
"@deepseek-ai/dsh-client-connection",
|
|
58
60
|
"@deepseek-ai/dsh-client-locale",
|
|
59
61
|
"@deepseek-ai/dsh-api-remotes",
|
|
@@ -65,11 +67,11 @@
|
|
|
65
67
|
}
|
|
66
68
|
},
|
|
67
69
|
"scripts": {
|
|
68
|
-
"build": "node
|
|
69
|
-
"verify": "node
|
|
70
|
-
"brand": "node
|
|
71
|
-
"sync-data": "node
|
|
72
|
-
"prepublishOnly": "npm run build && npm run verify && node
|
|
70
|
+
"build": "node ../brand.mjs && node ../build-client.mjs",
|
|
71
|
+
"verify": "node ../verify.mjs",
|
|
72
|
+
"brand": "node ../brand.mjs",
|
|
73
|
+
"sync-data": "node ../sync-data.mjs",
|
|
74
|
+
"prepublishOnly": "npm run build && npm run verify && node ../sync-data.mjs --check"
|
|
73
75
|
},
|
|
74
76
|
"peerDependencies": {
|
|
75
77
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
@@ -85,7 +87,6 @@
|
|
|
85
87
|
"@deepseek-ai/dsh-llm": "0.1.5-rc.1 || 0.1.5-rc.2",
|
|
86
88
|
"@deepseek-ai/dsh-session": "0.1.5-rc.1 || 0.1.5-rc.2",
|
|
87
89
|
"@deepseek-ai/dsh-settings": "0.1.5-rc.1 || 0.1.5-rc.2",
|
|
88
|
-
"@deepseek-ai/dsh-skill": "0.1.5-rc.1 || 0.1.5-rc.2",
|
|
89
90
|
"@deepseek-ai/dsh-subagent": "0.1.5-rc.1 || 0.1.5-rc.2",
|
|
90
91
|
"@deepseek-ai/dsh-system-prompt": "0.1.5-rc.1 || 0.1.5-rc.2",
|
|
91
92
|
"@deepseek-ai/dsh-tools": "0.1.5-rc.1 || 0.1.5-rc.2",
|
|
@@ -133,9 +134,6 @@
|
|
|
133
134
|
"@deepseek-ai/dsh-settings": {
|
|
134
135
|
"optional": true
|
|
135
136
|
},
|
|
136
|
-
"@deepseek-ai/dsh-skill": {
|
|
137
|
-
"optional": true
|
|
138
|
-
},
|
|
139
137
|
"@deepseek-ai/dsh-subagent": {
|
|
140
138
|
"optional": true
|
|
141
139
|
},
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 程序员阿江(Relakkes)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# vendor: @nanmicoder/dsh-agent-teams(T专家 内置团队引擎)
|
|
2
|
+
|
|
3
|
+
路线 A:把 AgentTeams 的团队引擎**原样**内置进 T专家,安装后不再需要单独安装 AgentTeams。
|
|
4
|
+
|
|
5
|
+
| 项 | 值 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| 上游仓库 | https://github.com/NanmiCoder/dsh-agent-teams |
|
|
8
|
+
| 上游版本 | `0.1.17-rc.1` |
|
|
9
|
+
| 仓库 commit | `18fba6211fc3aac305fc9bb1c8a7faaf7273137a` |
|
|
10
|
+
| 同步来源目录 | `~/.dsh/profiles/desktop/node_modules/@nanmicoder/dsh-agent-teams` |
|
|
11
|
+
| 同步时间 | 2026-09-11 18:31:14 +0800 |
|
|
12
|
+
| 许可 | MIT(见同目录 LICENSE;本目录文件保持逐字节原样) |
|
|
13
|
+
|
|
14
|
+
## 同步进来的文件
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
lib/teams/capabilities.js 4943 bytes
|
|
18
|
+
lib/teams/command.js 8378 bytes
|
|
19
|
+
lib/teams/event-types.js 569 bytes
|
|
20
|
+
lib/teams/events.js 2827 bytes
|
|
21
|
+
lib/teams/harness-compat.js 8733 bytes
|
|
22
|
+
lib/teams/index.js 27236 bytes
|
|
23
|
+
lib/teams/members.js 33124 bytes
|
|
24
|
+
lib/teams/profiles.js 23215 bytes
|
|
25
|
+
lib/teams/quality-gates.js 37323 bytes
|
|
26
|
+
lib/teams/scheduler.js 24563 bytes
|
|
27
|
+
lib/teams/snapshot.js 7725 bytes
|
|
28
|
+
lib/teams/state.js 38289 bytes
|
|
29
|
+
lib/teams/tool-names.js 708 bytes
|
|
30
|
+
lib/teams/tools.js 127546 bytes
|
|
31
|
+
lib/teams/types.js 780 bytes
|
|
32
|
+
lib/teams/web-routes.js 3328 bytes
|
|
33
|
+
vendor/dsh-agent-teams/client.bundle.txt 197722 bytes
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## T专家 的改动点(都在我们的代码里,vendor 文件不动)
|
|
37
|
+
|
|
38
|
+
1. host:`lib/index.js` 里 `ctx.plugin(engineNamespace, engineConfig)` 内部挂载引擎,配置来自
|
|
39
|
+
`~/.t-team/t-team.config.json`(由 `team-profiles.py` 依据 `teams.json` 生成)。
|
|
40
|
+
2. client:`src/client.jsx` 在 factory 里临时截获 `window.__ModuleLoader__.load`,取出上游 client bundle
|
|
41
|
+
注册的 factory,用我们自己的 `require` 物化它,再调用它的 `apply(ctx)` —— 于是团队树/草案面板与
|
|
42
|
+
T专家 的设置页共用同一个客户端模块(同一个注册 id,DSH 只允许一个包注册一个 id)。
|
|
43
|
+
3. 工具名保持上游的 `agent_teams_*`,命令保持 `/agent-teams`;T专家 另加 `/t` 短命令与专家名册工具。
|
|
44
|
+
|
|
45
|
+
## 更新上游
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
bash ~/web/t-team/tz.sh 13 # 从已安装的包同步(先 dshmarket/pnpm 升到新版)
|
|
49
|
+
bash ~/web/t-team/tz.sh engine-sync --check # 只看差异
|
|
50
|
+
bash ~/web/t-team/tz.sh engine-sync --from DIR --clone ~/.t-team/vendor/dsh-agent-teams
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
同步后:`bash ~/web/t-team/tz.sh 10`(自检)→ `tz.sh 12`(构建)→ `tz.sh 8`(安装)→ 重启 DSH Desktop。
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# 第三方许可文本(随包分发)
|
|
2
|
+
|
|
3
|
+
本目录只放**随包分发的第三方内容**的完整许可证文本,以便再分发时履行 MIT 的
|
|
4
|
+
"保留版权声明与许可声明"义务。说明与署名见包根目录的 `THIRD-PARTY-NOTICES`。
|
|
5
|
+
|
|
6
|
+
| 文件 | 覆盖的随包内容 | 来源 |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| `agency-agents.LICENSE` | `data/experts/`(18 分区 / 279 位专家的逐字节镜像) | The Agency / AgentLand 名册快照,MIT |
|
|
9
|
+
| `agency-agents-zh.LICENSE` | `data/zh/` 中的中文名字、简介与人格正文 | `agency-agents-zh` 中文翻译与本地化资产,MIT |
|
|
10
|
+
|
|
11
|
+
两份文本都是从实际用于生成快照的来源包内**逐字节复制**的,未经改写:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
<安装的 @michengai/dsh-agency-agents>/assets/agency-agents/LICENSE
|
|
15
|
+
<安装的 @michengai/dsh-agency-agents>/assets/agency-agents-zh/LICENSE
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`@michengai/dsh-agency-agents` 自身的 TypeScript 源码与构建脚本是 Apache-2.0,
|
|
19
|
+
但**本插件不打包它的任何代码**,只用到了上面这两份 MIT 授权的资产,
|
|
20
|
+
因此这里只需分发这两份 MIT 文本。
|
|
21
|
+
|
|
22
|
+
内置团队引擎的许可证在 `../dsh-agent-teams/LICENSE`(同样随包分发)。
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Michael Sitarzewski (original English version)
|
|
4
|
+
Copyright (c) 2026 jnMetaCode (Chinese translation and localization)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AgentLand Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|