dsh-plugin-t-expert 0.3.72 → 0.3.88
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/THIRD-PARTY-NOTICES +1 -1
- package/data/experts/engineering/engineering-deepseek-harness-project-expert.md +5 -5
- package/lib/client.js +704 -518
- package/lib/command.js +7 -0
- package/lib/index.js +2 -4
- package/lib/remote.js +33 -33
- package/lib/schedule.js +6 -3
- package/lib/skill.js +11 -12
- package/lib/teams/command.js +6 -2
- package/lib/teams/index.js +2 -2
- package/lib/teams/members.js +2 -2
- package/lib/teams/quality-gates.js +8 -2
- package/lib/teams/scheduler.js +207 -14
- package/lib/teams/tools.js +12 -0
- package/package.json +1 -3
- package/skills/dsh-harness-languages/SKILL.md +0 -175
- package/skills/dsh-harness-project/SKILL.md +0 -236
package/lib/command.js
CHANGED
|
@@ -133,6 +133,13 @@ function relayRouteDirective(input, list) {
|
|
|
133
133
|
`【T专家 · 未指定小队】用户敲的是:\`/t ${input}\`,其中没有能匹配到小队名的词。`,
|
|
134
134
|
`请把「${input}」当作目标,从下面的小队里挑最合适的一个,然后**直接调用 t_team_create 并带上 profile="<key>"** 建队(approval=\"required\" 先 staged,等用户在面板 review);`,
|
|
135
135
|
"不要反问用户该用哪个小队,也不要自己手搭成员——profile 已带好成员与人格。",
|
|
136
|
+
// profile 描述的是「这类目标通常涉及哪些角色」,不是「这次必须全员上场」。不写清这一条,
|
|
137
|
+
// 队长会把它读成「roster 不可动」,于是为了不让人闲着而给边缘角色硬造任务:实测一份单文件
|
|
138
|
+
// HTML 简历被派给了 5 个人 —— 后端架构师去写「信息架构」、数据库性能工程师去查「A4 分页」。
|
|
139
|
+
// 那次的队长思考里两处原话正是「有点牵强」「这有点为凑人头造工作」,但它仍然照派了。
|
|
140
|
+
"但 profile 是「编制清单」,不等于这次全员都得上场:挑定小队后先逐人自问「这次真的需要这个角色的独有产出吗」,用不上的在 staged 阶段用 t_team_edit_plan 的 remove_member 裁掉。",
|
|
141
|
+
"顺序必须是**先裁人、后建任务**——任务一旦引用了成员,再删人就得先改依赖引用。拿不准的一律保留,且至少留 2 人(一个产出者 + 一个独立验证者,否则独立验证无从谈起)。",
|
|
142
|
+
"无论裁没裁,都要在给用户的计划说明里写明留了谁、裁了谁、为什么。",
|
|
136
143
|
"若确实没有合适的小队,就用中文说明原因并列出可用小队。",
|
|
137
144
|
"",
|
|
138
145
|
list,
|
package/lib/index.js
CHANGED
|
@@ -1991,11 +1991,9 @@ export function apply(ctx, config) {
|
|
|
1991
1991
|
},
|
|
1992
1992
|
});
|
|
1993
1993
|
|
|
1994
|
-
// ---- 随包 skill
|
|
1995
|
-
//
|
|
1994
|
+
// ---- 随包 skill(运维入口)----
|
|
1995
|
+
// 它只对特定任务有用,所以不占常驻提示段,改成按需加载的 skill:
|
|
1996
1996
|
// · t-expert-manager 维护这个插件的人:名册 / 小队 / 装机 / 发布
|
|
1997
|
-
// · dsh-harness-project 在 deepseek-harness 检出里读写代码:架构、启动模型、扩展点、门禁
|
|
1998
|
-
// · dsh-harness-languages 同一个仓库里的各语言面规则与工具链
|
|
1999
1997
|
// 可选依赖:宿主没有 skill 注册表时静默跳过,不影响上面任何功能。
|
|
2000
1998
|
installBundledSkills(ctx, config);
|
|
2001
1999
|
}
|
package/lib/remote.js
CHANGED
|
@@ -58,7 +58,7 @@ function descriptor(method, parameters, typeSymbol, schema) {
|
|
|
58
58
|
method,
|
|
59
59
|
invocation: { kind: "direct" },
|
|
60
60
|
parameters,
|
|
61
|
-
result: { mode: "strict", typeSymbol, schema },
|
|
61
|
+
result: { mode: "strict", typeSymbol, schema, create: () => schema },
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -72,7 +72,7 @@ const DESCRIPTORS = [
|
|
|
72
72
|
name: "patch",
|
|
73
73
|
wire: "patch",
|
|
74
74
|
source: "json",
|
|
75
|
-
codec: { mode: "strict", typeSymbol: "TTeamUiPrefsPatch", schema: uiPrefsPatchSchema },
|
|
75
|
+
codec: { mode: "strict", typeSymbol: "TTeamUiPrefsPatch", schema: uiPrefsPatchSchema, create: () => uiPrefsPatchSchema },
|
|
76
76
|
},
|
|
77
77
|
], "TTeamUiPrefs", uiPrefsSchema),
|
|
78
78
|
// 定时事项:全部写操作都回传刷新后的整份快照,面板拿到就能直接重绘(与名册/小队同一口径)。
|
|
@@ -82,7 +82,7 @@ const DESCRIPTORS = [
|
|
|
82
82
|
name: "input",
|
|
83
83
|
wire: "input",
|
|
84
84
|
source: "json",
|
|
85
|
-
codec: { mode: "strict", typeSymbol: "TTeamScheduleInput", schema: scheduleInputSchema },
|
|
85
|
+
codec: { mode: "strict", typeSymbol: "TTeamScheduleInput", schema: scheduleInputSchema, create: () => scheduleInputSchema },
|
|
86
86
|
},
|
|
87
87
|
], "TTeamSchedule", scheduleSnapshotSchema),
|
|
88
88
|
descriptor("updateScheduleItem", [
|
|
@@ -90,13 +90,13 @@ const DESCRIPTORS = [
|
|
|
90
90
|
name: "id",
|
|
91
91
|
wire: "id",
|
|
92
92
|
source: "json",
|
|
93
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
93
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
name: "patch",
|
|
97
97
|
wire: "patch",
|
|
98
98
|
source: "json",
|
|
99
|
-
codec: { mode: "strict", typeSymbol: "TTeamSchedulePatch", schema: schedulePatchSchema },
|
|
99
|
+
codec: { mode: "strict", typeSymbol: "TTeamSchedulePatch", schema: schedulePatchSchema, create: () => schedulePatchSchema },
|
|
100
100
|
},
|
|
101
101
|
], "TTeamSchedule", scheduleSnapshotSchema),
|
|
102
102
|
descriptor("deleteScheduleItem", [
|
|
@@ -104,7 +104,7 @@ const DESCRIPTORS = [
|
|
|
104
104
|
name: "id",
|
|
105
105
|
wire: "id",
|
|
106
106
|
source: "json",
|
|
107
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
107
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
108
108
|
},
|
|
109
109
|
], "TTeamSchedule", scheduleSnapshotSchema),
|
|
110
110
|
descriptor("runScheduleItem", [
|
|
@@ -112,7 +112,7 @@ const DESCRIPTORS = [
|
|
|
112
112
|
name: "id",
|
|
113
113
|
wire: "id",
|
|
114
114
|
source: "json",
|
|
115
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
115
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
116
116
|
},
|
|
117
117
|
], "TTeamSchedule", scheduleSnapshotSchema),
|
|
118
118
|
descriptor("previewSchedule", [
|
|
@@ -120,7 +120,7 @@ const DESCRIPTORS = [
|
|
|
120
120
|
name: "cron",
|
|
121
121
|
wire: "cron",
|
|
122
122
|
source: "json",
|
|
123
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(120) },
|
|
123
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(120), create: () => z.string().min(1).max(120) },
|
|
124
124
|
},
|
|
125
125
|
], "TTeamSchedulePreview", schedulePreviewSchema),
|
|
126
126
|
descriptor("ensureScheduleWorkspace", [
|
|
@@ -128,7 +128,7 @@ const DESCRIPTORS = [
|
|
|
128
128
|
name: "path",
|
|
129
129
|
wire: "path",
|
|
130
130
|
source: "json",
|
|
131
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(1024) },
|
|
131
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(1024), create: () => z.string().max(1024) },
|
|
132
132
|
},
|
|
133
133
|
], "TTeamScheduleWorkspace", scheduleWorkspaceSchema),
|
|
134
134
|
descriptor("setEnabled", [
|
|
@@ -136,13 +136,13 @@ const DESCRIPTORS = [
|
|
|
136
136
|
name: "enabled",
|
|
137
137
|
wire: "enabled",
|
|
138
138
|
source: "json",
|
|
139
|
-
codec: { mode: "strict", typeSymbol: "string[]", schema: z.array(z.string()) },
|
|
139
|
+
codec: { mode: "strict", typeSymbol: "string[]", schema: z.array(z.string()), create: () => z.array(z.string()) },
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
name: "expectedRevision",
|
|
143
143
|
wire: "expectedRevision",
|
|
144
144
|
source: "json",
|
|
145
|
-
codec: { mode: "strict", typeSymbol: "number", schema: z.number().int().min(0) },
|
|
145
|
+
codec: { mode: "strict", typeSymbol: "number", schema: z.number().int().min(0), create: () => z.number().int().min(0) },
|
|
146
146
|
},
|
|
147
147
|
], "TTeamEnabledState", enabledStateSchema),
|
|
148
148
|
descriptor("getPrompt", [
|
|
@@ -150,13 +150,13 @@ const DESCRIPTORS = [
|
|
|
150
150
|
name: "slug",
|
|
151
151
|
wire: "slug",
|
|
152
152
|
source: "json",
|
|
153
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
153
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
154
154
|
},
|
|
155
155
|
{
|
|
156
156
|
name: "division",
|
|
157
157
|
wire: "division",
|
|
158
158
|
source: "json",
|
|
159
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(64) },
|
|
159
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(64), create: () => z.string().min(1).max(64) },
|
|
160
160
|
},
|
|
161
161
|
], "TTeamPrompt", promptSchema),
|
|
162
162
|
descriptor("getSquads", [], "TTeamSquads", squadsSnapshotSchema),
|
|
@@ -165,13 +165,13 @@ const DESCRIPTORS = [
|
|
|
165
165
|
name: "squads",
|
|
166
166
|
wire: "squads",
|
|
167
167
|
source: "json",
|
|
168
|
-
codec: { mode: "strict", typeSymbol: "TTeamSquadInput[]", schema: z.array(squadInputSchema).min(1).max(48) },
|
|
168
|
+
codec: { mode: "strict", typeSymbol: "TTeamSquadInput[]", schema: z.array(squadInputSchema).min(1).max(48), create: () => z.array(squadInputSchema).min(1).max(48) },
|
|
169
169
|
},
|
|
170
170
|
{
|
|
171
171
|
name: "expectedRevision",
|
|
172
172
|
wire: "expectedRevision",
|
|
173
173
|
source: "json",
|
|
174
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(64) },
|
|
174
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(64), create: () => z.string().max(64) },
|
|
175
175
|
},
|
|
176
176
|
], "TTeamSquads", squadsSnapshotSchema),
|
|
177
177
|
descriptor("startSquad", [
|
|
@@ -179,13 +179,13 @@ const DESCRIPTORS = [
|
|
|
179
179
|
name: "profileKey",
|
|
180
180
|
wire: "profileKey",
|
|
181
181
|
source: "json",
|
|
182
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(64) },
|
|
182
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(64), create: () => z.string().min(1).max(64) },
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
name: "sessionId",
|
|
186
186
|
wire: "sessionId",
|
|
187
187
|
source: "json",
|
|
188
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(128) },
|
|
188
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(128), create: () => z.string().max(128) },
|
|
189
189
|
},
|
|
190
190
|
], "TTeamStartResult", startResultSchema),
|
|
191
191
|
descriptor("getTeams", [
|
|
@@ -193,7 +193,7 @@ const DESCRIPTORS = [
|
|
|
193
193
|
name: "archived",
|
|
194
194
|
wire: "archived",
|
|
195
195
|
source: "json",
|
|
196
|
-
codec: { mode: "strict", typeSymbol: "boolean", schema: z.boolean() },
|
|
196
|
+
codec: { mode: "strict", typeSymbol: "boolean", schema: z.boolean(), create: () => z.boolean() },
|
|
197
197
|
},
|
|
198
198
|
], "TTeamTeams", teamsSnapshotSchema),
|
|
199
199
|
// teamId 是**团队名 slug 化**的产物,跨工作区会重名;workspacePath 让宿主精确定位到
|
|
@@ -207,14 +207,14 @@ const DESCRIPTORS = [
|
|
|
207
207
|
name: "teamId",
|
|
208
208
|
wire: "teamId",
|
|
209
209
|
source: "json",
|
|
210
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
210
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
213
|
name: "workspacePath",
|
|
214
214
|
wire: "workspacePath",
|
|
215
215
|
source: "json",
|
|
216
216
|
acceptsUndefined: true,
|
|
217
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(1024) },
|
|
217
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(1024), create: () => z.string().max(1024) },
|
|
218
218
|
},
|
|
219
219
|
], "TTeamStopResult", stopResultSchema),
|
|
220
220
|
// 彻底删除一支团队(运行中的与已归档的都收):设置页「运行中的团队」那一行的「删除」按钮。
|
|
@@ -223,14 +223,14 @@ const DESCRIPTORS = [
|
|
|
223
223
|
name: "teamId",
|
|
224
224
|
wire: "teamId",
|
|
225
225
|
source: "json",
|
|
226
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
226
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
227
227
|
},
|
|
228
228
|
{
|
|
229
229
|
name: "workspacePath",
|
|
230
230
|
wire: "workspacePath",
|
|
231
231
|
source: "json",
|
|
232
232
|
acceptsUndefined: true,
|
|
233
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(1024) },
|
|
233
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(1024), create: () => z.string().max(1024) },
|
|
234
234
|
},
|
|
235
235
|
], "TTeamDeleteResult", deleteTeamResultSchema),
|
|
236
236
|
// 自建专家的增删改:都回传刷新后的整份名册快照,面板拿到就能直接重绘。
|
|
@@ -239,7 +239,7 @@ const DESCRIPTORS = [
|
|
|
239
239
|
name: "expert",
|
|
240
240
|
wire: "expert",
|
|
241
241
|
source: "json",
|
|
242
|
-
codec: { mode: "strict", typeSymbol: "TTeamCustomExpertInput", schema: customExpertInputSchema },
|
|
242
|
+
codec: { mode: "strict", typeSymbol: "TTeamCustomExpertInput", schema: customExpertInputSchema, create: () => customExpertInputSchema },
|
|
243
243
|
},
|
|
244
244
|
], "TTeamCatalog", catalogSnapshotSchema),
|
|
245
245
|
descriptor("updateExpert", [
|
|
@@ -247,19 +247,19 @@ const DESCRIPTORS = [
|
|
|
247
247
|
name: "slug",
|
|
248
248
|
wire: "slug",
|
|
249
249
|
source: "json",
|
|
250
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
250
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
251
251
|
},
|
|
252
252
|
{
|
|
253
253
|
name: "expert",
|
|
254
254
|
wire: "expert",
|
|
255
255
|
source: "json",
|
|
256
|
-
codec: { mode: "strict", typeSymbol: "TTeamCustomExpertInput", schema: customExpertInputSchema },
|
|
256
|
+
codec: { mode: "strict", typeSymbol: "TTeamCustomExpertInput", schema: customExpertInputSchema, create: () => customExpertInputSchema },
|
|
257
257
|
},
|
|
258
258
|
{
|
|
259
259
|
name: "expectedHash",
|
|
260
260
|
wire: "expectedHash",
|
|
261
261
|
source: "json",
|
|
262
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(64) },
|
|
262
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(64), create: () => z.string().max(64) },
|
|
263
263
|
},
|
|
264
264
|
], "TTeamCatalog", catalogSnapshotSchema),
|
|
265
265
|
descriptor("deleteExpert", [
|
|
@@ -267,13 +267,13 @@ const DESCRIPTORS = [
|
|
|
267
267
|
name: "slug",
|
|
268
268
|
wire: "slug",
|
|
269
269
|
source: "json",
|
|
270
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
270
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
271
271
|
},
|
|
272
272
|
{
|
|
273
273
|
name: "expectedHash",
|
|
274
274
|
wire: "expectedHash",
|
|
275
275
|
source: "json",
|
|
276
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(64) },
|
|
276
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(64), create: () => z.string().max(64) },
|
|
277
277
|
},
|
|
278
278
|
], "TTeamCatalog", catalogSnapshotSchema),
|
|
279
279
|
// 分类管理:都回传刷新后的整份名册快照(面板拿到就能直接重绘)。
|
|
@@ -282,13 +282,13 @@ const DESCRIPTORS = [
|
|
|
282
282
|
name: "key",
|
|
283
283
|
wire: "key",
|
|
284
284
|
source: "json",
|
|
285
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
285
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
286
286
|
},
|
|
287
287
|
{
|
|
288
288
|
name: "label",
|
|
289
289
|
wire: "label",
|
|
290
290
|
source: "json",
|
|
291
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(40) },
|
|
291
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(40), create: () => z.string().max(40) },
|
|
292
292
|
},
|
|
293
293
|
], "TTeamCatalog", catalogSnapshotSchema),
|
|
294
294
|
descriptor("updateCategory", [
|
|
@@ -296,13 +296,13 @@ const DESCRIPTORS = [
|
|
|
296
296
|
name: "key",
|
|
297
297
|
wire: "key",
|
|
298
298
|
source: "json",
|
|
299
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
299
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
300
300
|
},
|
|
301
301
|
{
|
|
302
302
|
name: "label",
|
|
303
303
|
wire: "label",
|
|
304
304
|
source: "json",
|
|
305
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(40) },
|
|
305
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(40), create: () => z.string().max(40) },
|
|
306
306
|
},
|
|
307
307
|
], "TTeamCatalog", catalogSnapshotSchema),
|
|
308
308
|
descriptor("deleteCategory", [
|
|
@@ -310,7 +310,7 @@ const DESCRIPTORS = [
|
|
|
310
310
|
name: "key",
|
|
311
311
|
wire: "key",
|
|
312
312
|
source: "json",
|
|
313
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128) },
|
|
313
|
+
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
314
314
|
},
|
|
315
315
|
], "TTeamCatalog", catalogSnapshotSchema),
|
|
316
316
|
];
|
package/lib/schedule.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* 为什么自建而不是直接依赖 `@weibaohui/dsh-tasks`:用户明确要求两个插件可以同时安装、
|
|
6
6
|
* 同时可用。所以这里与它**零共享**——
|
|
7
7
|
* · 数据不落它的 `dsh_tasks` 存储域,而是 T专家 自己的数据目录(`<root 同级>/schedule.json`);
|
|
8
|
-
* · 服务名 `tTeamSchedule`、错误码前缀 `tTeam/schedule
|
|
9
|
-
*
|
|
8
|
+
* · 服务名 `tTeamSchedule`、错误码前缀 `tTeam/schedule-*` 全部独立,两个插件的任务互不可见、互不干扰。
|
|
9
|
+
* 提交给会话的提示词用 `source.kind: "user"`(聊天右侧才渲染用户气泡;`plugin` 源会被收成 inject)。
|
|
10
10
|
*
|
|
11
11
|
* 三件事在这里闭环:
|
|
12
12
|
* 1. 持久化:一个 JSON 文件,原子写(与 `teams.json` 同级、同风格);
|
|
@@ -284,11 +284,14 @@ export function createScheduleEngine(options) {
|
|
|
284
284
|
} catch (error) {
|
|
285
285
|
logger?.warn?.(`[t-team] 定时任务「${item.title}」的提示词改写失败,按原文提交:${error instanceof Error ? error.message : String(error)}`);
|
|
286
286
|
}
|
|
287
|
+
// 必须用 source.kind: "user":DSH 聊天把非 user 的 user/message 收成左侧
|
|
288
|
+
// inject/context(plugin 源只显示插件名,正文不进右侧气泡)。定时任务的指令
|
|
289
|
+
// 本身就是用户写的提示词,应当像手打那条一样出现在对话右侧。
|
|
287
290
|
handle.agent.followup({
|
|
288
291
|
id: randomUUID(),
|
|
289
292
|
role: "user",
|
|
290
293
|
content: [{ type: "text", text: promptText }],
|
|
291
|
-
source: { kind: "
|
|
294
|
+
source: { kind: "user" },
|
|
292
295
|
});
|
|
293
296
|
return withRun(item, { at: startedAt, ok: true, sessionId });
|
|
294
297
|
} catch (error) {
|
package/lib/skill.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
/**
|
|
3
|
-
* 随包发布的 skill:把「T专家
|
|
3
|
+
* 随包发布的 skill:把「T专家 运维」做成**随 npm 包分发**的 skill。
|
|
4
4
|
*
|
|
5
|
-
* 为什么是 skill 而不是再加一段 systemPrompt
|
|
6
|
-
*
|
|
5
|
+
* 为什么是 skill 而不是再加一段 systemPrompt:这些内容只在特定任务里需要(改名册/小队/装机/发布),
|
|
6
|
+
* 常驻提示段是纯浪费 token;skill 的 description 本来就是路由面。
|
|
7
7
|
*
|
|
8
8
|
* 形态:`<包根>/skills/<name>/SKILL.md` 是标准 skill 文件(任何 skill 扫描器都能直接发现),
|
|
9
9
|
* 这里额外把它们注册进宿主的 skill 注册表,这样 npm 安装(插件在 node_modules 里、没有运维台)也能用。
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* - `t-expert-manager`:本仓运维入口,正文带占位符,注册时按本机布局渲染。
|
|
13
|
-
* - `dsh-harness-project` / `dsh-harness-languages`:`dsh-project-expert` agent preset 里那两个
|
|
14
|
-
* 项目知识 skill 的**逐字节拷贝**(preset 是源,这里是随插件发布的副本),
|
|
15
|
-
* 所以插件用户在任何仓库里都能加载这套 harness 项目知识。
|
|
11
|
+
* 唯一的 skill 是 `t-expert-manager`:本仓运维入口,正文带占位符,注册时按本机布局渲染。
|
|
16
12
|
*
|
|
17
13
|
* 两条刻意的设计:
|
|
18
14
|
* 1. **可选依赖**:走 `ctx.inject(["skills"], …)`,而不是把 `skills` 加进插件的静态 `inject`。
|
|
@@ -33,9 +29,9 @@ export const SKILLS_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."
|
|
|
33
29
|
* 随包发布的 skill 清单(目录名 = 宿主路由用的 skill 名)。
|
|
34
30
|
*
|
|
35
31
|
* 顺序即注册顺序。缺文件的条目会被跳过并逐条告警,而不是让整段注册失败 ——
|
|
36
|
-
* 一个 skill
|
|
32
|
+
* 一个 skill 文件缺失不该带走其余(见 {@link LOCAL_ONLY_SKILL_NAMES} 的例外)。
|
|
37
33
|
*/
|
|
38
|
-
export const BUNDLED_SKILL_NAMES = ["t-expert-manager"
|
|
34
|
+
export const BUNDLED_SKILL_NAMES = ["t-expert-manager"];
|
|
39
35
|
|
|
40
36
|
/**
|
|
41
37
|
* **只在本机布局里存在**的 skill:不进 npm 包(见 `package.json` 的 `files`)。
|
|
@@ -204,7 +200,7 @@ export function buildBundledSkills(config = {}) {
|
|
|
204
200
|
/**
|
|
205
201
|
* 把随包 skill 注册进宿主。`skills` 服务缺席时什么都不做(可选依赖,见文件头注释)。
|
|
206
202
|
*
|
|
207
|
-
* 缺失条目**逐条 warn**(一个 skill
|
|
203
|
+
* 缺失条目**逐条 warn**(一个 skill 文件缺失不该带走其余,但也不能悄悄消失);
|
|
208
204
|
* 全军覆没时额外再给一条汇总 warn,保持既有的「都读不到」信号。
|
|
209
205
|
* @param ctx - 插件上下文。
|
|
210
206
|
* @param config - 插件配置。
|
|
@@ -217,7 +213,10 @@ export function installBundledSkills(ctx, config) {
|
|
|
217
213
|
ctx.logger?.warn?.(`[t-team] 随包 skill「${item.name}」未注册:${item.reason}(其余 skill 照常注册)`);
|
|
218
214
|
}
|
|
219
215
|
if (skills.length === 0) {
|
|
220
|
-
|
|
216
|
+
/* 没有 `missing` 就说明缺席的全是 LOCAL_ONLY:那是 npm 安装的预期状态,出声反而是噪音。 */
|
|
217
|
+
if (missing.length > 0) {
|
|
218
|
+
ctx.logger?.warn?.(`[t-team] 随包 skill 未注册:读不到 ${join(SKILLS_ROOT, "<name>", "SKILL.md")}`);
|
|
219
|
+
}
|
|
221
220
|
return 0;
|
|
222
221
|
}
|
|
223
222
|
ctx.inject(["skills"], (scoped) => {
|
package/lib/teams/command.js
CHANGED
|
@@ -60,10 +60,14 @@ export function buildActivationDirective(goal, profile, taskPlanning = 'seed') {
|
|
|
60
60
|
if (profile !== undefined) {
|
|
61
61
|
lines.push(`Use profile="${profile}" when creating a new team.`);
|
|
62
62
|
if (taskPlanning === 'captain') {
|
|
63
|
-
|
|
63
|
+
// 编制授权:profile 是「这类目标通常涉及哪些角色」,不是「这次必须全员上场」。
|
|
64
|
+
// 缺了这句,队长会把上面那句 "do not recreate members" 读成「roster 不可动」,转而给
|
|
65
|
+
// 边缘角色硬造任务;而下面那句原文(idle members can run in parallel)更是在明示
|
|
66
|
+
// 「给闲着的成员找活」—— 实测一份单文件 HTML 简历就是这样被派给 5 个人的。
|
|
67
|
+
lines.push('This profile supplies the roster and guardrails. After create, do not recreate members.', 'The roster is a staffing list, not a mandate to field everyone: drop any role whose distinct output this goal does not need (t_team_edit_plan remove_member while staged), and trim BEFORE creating tasks — once a task references a member, removing that member means fixing references first. When unsure, keep the role. Keep at least two members (a producer plus an independent verifier). State in the plan whom you kept, whom you trimmed, and why.', 'Derive the smallest useful task graph from the goal while the team is staged; do not ask the user whether to split, merge, serialize, or parallelize.', 'An idle member is not a reason to invent work: add a task only when the goal itself needs that output, and say why. When work genuinely is independent, make it a separate ready task so it can run in parallel instead of serializing it. Add dependencies only for genuine prerequisites and later synthesis.');
|
|
64
68
|
}
|
|
65
69
|
else {
|
|
66
|
-
lines.push('Do not recreate the same members or seed tasks manually.');
|
|
70
|
+
lines.push('Do not recreate the same members or seed tasks manually.', 'You may still drop a role this goal does not need (t_team_edit_plan remove_member while the team is staged); state in the plan whom you kept, whom you trimmed, and why.');
|
|
67
71
|
}
|
|
68
72
|
}
|
|
69
73
|
lines.push(goal === '' ? 'The goal was not given — ask the user what the team should accomplish.' : `Goal: ${goal}`);
|
package/lib/teams/index.js
CHANGED
|
@@ -92,10 +92,10 @@ export function usageSectionText(toolNames, profilesText = '') {
|
|
|
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
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
|
-
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.
|
|
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. A member report already states what changed, so do not call status again merely to confirm it; call it when you need the parts the report does not cover (the current graph, other members' state).
|
|
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.
|
|
98
|
-
8. When full quality mode is requested: requirements → implementation → verification → review → integration. Plan the entire DAG while staged, including implementation before requirements finishes and integration depending on review round 1. Failed review automatically adds repair + next review and rewires pending downstream gates. Do not recreate this loop, omit integration or depend on a failed task. Review acceptance judges the latest implementation. Do not put smoke-test scripts into task instructions.
|
|
98
|
+
8. When full quality mode is requested: requirements → implementation → verification → review → integration. Plan the entire DAG while staged, including implementation before requirements finishes and integration depending on review round 1. Failed review automatically adds repair + next review and rewires pending downstream gates. Do not recreate this loop, omit integration or depend on a failed task. The automatic repair task already carries the review findings and is already assigned to the implementer, so do NOT relay those findings through send_message: the implementer has them, and the relay only makes it stop and explain that the repair is already done. Review acceptance judges the latest implementation. Do not put smoke-test scripts into task instructions.
|
|
99
99
|
9. Halted means the user stopped work (including the captain turn). Resume only on a later explicit user request with a reason, via t_team_resume or create_task({resume:true,resumeReason}); creating tasks alone never resumes. Escalated means the review loop hit its limit, not a halt. Deployment requires explicit user confirmation.
|
|
100
100
|
10. Wait for all required tasks to be terminal and members idle/ready, present results, then delete/archive unless the user wants to continue. Never discard unfinished work without authorization.
|
|
101
101
|
Tools: ${toolNames}${profilesText === '' ? '' : `\n\n${profilesText}`}`;
|
package/lib/teams/members.js
CHANGED
|
@@ -447,8 +447,8 @@ Working rules:
|
|
|
447
447
|
- include a concise output in either case;
|
|
448
448
|
- a stale-attempt rejection means the captain reassigned or took over the task; stop touching that task and wait for new work.
|
|
449
449
|
claimed cannot jump to completed. Mark in_progress first, then completed or failed.
|
|
450
|
-
Include attempt_id on every update
|
|
451
|
-
4.
|
|
450
|
+
Include attempt_id on every update, then become idle.
|
|
451
|
+
4. Write the report exactly once. t_team_update_task IS your report: the captain and every downstream task read output/summary/tried from it. Then send_message (to=captain) a ONE-LINE notice ("<the task id> completed — result in the task output") — that line is what wakes the captain, it must not restate the report — and keep your closing turn message to ONE line as well, because it is delivered to the captain in full. A completion written out three times costs three times the context and says nothing new. Use a longer send_message only for what the task record cannot carry: a blocker, or a decision only the captain can make.
|
|
452
452
|
5. To ask a teammate something, use t_team_send_message with to=<teammate name>; the message lands in their mailbox and wakes them directly — teammates talk to each other without the captain in the loop. The same applies to the captain (to=captain).
|
|
453
453
|
6. After your turn becomes idle, the shared task scheduler may assign your next ready task automatically. Never claim a second task while you still own unfinished work.
|
|
454
454
|
7. If you already own an open attempt (claimed or in_progress) and receive mail, treat it as guidance for that same attempt_id unless the mail explicitly tells you to stop or fail. Do not claim a new task in that turn.
|
|
@@ -682,7 +682,7 @@ export function isCommandResult(value) {
|
|
|
682
682
|
// optional tool parameters as "" instead of omitting them (e.g. GPT-5.6
|
|
683
683
|
// sending reviewedTaskId:"" or profile:""), which would otherwise be written
|
|
684
684
|
// to team.json and then brick the whole team state on reload.
|
|
685
|
-
const BLANK_SENSITIVE_STRING_FIELDS = ['objective', 'reviewedTaskId', 'sourceTaskId'];
|
|
685
|
+
const BLANK_SENSITIVE_STRING_FIELDS = ['objective', 'reviewedTaskId', 'sourceTaskId', 'summary'];
|
|
686
686
|
const BLANK_SENSITIVE_STRING_LIST_FIELDS = [
|
|
687
687
|
'inScope',
|
|
688
688
|
'outOfScope',
|
|
@@ -693,6 +693,7 @@ const BLANK_SENSITIVE_STRING_LIST_FIELDS = [
|
|
|
693
693
|
'changedPaths',
|
|
694
694
|
'sourceFindingIds',
|
|
695
695
|
'coverageOf',
|
|
696
|
+
'tried',
|
|
696
697
|
];
|
|
697
698
|
/**
|
|
698
699
|
* Normalize blank optional task fields to omitted ("blank means absent").
|
|
@@ -735,10 +736,15 @@ export function hasValidQualityTaskFields(value) {
|
|
|
735
736
|
return false;
|
|
736
737
|
if (value['sourceTaskId'] !== undefined && !nonemptyString(value['sourceTaskId']))
|
|
737
738
|
return false;
|
|
739
|
+
// 交接契约(2026-09-20):`summary` 是给下游看的压缩结论,`tried` 是走过的死路。
|
|
740
|
+
// 只校验形状、**不限长度** —— 过长的摘要在渲染时截断即可,绝不能因为写多了就让一次
|
|
741
|
+
// 已完成的工作被判成非法状态(那会连带整份 team.json 在重载时失效)。
|
|
742
|
+
if (value['summary'] !== undefined && !nonemptyString(value['summary']))
|
|
743
|
+
return false;
|
|
738
744
|
if (value['reviewedAttempt'] !== undefined && !(Number.isSafeInteger(value['reviewedAttempt']) && value['reviewedAttempt'] >= 0)) {
|
|
739
745
|
return false;
|
|
740
746
|
}
|
|
741
|
-
const stringLists = ['inScope', 'outOfScope', 'acceptance', 'verify', 'deliverables', 'nonGoals', 'changedPaths', 'sourceFindingIds', 'coverageOf'];
|
|
747
|
+
const stringLists = ['inScope', 'outOfScope', 'acceptance', 'verify', 'deliverables', 'nonGoals', 'changedPaths', 'sourceFindingIds', 'coverageOf', 'tried'];
|
|
742
748
|
for (const key of stringLists) {
|
|
743
749
|
if (value[key] === undefined)
|
|
744
750
|
continue;
|