dsh-agent-toolkit 0.2.1 → 0.2.3

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/lib/index.d.ts CHANGED
@@ -47,6 +47,21 @@ interface BotsModuleConfig {
47
47
  errorDetailMaxChars: number;
48
48
  }
49
49
  //#endregion
50
+ //#region src/agents/team-preset.d.ts
51
+ /** 本功能的可调配置(Config schema 在 ../index.ts)。 */
52
+ interface AgentTeamPresetConfig {
53
+ /** 总开关:false 时启动不生成/刷新 preset。 */
54
+ enabled: boolean;
55
+ /** 生成的 preset id(即目录名)。 */
56
+ id: string;
57
+ /** 源 preset id,读其 composition 做派生。 */
58
+ source: string;
59
+ /** preset.yml 的显示名。 */
60
+ name: string;
61
+ /** preset.yml 的描述。 */
62
+ description: string;
63
+ }
64
+ //#endregion
50
65
  //#region src/index.d.ts
51
66
  declare const name = "dsh-agent-toolkit";
52
67
  declare const inject: string[];
@@ -62,6 +77,7 @@ interface Config {
62
77
  provider: string;
63
78
  toolName: string;
64
79
  feishu: BotsModuleConfig;
80
+ agentTeamPreset: AgentTeamPresetConfig;
65
81
  }
66
82
  /** layers/rules 的 schemastery schema 照归档 prompt-stack/src/index.ts:21-41 逐字段平移(含 overrides transform hack)。 */
67
83
  declare const Config: z<unknown, Config>;
package/lib/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { z as z$1 } from "zod";
3
3
  import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
4
- import { readFile, readdir } from "node:fs/promises";
5
- import { join } from "node:path";
4
+ import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
5
+ import { join, resolve } from "node:path";
6
6
  import yaml from "js-yaml";
7
- import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
7
+ import { expandHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
8
8
  import { defineTool } from "@deepseek-ai/dsh-tools";
9
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
9
10
  import { existsSync } from "node:fs";
10
11
  import { SessionId } from "@deepseek-ai/dsh-session";
11
12
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
12
13
  import * as lark from "@larksuiteoapi/node-sdk";
13
- import { createUserMessage } from "@deepseek-ai/dsh-llm";
14
14
  import { randomBytes, randomUUID } from "node:crypto";
15
15
  import { setupUsage } from "@dsh-agent-toolkit/token-usage";
16
16
  //#region src/agents/store.ts
@@ -1612,6 +1612,59 @@ function setupAgentsApi(ctx, deps) {
1612
1612
  });
1613
1613
  }
1614
1614
  //#endregion
1615
+ //#region src/agents/create-command.ts
1616
+ /** 拼装 /create-agent 的引导文本(模型面契约,文案为规格锁定内容)。 */
1617
+ function buildCreateAgentGuidance(input) {
1618
+ const lines = [
1619
+ "# 交互式创建 Agent 团队成员",
1620
+ "",
1621
+ "## 工作流(三步)",
1622
+ "1. 澄清需求:需求不明确时用 ask_user_question 向用户提问,整个流程提问总次数不超过 5 次,不重复问已确认的信息;",
1623
+ "2. 生成推荐并请用户确认:推荐 id / name / description / persona / tools 五个字段(id 是团队内唯一标识,name 是显示名,description 是职责一句话描述,persona 是系统提示词个性段,tools 是工具白名单);",
1624
+ "3. 迭代:用户有修改意见时按意见修订名称、描述、个性和工具后再次确认,直到用户明确确认。"
1625
+ ];
1626
+ if (input.requirement !== "") lines.push("", "## 用户初始需求", `用户已在命令中提供初始需求:「${input.requirement}」。请据此减少提问轮次,仅就不明确的点提问。`);
1627
+ lines.push("", "## 现有 Agent id(不可复用)", input.agentIds.join(", "), "id 规则:小写字母开头,仅含小写字母/数字/连字符([a-z0-9-]),最长 32 字符。", "", "## 可用工具清单", `原生工具:${NATIVE_TOOL_NAMES.join(", ")}`, `全局工具:${input.globalTools.join(", ")}`, "省略 tools 字段表示不限制(Agent 可使用全部工具)。一旦给出白名单,该 Agent 只有列出的工具可用:通常应保留原生工具,否则失去读文件/搜索/执行命令等基本能力(最终取舍按需求判断,如只读角色可去掉 write/edit)。");
1628
+ if (input.origin === void 0) lines.push("", "## 落库", "当前宿主无 web 服务,无法自动落库。用户确认推荐后,请把最终配置完整输出给用户,并提示其打开 Agents 面板按推荐内容手动创建。");
1629
+ else lines.push("", "## 落库(用户明确确认后执行)", "用你的 shell 工具调用 Agents 面板同一 HTTP 端点完成创建:", `1. 先 GET ${input.origin}/dsh-agent-toolkit/api/agents 复核所选 id 仍未被占用;`, `2. 再 PUT ${input.origin}/dsh-agent-toolkit/api/agents/<id>,请求体为 JSON(不要在 body 中携带 id 或 builtin 字段):`, " {\"name\":\"...\",\"description\":\"...\",\"persona\":\"...\",\"tools\":{\"allow\":[\"...\"]}}", " (description/persona/tools 均可省略;省略 tools 表示不限制)", "3. curl 示例(Windows 的 pwsh 里用 curl.exe):", ` curl.exe -s -X PUT "${input.origin}/dsh-agent-toolkit/api/agents/<id>" -H "Content-Type: application/json" -d "{\\"name\\":\\"...\\"}"`, `4. 返回 200 后必须再 GET ${input.origin}/dsh-agent-toolkit/api/agents,在返回列表中找到该 id 的记录,把它的 name/description/persona/tools 关键字段展示给用户,作为落库证据;`, "5. 落库成功后告知用户可在 Agents 面板查看、并可被 team_delegate 委派;任一步返回 4xx 则把错误信息展示给用户,修正后重试。");
1630
+ return lines.join("\n");
1631
+ }
1632
+ /**
1633
+ * 注册 /create-agent。webServer 为可选服务按仓库规则经 ctx.get 读取(不进 inject),
1634
+ * 缺席(headless/CLI)时 origin 为 undefined,引导文本落库节降级为手动创建指引。
1635
+ *
1636
+ * 命令结果(command/done)是 log-only、不进模型:引导文本必须经 agent.followup
1637
+ * 投递为 user 消息驱动主 Agent(与 channels/inbound.ts 同一机制),命令卡只回执短文案。
1638
+ */
1639
+ function setupCreateAgentCommand(ctx, deps) {
1640
+ ctx.commands.register({
1641
+ name: "create-agent",
1642
+ description: "交互式创建 Agent 团队成员:访谈澄清需求 → 推荐配置 → 确认后经面板 API 落库",
1643
+ input: { hint: "初始需求描述,可空" },
1644
+ handler: ({ rawInput, agent }) => {
1645
+ const webServer = ctx.get("webServer");
1646
+ const origin = webServer === void 0 ? void 0 : `http://127.0.0.1:${webServer.port}`;
1647
+ const text = buildCreateAgentGuidance({
1648
+ requirement: rawInput.trim(),
1649
+ agentIds: deps.registry.list().map((agent) => agent.id),
1650
+ globalTools: deps.listTools(),
1651
+ origin
1652
+ });
1653
+ agent.followup(createUserMessage({
1654
+ content: [{
1655
+ type: "text",
1656
+ text
1657
+ }],
1658
+ source: { kind: "user" }
1659
+ }));
1660
+ return {
1661
+ kind: "success",
1662
+ text: "已向主 Agent 发出创建 Agent 引导,请继续对话完成访谈与确认。"
1663
+ };
1664
+ }
1665
+ });
1666
+ }
1667
+ //#endregion
1615
1668
  //#region src/channels/agent-setup.ts
1616
1669
  /** bot 会话角色 persona 的 scoped 段名:与全局 persona 层同名,scoped 注册即 shadow 覆盖主 Agent persona。 */
1617
1670
  const TOOLKIT_PERSONA_SECTION = "prompt-stack:persona";
@@ -2213,20 +2266,23 @@ const feishuChannel = {
2213
2266
  appSecret: bot.secret
2214
2267
  }));
2215
2268
  const dedup = new MessageDedup();
2216
- const dispatcher = new lark.EventDispatcher({}).register({ "im.message.receive_v1": async (data) => {
2217
- const parsed = parseMessageEvent(data);
2218
- if (parsed === null || !dedup.check(parsed.messageId)) return;
2219
- const reply = new FeishuReplyHandle(api, parsed.chatId, tunables, log);
2220
- io.onMessage({
2221
- botId: bot.record.id,
2222
- chatId: parsed.chatId,
2223
- userId: parsed.userId,
2224
- messageId: parsed.messageId,
2225
- text: parsed.text,
2226
- reply,
2227
- ackProcessing: makeAck(api, parsed.messageId, tunables.processingReactionEmoji)
2228
- });
2229
- } });
2269
+ const dispatcher = new lark.EventDispatcher({}).register({
2270
+ "im.message.message_read_v1": async () => void 0,
2271
+ "im.message.receive_v1": async (data) => {
2272
+ const parsed = parseMessageEvent(data);
2273
+ if (parsed === null || !dedup.check(parsed.messageId)) return;
2274
+ const reply = new FeishuReplyHandle(api, parsed.chatId, tunables, log);
2275
+ io.onMessage({
2276
+ botId: bot.record.id,
2277
+ chatId: parsed.chatId,
2278
+ userId: parsed.userId,
2279
+ messageId: parsed.messageId,
2280
+ text: parsed.text,
2281
+ reply,
2282
+ ackProcessing: makeAck(api, parsed.messageId, tunables.processingReactionEmoji)
2283
+ });
2284
+ }
2285
+ });
2230
2286
  const ws = new lark.WSClient({
2231
2287
  appId,
2232
2288
  appSecret: bot.secret,
@@ -2289,66 +2345,6 @@ const projectBotDomain = defineDomain({
2289
2345
  function bindingKey(botId, chatId) {
2290
2346
  return `${botId}:${chatId}`;
2291
2347
  }
2292
- //#endregion
2293
- //#region src/channels/inbound.ts
2294
- /** 入站:指令分流 → 路由 → 单 in-flight 准入 → 表情回复 → followup 投递。 */
2295
- var Inbound = class {
2296
- deps;
2297
- constructor(deps) {
2298
- this.deps = deps;
2299
- }
2300
- onMessage(msg) {
2301
- this.handle(msg).catch(async (error) => {
2302
- this.deps.onError(`[project-bot] 入站处理失败:${error instanceof Error ? error.message : String(error)}`);
2303
- await msg.reply.notice("处理失败,请稍后再试").catch(() => void 0);
2304
- });
2305
- }
2306
- async handle(msg) {
2307
- const bot = this.deps.bots.get(msg.botId);
2308
- if (bot === void 0) return;
2309
- const directive = parseDirective(msg.text);
2310
- if (directive === "new") {
2311
- await this.deps.router.reset(bot, msg.chatId, msg.reply);
2312
- await msg.reply.notice("已开启新会话");
2313
- return;
2314
- }
2315
- if (directive === "stop") {
2316
- const rt = this.deps.router.lookup(bot.id, msg.chatId);
2317
- if (rt?.inflight !== void 0) {
2318
- rt.agent.cancel();
2319
- await msg.reply.notice("已请求停止当前任务");
2320
- } else await msg.reply.notice("当前没有进行中的任务");
2321
- return;
2322
- }
2323
- if (directive === "status") {
2324
- const rt = this.deps.router.lookup(bot.id, msg.chatId);
2325
- await msg.reply.notice(rt === void 0 ? `项目:${bot.project}\n会话:未创建(发送消息即创建)` : `项目:${bot.project}\n会话:${rt.sessionId}\n状态:${rt.inflight !== void 0 ? "处理中" : "空闲"}`);
2326
- return;
2327
- }
2328
- const rt = await this.deps.router.ensure(bot, msg.chatId, msg.reply);
2329
- if (rt.inflight !== void 0) {
2330
- await msg.reply.notice("上一条还在处理中,请稍候(或发送 /stop 取消)");
2331
- return;
2332
- }
2333
- rt.inflight = { ack: void 0 };
2334
- rt.inflight.ack = await msg.ackProcessing().catch(() => void 0) ?? void 0;
2335
- const message = createUserMessage({
2336
- content: [{
2337
- type: "text",
2338
- text: msg.text
2339
- }],
2340
- source: { kind: "user" }
2341
- });
2342
- try {
2343
- rt.agent.followup(message);
2344
- } catch (error) {
2345
- const ack = rt.inflight.ack;
2346
- rt.inflight = void 0;
2347
- await ack?.();
2348
- throw error;
2349
- }
2350
- }
2351
- };
2352
2348
  /** 向段序列追加内容:与尾段同类则合并,异类开新段。 */
2353
2349
  function appendToSegments(segments, kind, text) {
2354
2350
  const tail = segments[segments.length - 1];
@@ -2466,6 +2462,67 @@ var Outbound = class {
2466
2462
  }
2467
2463
  };
2468
2464
  //#endregion
2465
+ //#region src/channels/inbound.ts
2466
+ /** 入站:指令分流 → 路由 → 单 in-flight 准入 → 表情回复 → followup 投递。 */
2467
+ var Inbound = class {
2468
+ deps;
2469
+ constructor(deps) {
2470
+ this.deps = deps;
2471
+ }
2472
+ onMessage(msg) {
2473
+ this.handle(msg).catch(async (error) => {
2474
+ const detail = truncateDetail(error instanceof Error ? error.message : String(error), this.deps.maxErrorDetailChars);
2475
+ this.deps.onError(`[project-bot] 入站处理失败:${detail}`);
2476
+ await msg.reply.notice(`处理失败:${detail}`).catch(() => void 0);
2477
+ });
2478
+ }
2479
+ async handle(msg) {
2480
+ const bot = this.deps.bots.get(msg.botId);
2481
+ if (bot === void 0) return;
2482
+ const directive = parseDirective(msg.text);
2483
+ if (directive === "new") {
2484
+ await this.deps.router.reset(bot, msg.chatId, msg.reply);
2485
+ await msg.reply.notice("已开启新会话");
2486
+ return;
2487
+ }
2488
+ if (directive === "stop") {
2489
+ const rt = this.deps.router.lookup(bot.id, msg.chatId);
2490
+ if (rt?.inflight !== void 0) {
2491
+ rt.agent.cancel();
2492
+ await msg.reply.notice("已请求停止当前任务");
2493
+ } else await msg.reply.notice("当前没有进行中的任务");
2494
+ return;
2495
+ }
2496
+ if (directive === "status") {
2497
+ const rt = this.deps.router.lookup(bot.id, msg.chatId);
2498
+ await msg.reply.notice(rt === void 0 ? `项目:${bot.project}\n会话:未创建(发送消息即创建)` : `项目:${bot.project}\n会话:${rt.sessionId}\n状态:${rt.inflight !== void 0 ? "处理中" : "空闲"}`);
2499
+ return;
2500
+ }
2501
+ const rt = await this.deps.router.ensure(bot, msg.chatId, msg.reply);
2502
+ if (rt.inflight !== void 0) {
2503
+ await msg.reply.notice("上一条还在处理中,请稍候(或发送 /stop 取消)");
2504
+ return;
2505
+ }
2506
+ rt.inflight = { ack: void 0 };
2507
+ rt.inflight.ack = await msg.ackProcessing().catch(() => void 0) ?? void 0;
2508
+ const message = createUserMessage({
2509
+ content: [{
2510
+ type: "text",
2511
+ text: msg.text
2512
+ }],
2513
+ source: { kind: "user" }
2514
+ });
2515
+ try {
2516
+ rt.agent.followup(message);
2517
+ } catch (error) {
2518
+ const ack = rt.inflight.ack;
2519
+ rt.inflight = void 0;
2520
+ await ack?.();
2521
+ throw error;
2522
+ }
2523
+ }
2524
+ };
2525
+ //#endregion
2469
2526
  //#region src/channels/ports.ts
2470
2527
  /** 从 bot 记录提取创作期注入(主 Agent 绑定形态)。 */
2471
2528
  function hooksOf(bot) {
@@ -2606,6 +2663,7 @@ var BotRuntime = class {
2606
2663
  this.inbound = new Inbound({
2607
2664
  router: this.router,
2608
2665
  bots: deps.bots,
2666
+ maxErrorDetailChars: deps.maxErrorDetailChars,
2609
2667
  onError: (m) => deps.log.warn(m)
2610
2668
  });
2611
2669
  this.outbound = new Outbound(this.sessions, (m) => deps.log.warn(m), deps.maxErrorDetailChars);
@@ -2940,6 +2998,18 @@ function createApiHandler(deps) {
2940
2998
  //#endregion
2941
2999
  //#region src/bots/register-app.ts
2942
3000
  /** 扫码一键创建飞书应用:lark.registerApp(OAuth 2.0 Device Authorization Grant)的状态机封装。 */
3001
+ /** 扫码创建应用时申请的权限/事件(流式卡片 + 收发消息 + 表情 + 通讯录基础信息)。
3002
+ * 故意不加 as const:readonly 元组不可赋值给 SDK AppAddons 的 mutable string[],
3003
+ * 否则 bots/index.ts 的 lark.registerApp(options) 透传会 typecheck 失败。 */
3004
+ const FEISHU_REGISTER_APP_ADDONS = {
3005
+ scopes: { tenant: [
3006
+ "im:message",
3007
+ "im:message:send_as_bot",
3008
+ "cardkit:card:write",
3009
+ "contact:user.base:readonly"
3010
+ ] },
3011
+ events: { items: { tenant: ["im.message.receive_v1"] } }
3012
+ };
2943
3013
  var RegisterAppService = class {
2944
3014
  deps;
2945
3015
  sessions = /* @__PURE__ */ new Map();
@@ -2960,6 +3030,7 @@ var RegisterAppService = class {
2960
3030
  this.sessions.set(id, entry);
2961
3031
  this.deps.registerApp({
2962
3032
  signal: controller.signal,
3033
+ addons: FEISHU_REGISTER_APP_ADDONS,
2963
3034
  onQRCodeReady: (info) => {
2964
3035
  entry.state = {
2965
3036
  status: "pending",
@@ -3141,6 +3212,125 @@ function setupBots(ctx, config, deps) {
3141
3212
  });
3142
3213
  }
3143
3214
  //#endregion
3215
+ //#region src/agents/team-preset.ts
3216
+ /**
3217
+ * Agent 团队 preset 自动生成:派生宿主当前 shipped standard composition,
3218
+ * 文本级禁用 subagent 工具族 4 个行,写入首个 trust=user 的 preset root。
3219
+ * 设计:docs/superpowers/specs/2026-09-02-agent-team-preset-design.md
3220
+ */
3221
+ /** 禁用目标行:覆盖与 team_delegate 竞争/配套的 5 个模型可见工具所属的 4 个行。 */
3222
+ const TEAM_PRESET_DISABLED_ROWS = [
3223
+ "tool-subagent",
3224
+ "tool-subagent-fork",
3225
+ "tool-subagent-control",
3226
+ "tool-subagent-list-agents"
3227
+ ];
3228
+ /**
3229
+ * 文本级锚点改写:对 4 个目标行各插入一行 `disabled: true`(缩进 = 锚点缩进 + 2 空格)。
3230
+ * 锚点是整行精确匹配 `- id: <行id>`(忽略首尾空白),防 tool-subagent 误中
3231
+ * tool-subagent-fork / tool-subagent-control 前缀;锚点所属块内(锚点行之后、
3232
+ * 首个缩进 <= 锚点缩进的非空行之前)已有 `disabled:` 键则跳过——既幂等,也避免
3233
+ * 与宿主已有的 `disabled: !!js ...` 撞出 YAML 重复键。锚点缺失 warn 并跳过该锚点,
3234
+ * 其余照常。除插入行外文本逐字节不变。
3235
+ */
3236
+ function disableSubagentRows(source, warn) {
3237
+ const lines = source.split("\n");
3238
+ for (const row of TEAM_PRESET_DISABLED_ROWS) {
3239
+ const anchor = `- id: ${row}`;
3240
+ const index = lines.findIndex((line) => line.trim() === anchor);
3241
+ if (index === -1) {
3242
+ warn(`dsh-agent-toolkit: agent-team preset 锚点行 "${anchor}" 在源 composition 中缺失,已跳过`);
3243
+ continue;
3244
+ }
3245
+ const indent = lines[index].length - lines[index].trimStart().length;
3246
+ let hasDisabled = false;
3247
+ for (let i = index + 1; i < lines.length; i += 1) {
3248
+ const line = lines[i];
3249
+ if (line.trim() === "") continue;
3250
+ if (line.length - line.trimStart().length <= indent) break;
3251
+ if (/^\s*disabled\s*:/.test(line)) {
3252
+ hasDisabled = true;
3253
+ break;
3254
+ }
3255
+ }
3256
+ if (hasDisabled) continue;
3257
+ lines.splice(index + 1, 0, `${" ".repeat(indent + 2)}disabled: true`);
3258
+ }
3259
+ return lines.join("\n");
3260
+ }
3261
+ const COMPOSITION_FILE = "agent.cordis.yml";
3262
+ const METADATA_FILE = "preset.yml";
3263
+ /**
3264
+ * 生成目录的归属标记:无此标记的同名目录视为用户手工 preset,不覆盖。
3265
+ * 标记必须在 mkdir 后最先写入(marker-first):这样写 composition/metadata 失败留下的是
3266
+ * 带标记的半成品目录,下次启动会被正常重写自愈;若最后才写标记,则半成品目录无标记、
3267
+ * 会被误判为用户手工 preset 而永久卡死不覆盖。
3268
+ */
3269
+ const MARKER_FILE = ".generated-by";
3270
+ const MARKER_CONTENT = "dsh-agent-toolkit";
3271
+ /** 镜像宿主 PRESET_ID:preset id 即目录名,正则白名单是路径逃逸的 containment 边界。 */
3272
+ const PRESET_ID = /^[a-z0-9][a-z0-9-]*$/;
3273
+ const GENERATED_HEADER = "# 本文件由 dsh-agent-toolkit 自动生成,勿手改(每次启动重写)。\n";
3274
+ /**
3275
+ * 启动时生成/刷新 agent-team preset。所有失败路径 warn 降级,不影响插件其余功能。
3276
+ * 不设为默认 preset、卸载不删目录(可能有会话在用;composition 不引用 toolkit 行,
3277
+ * 残留 preset 自身仍可用)。每次启动重写:standing mount 按文件代际,重写只影响新会话。
3278
+ */
3279
+ async function setupAgentTeamPreset(ctx, config) {
3280
+ if (!config.enabled) return;
3281
+ const warn = (msg) => {
3282
+ ctx.logger.warn(msg);
3283
+ };
3284
+ const agentPresets = ctx.get("agentPresets", false);
3285
+ if (agentPresets === void 0) return;
3286
+ if (!PRESET_ID.test(config.id)) {
3287
+ warn(`dsh-agent-toolkit: agentTeamPreset.id "${config.id}" 不是合法 preset id,跳过 agent-team 生成`);
3288
+ return;
3289
+ }
3290
+ let source;
3291
+ try {
3292
+ source = await agentPresets.read(config.source);
3293
+ } catch (error) {
3294
+ warn(`dsh-agent-toolkit: 读取源 preset "${config.source}" 失败,跳过 agent-team 生成:${error instanceof Error ? error.message : String(error)}`);
3295
+ return;
3296
+ }
3297
+ const root = agentPresets.roots.find((r) => r.trust === "user");
3298
+ if (root === void 0) {
3299
+ warn("dsh-agent-toolkit: preset roots 中无 trust=user 的目录,跳过 agent-team 生成");
3300
+ return;
3301
+ }
3302
+ const composition = GENERATED_HEADER + disableSubagentRows(source, warn);
3303
+ const dir = join(resolve(expandHomePath(root.path)), config.id);
3304
+ try {
3305
+ const markerPath = join(dir, MARKER_FILE);
3306
+ let dirExists = true;
3307
+ try {
3308
+ await access(dir);
3309
+ } catch {
3310
+ dirExists = false;
3311
+ }
3312
+ if (dirExists) {
3313
+ let marked = false;
3314
+ try {
3315
+ marked = (await readFile(markerPath, "utf8")).trim() === MARKER_CONTENT;
3316
+ } catch {}
3317
+ if (!marked) {
3318
+ warn(`dsh-agent-toolkit: ${dir} 已存在且非本插件生成,不覆盖,跳过 agent-team 生成`);
3319
+ return;
3320
+ }
3321
+ }
3322
+ await mkdir(dir, { recursive: true });
3323
+ await writeFile(markerPath, `${MARKER_CONTENT}\n`, "utf8");
3324
+ await writeFile(join(dir, COMPOSITION_FILE), composition, "utf8");
3325
+ await writeFile(join(dir, METADATA_FILE), yaml.dump({
3326
+ name: config.name,
3327
+ description: config.description
3328
+ }, { lineWidth: -1 }), "utf8");
3329
+ } catch (error) {
3330
+ warn(`dsh-agent-toolkit: 写入 agent-team preset 失败(${dir}):${error instanceof Error ? error.message : String(error)}`);
3331
+ }
3332
+ }
3333
+ //#endregion
3144
3334
  //#region src/index.ts
3145
3335
  const name = "dsh-agent-toolkit";
3146
3336
  const inject = [
@@ -3195,6 +3385,19 @@ const Config = z.object({
3195
3385
  registerAppTimeoutMs: 6e5,
3196
3386
  processingReactionEmoji: "OneSecond",
3197
3387
  errorDetailMaxChars: 500
3388
+ }),
3389
+ agentTeamPreset: z.object({
3390
+ enabled: z.boolean().default(true),
3391
+ id: z.string().default("agent-team"),
3392
+ source: z.string().default("standard"),
3393
+ name: z.string().default("Agent 团队"),
3394
+ description: z.string().default("Agent 团队模式:禁用原生 subagent 工具族,委派统一走 team_delegate 团队角色")
3395
+ }).default({
3396
+ enabled: true,
3397
+ id: "agent-team",
3398
+ source: "standard",
3399
+ name: "Agent 团队",
3400
+ description: "Agent 团队模式:禁用原生 subagent 工具族,委派统一走 team_delegate 团队角色"
3198
3401
  })
3199
3402
  });
3200
3403
  async function apply(ctx, config) {
@@ -3241,9 +3444,10 @@ async function apply(ctx, config) {
3241
3444
  active: activeRoutes,
3242
3445
  routes: routesTable
3243
3446
  });
3447
+ const listTools = () => ctx.tools.schemas().map((s) => s.name);
3244
3448
  setupAgentsApi(ctx, {
3245
3449
  registry,
3246
- listTools: () => ctx.tools.schemas().map((s) => s.name),
3450
+ listTools,
3247
3451
  listProviders: () => ctx.llm.listProviders().map(({ id, name }) => ({
3248
3452
  id,
3249
3453
  name
@@ -3253,6 +3457,10 @@ async function apply(ctx, config) {
3253
3457
  name
3254
3458
  })))
3255
3459
  });
3460
+ setupCreateAgentCommand(ctx, {
3461
+ registry,
3462
+ listTools
3463
+ });
3256
3464
  setupPromptLayersApi(ctx, {
3257
3465
  source: layerSource,
3258
3466
  rules: config.rules,
@@ -3271,6 +3479,7 @@ async function apply(ctx, config) {
3271
3479
  };
3272
3480
  }
3273
3481
  });
3482
+ await setupAgentTeamPreset(ctx, config.agentTeamPreset);
3274
3483
  if (config.modules.feishu) setupBots(ctx, config.feishu, { registry });
3275
3484
  if (config.modules.usage) setupUsage(ctx, { timezone: config.timezone }, name);
3276
3485
  }