dsh-agent-toolkit 0.2.6 → 0.2.7
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/client.js +132 -93
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +91 -37
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ interface BotsModuleConfig {
|
|
|
45
45
|
processingReactionEmoji: string;
|
|
46
46
|
/** 回传飞书的错误摘要最大字符数。 */
|
|
47
47
|
errorDetailMaxChars: number;
|
|
48
|
+
/** 会话创建/恢复时注入「渠道 + 发起人 open_id」提示段(dsh-agent-toolkit:channel:sender)。 */
|
|
49
|
+
injectSender: boolean;
|
|
48
50
|
}
|
|
49
51
|
//#endregion
|
|
50
52
|
//#region src/agents/team-preset.d.ts
|
package/lib/index.js
CHANGED
|
@@ -2404,8 +2404,9 @@ const FeishuConfigSchema = z$1.object({
|
|
|
2404
2404
|
const BotRecordSchema = z$1.object({
|
|
2405
2405
|
id: z$1.string().regex(BOT_ID_RE),
|
|
2406
2406
|
name: z$1.string().min(1).max(64),
|
|
2407
|
-
|
|
2408
|
-
|
|
2407
|
+
/** 渠道类型;与 feishu 同有或同无(未绑定为双双缺省)。 */
|
|
2408
|
+
channel: z$1.literal("feishu").optional(),
|
|
2409
|
+
feishu: FeishuConfigSchema.optional(),
|
|
2409
2410
|
/** 绑定项目(agent 的 cwd,绝对路径)。一 bot 一项目。 */
|
|
2410
2411
|
project: z$1.string().min(1),
|
|
2411
2412
|
/** 透传到 agent 创作期的 persona 提示段。 */
|
|
@@ -2420,7 +2421,7 @@ const BotRecordSchema = z$1.object({
|
|
|
2420
2421
|
}).optional(),
|
|
2421
2422
|
createdAt: z$1.number().int().nonnegative(),
|
|
2422
2423
|
updatedAt: z$1.number().int().nonnegative()
|
|
2423
|
-
});
|
|
2424
|
+
}).refine((r) => r.channel === void 0 === (r.feishu === void 0), { message: "channel 与 feishu 必须同有或同无" });
|
|
2424
2425
|
const BindingSchema = z$1.object({ sessionId: z$1.string().min(1) });
|
|
2425
2426
|
/** domain 名受 UNIT_NAME_RE 约束(^[a-z][a-z0-9_]*$),不允许连字符。 */
|
|
2426
2427
|
const projectBotDomain = defineDomain({
|
|
@@ -2571,7 +2572,7 @@ var Inbound = class {
|
|
|
2571
2572
|
if (bot === void 0) return;
|
|
2572
2573
|
const directive = parseDirective(msg.text);
|
|
2573
2574
|
if (directive === "new") {
|
|
2574
|
-
await this.deps.router.reset(bot, msg.chatId, msg.reply);
|
|
2575
|
+
await this.deps.router.reset(bot, msg.chatId, msg.reply, msg.userId);
|
|
2575
2576
|
await msg.reply.notice("已开启新会话");
|
|
2576
2577
|
return;
|
|
2577
2578
|
}
|
|
@@ -2588,7 +2589,7 @@ var Inbound = class {
|
|
|
2588
2589
|
await msg.reply.notice(rt === void 0 ? `项目:${bot.project}\n会话:未创建(发送消息即创建)` : `项目:${bot.project}\n会话:${rt.sessionId}\n状态:${rt.inflight !== void 0 ? "处理中" : "空闲"}`);
|
|
2589
2590
|
return;
|
|
2590
2591
|
}
|
|
2591
|
-
const rt = await this.deps.router.ensure(bot, msg.chatId, msg.reply);
|
|
2592
|
+
const rt = await this.deps.router.ensure(bot, msg.chatId, msg.reply, msg.userId);
|
|
2592
2593
|
if (rt.inflight !== void 0) {
|
|
2593
2594
|
await msg.reply.notice("上一条还在处理中,请稍候(或发送 /stop 取消)");
|
|
2594
2595
|
return;
|
|
@@ -2636,6 +2637,12 @@ function hooksOf(bot) {
|
|
|
2636
2637
|
//#endregion
|
|
2637
2638
|
//#region src/channels/router.ts
|
|
2638
2639
|
/** 绑定路由:(botId, chatId) → 长期会话;create / resume / reset。 */
|
|
2640
|
+
/** 发起人提示段名:bot 会话声明来源渠道与发起人 open_id。 */
|
|
2641
|
+
const SENDER_SECTION_NAME = "dsh-agent-toolkit:channel:sender";
|
|
2642
|
+
/** sender 段文本(单聊语义;channel 取 BotRecord.channel,未来新渠道零改动透传)。 */
|
|
2643
|
+
function senderSectionText(channel, userId) {
|
|
2644
|
+
return `本会话由 ${channel} 渠道的单聊会话发起。发起人 ID(${channel} open_id):\`${userId}\`。`;
|
|
2645
|
+
}
|
|
2639
2646
|
var Router = class {
|
|
2640
2647
|
agents;
|
|
2641
2648
|
bindings;
|
|
@@ -2644,7 +2651,8 @@ var Router = class {
|
|
|
2644
2651
|
workspace;
|
|
2645
2652
|
onWarn;
|
|
2646
2653
|
registry;
|
|
2647
|
-
|
|
2654
|
+
injectSender;
|
|
2655
|
+
constructor(agents, bindings, sessions, defaultModel, workspace, onWarn, registry, injectSender = true) {
|
|
2648
2656
|
this.agents = agents;
|
|
2649
2657
|
this.bindings = bindings;
|
|
2650
2658
|
this.sessions = sessions;
|
|
@@ -2652,9 +2660,10 @@ var Router = class {
|
|
|
2652
2660
|
this.workspace = workspace;
|
|
2653
2661
|
this.onWarn = onWarn;
|
|
2654
2662
|
this.registry = registry;
|
|
2663
|
+
this.injectSender = injectSender;
|
|
2655
2664
|
}
|
|
2656
2665
|
/** 取(或建/恢复)该 chat 的会话 runtime;reply 刷新为最近一次入站携带的句柄。 */
|
|
2657
|
-
async ensure(bot, chatId, reply) {
|
|
2666
|
+
async ensure(bot, chatId, reply, userId) {
|
|
2658
2667
|
const bound = this.bindings.get(bot.id, chatId);
|
|
2659
2668
|
if (bound !== void 0) {
|
|
2660
2669
|
const existing = this.sessions.get(bound);
|
|
@@ -2664,7 +2673,7 @@ var Router = class {
|
|
|
2664
2673
|
}
|
|
2665
2674
|
const agent = await this.agents.resume({
|
|
2666
2675
|
sessionId: bound,
|
|
2667
|
-
...this.resolveSession(bot)
|
|
2676
|
+
...this.resolveSession(bot, userId)
|
|
2668
2677
|
});
|
|
2669
2678
|
await this.attach(bot.project, bound);
|
|
2670
2679
|
return this.adopt(bot.id, chatId, bound, agent, reply);
|
|
@@ -2673,7 +2682,7 @@ var Router = class {
|
|
|
2673
2682
|
const agent = await this.agents.create({
|
|
2674
2683
|
sessionId,
|
|
2675
2684
|
cwd: bot.project,
|
|
2676
|
-
...this.resolveSession(bot)
|
|
2685
|
+
...this.resolveSession(bot, userId)
|
|
2677
2686
|
});
|
|
2678
2687
|
await this.bindings.set(bot.id, chatId, sessionId);
|
|
2679
2688
|
await this.attach(bot.project, sessionId);
|
|
@@ -2691,20 +2700,33 @@ var Router = class {
|
|
|
2691
2700
|
resolveOptions(bot) {
|
|
2692
2701
|
return bot.agentOptions ?? this.defaultModel();
|
|
2693
2702
|
}
|
|
2703
|
+
/** injectSender 开启时向 hooks.sections 末尾追加 sender 段(主/角色形态通用)。 */
|
|
2704
|
+
withSenderSection(hooks, bot, userId) {
|
|
2705
|
+
if (!this.injectSender) return hooks;
|
|
2706
|
+
const section = {
|
|
2707
|
+
name: SENDER_SECTION_NAME,
|
|
2708
|
+
order: 20,
|
|
2709
|
+
text: senderSectionText(bot.channel ?? "unknown", userId)
|
|
2710
|
+
};
|
|
2711
|
+
return {
|
|
2712
|
+
...hooks,
|
|
2713
|
+
sections: [...hooks.sections ?? [], section]
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2694
2716
|
/**
|
|
2695
2717
|
* 按 bot.agentRef 解析会话组装(agentOptions + 创作期 hooks):
|
|
2696
2718
|
* - 缺省/指向 main → 主 Agent 形态:bot 自带 persona/tools + 默认模型回退;
|
|
2697
2719
|
* - 指向角色 → 角色形态:persona 单 section + tools.restrict + role.model;
|
|
2698
2720
|
* - 指向不存在角色 → warn 并降级为主 Agent 形态。
|
|
2699
2721
|
*/
|
|
2700
|
-
resolveSession(bot) {
|
|
2722
|
+
resolveSession(bot, userId) {
|
|
2701
2723
|
const ref = bot.agentRef ?? "main";
|
|
2702
2724
|
const role = this.registry.get(ref);
|
|
2703
2725
|
if (role === void 0 || ref === "main") {
|
|
2704
2726
|
if (role === void 0 && ref !== "main") this.onWarn(`[project-bot] bot "${bot.id}" 的 agentRef "${ref}" 不存在,降级绑定主 Agent`);
|
|
2705
2727
|
return {
|
|
2706
2728
|
agentOptions: this.resolveOptions(bot),
|
|
2707
|
-
hooks: hooksOf(bot)
|
|
2729
|
+
hooks: this.withSenderSection(hooksOf(bot), bot, userId)
|
|
2708
2730
|
};
|
|
2709
2731
|
}
|
|
2710
2732
|
const sections = role.persona === void 0 || role.persona.trim().length === 0 ? [] : [{
|
|
@@ -2714,21 +2736,21 @@ var Router = class {
|
|
|
2714
2736
|
}];
|
|
2715
2737
|
return {
|
|
2716
2738
|
agentOptions: role.model ?? this.resolveOptions(bot),
|
|
2717
|
-
hooks: {
|
|
2739
|
+
hooks: this.withSenderSection({
|
|
2718
2740
|
...sections.length > 0 ? { sections } : {},
|
|
2719
2741
|
...role.tools !== void 0 ? { tools: role.tools.allow } : {}
|
|
2720
|
-
}
|
|
2742
|
+
}, bot, userId)
|
|
2721
2743
|
};
|
|
2722
2744
|
}
|
|
2723
2745
|
/** /new:取消旧会话、清绑定、开新会话。 */
|
|
2724
|
-
async reset(bot, chatId, reply) {
|
|
2746
|
+
async reset(bot, chatId, reply, userId) {
|
|
2725
2747
|
const bound = this.bindings.get(bot.id, chatId);
|
|
2726
2748
|
if (bound !== void 0) {
|
|
2727
2749
|
this.sessions.get(bound)?.agent.cancel();
|
|
2728
2750
|
this.sessions.delete(bound);
|
|
2729
2751
|
await this.bindings.delete(bot.id, chatId);
|
|
2730
2752
|
}
|
|
2731
|
-
return this.ensure(bot, chatId, reply);
|
|
2753
|
+
return this.ensure(bot, chatId, reply, userId);
|
|
2732
2754
|
}
|
|
2733
2755
|
lookup(botId, chatId) {
|
|
2734
2756
|
const bound = this.bindings.get(botId, chatId);
|
|
@@ -2761,7 +2783,7 @@ var BotRuntime = class {
|
|
|
2761
2783
|
constructor(deps) {
|
|
2762
2784
|
this.deps = deps;
|
|
2763
2785
|
const bindingStore = this.bindingStore();
|
|
2764
|
-
this.router = new Router(deps.agents, bindingStore, this.sessions, deps.defaultModel, deps.workspace, (m) => deps.log.warn(m), deps.registry);
|
|
2786
|
+
this.router = new Router(deps.agents, bindingStore, this.sessions, deps.defaultModel, deps.workspace, (m) => deps.log.warn(m), deps.registry, deps.injectSender ?? true);
|
|
2765
2787
|
this.inbound = new Inbound({
|
|
2766
2788
|
router: this.router,
|
|
2767
2789
|
bots: deps.bots,
|
|
@@ -2774,11 +2796,12 @@ var BotRuntime = class {
|
|
|
2774
2796
|
async startAll() {
|
|
2775
2797
|
for (const botId of [...this.deps.bots.keys()]) await this.reconcile(botId);
|
|
2776
2798
|
}
|
|
2777
|
-
/** 按最新记录重建该 bot
|
|
2799
|
+
/** 按最新记录重建该 bot 的渠道(创建/更新后调用;记录已删或未绑定则纯停止)。 */
|
|
2778
2800
|
async reconcile(botId) {
|
|
2779
2801
|
await this.stopChannel(botId);
|
|
2780
2802
|
const record = this.deps.bots.get(botId);
|
|
2781
2803
|
if (record === void 0) return;
|
|
2804
|
+
if (record.feishu === void 0 || record.channel === void 0) return;
|
|
2782
2805
|
if (!this.deps.validateProject(record.project)) {
|
|
2783
2806
|
this.deps.log.warn(`[project-bot] bot "${botId}" 的项目路径不可用:${record.project}`);
|
|
2784
2807
|
return;
|
|
@@ -2812,7 +2835,17 @@ var BotRuntime = class {
|
|
|
2812
2835
|
}
|
|
2813
2836
|
await this.bindingStore().deleteBot(botId);
|
|
2814
2837
|
}
|
|
2838
|
+
/** 解绑渠道:停渠道、取消在飞会话;绑定表与持久会话保留(重绑后 resume 接续)。 */
|
|
2839
|
+
async unbindBot(botId) {
|
|
2840
|
+
await this.stopChannel(botId);
|
|
2841
|
+
for (const [sessionId, rt] of [...this.sessions]) if (rt.botId === botId) {
|
|
2842
|
+
rt.agent.cancel();
|
|
2843
|
+
this.sessions.delete(sessionId);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2815
2846
|
statusOf(botId) {
|
|
2847
|
+
const record = this.deps.bots.get(botId);
|
|
2848
|
+
if (record !== void 0 && record.feishu === void 0) return "unbound";
|
|
2816
2849
|
return this.handles.get(botId)?.status() ?? "not-running";
|
|
2817
2850
|
}
|
|
2818
2851
|
/** 卸载时序:取消在飞会话 → 等 idle → drain 出站链(卡片定格)→ 断全部渠道。 */
|
|
@@ -2920,7 +2953,7 @@ const CreateBodySchema = z$1.object({
|
|
|
2920
2953
|
/** 手动填写路径:明文密钥(立即入 credentials,不落表)。 */
|
|
2921
2954
|
appSecret: z$1.string().min(1).optional(),
|
|
2922
2955
|
/** 扫码路径:registerApp 已入库,直接给引用。 */
|
|
2923
|
-
appSecretRef: z$1.string().optional()
|
|
2956
|
+
appSecretRef: z$1.string().regex(CREDENTIAL_REF_RE).optional()
|
|
2924
2957
|
})
|
|
2925
2958
|
});
|
|
2926
2959
|
const UpdateBodySchema = z$1.object({
|
|
@@ -2933,11 +2966,12 @@ const UpdateBodySchema = z$1.object({
|
|
|
2933
2966
|
provider: z$1.string().min(1).optional(),
|
|
2934
2967
|
model: z$1.string().min(1).optional()
|
|
2935
2968
|
}).nullable().optional(),
|
|
2936
|
-
/**
|
|
2969
|
+
/** 重绑:明文新密钥(立即入 credentials)或扫码引用(已入库);null = 解绑渠道(删密钥、保留会话绑定)。 */
|
|
2937
2970
|
feishu: z$1.object({
|
|
2938
2971
|
appId: z$1.string().regex(FEISHU_APP_ID_RE),
|
|
2939
|
-
appSecret: z$1.string().min(1)
|
|
2940
|
-
|
|
2972
|
+
appSecret: z$1.string().min(1).optional(),
|
|
2973
|
+
appSecretRef: z$1.string().regex(CREDENTIAL_REF_RE).optional()
|
|
2974
|
+
}).refine((f) => f.appSecret !== void 0 || f.appSecretRef !== void 0, { message: "缺少 appSecret 或 appSecretRef" }).nullable().optional()
|
|
2941
2975
|
});
|
|
2942
2976
|
function json(res, code, body) {
|
|
2943
2977
|
res.writeHead(code, { "content-type": "application/json" }).end(JSON.stringify(body));
|
|
@@ -2999,7 +3033,7 @@ function createApiHandler(deps) {
|
|
|
2999
3033
|
json(res, 409, { error: `bot id "${id}" 已存在` });
|
|
3000
3034
|
return;
|
|
3001
3035
|
}
|
|
3002
|
-
for (const [, existing] of deps.bots.entries()) if (existing.feishu.appId === input.feishu.appId) {
|
|
3036
|
+
for (const [, existing] of deps.bots.entries()) if (existing.feishu !== void 0 && existing.feishu.appId === input.feishu.appId) {
|
|
3003
3037
|
json(res, 409, { error: `appId 已被 bot "${existing.id}" 使用` });
|
|
3004
3038
|
return;
|
|
3005
3039
|
}
|
|
@@ -3054,23 +3088,40 @@ function createApiHandler(deps) {
|
|
|
3054
3088
|
return;
|
|
3055
3089
|
}
|
|
3056
3090
|
const input = parsed.data;
|
|
3057
|
-
let feishu = existing.feishu;
|
|
3058
|
-
if (input.feishu !== void 0) feishu = {
|
|
3059
|
-
appId: input.feishu.appId,
|
|
3060
|
-
appSecretRef: await deps.storeSecret(id, input.feishu.appSecret)
|
|
3061
|
-
};
|
|
3062
3091
|
const project = input.project ?? existing.project;
|
|
3063
3092
|
if (!deps.validateProject(project)) {
|
|
3064
3093
|
json(res, 400, { error: `项目路径不可用:${project}` });
|
|
3065
3094
|
return;
|
|
3066
3095
|
}
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3096
|
+
if (input.feishu !== null && input.feishu !== void 0) {
|
|
3097
|
+
for (const [, other] of deps.bots.entries()) if (other.id !== id && other.feishu?.appId === input.feishu.appId) {
|
|
3098
|
+
json(res, 409, { error: `appId 已被 bot "${other.id}" 使用` });
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
let appSecretRef;
|
|
3103
|
+
if (input.feishu === null) {
|
|
3104
|
+
await deps.runtime.unbindBot(id);
|
|
3105
|
+
if (existing.feishu !== void 0) await deps.deleteSecret(existing.feishu.appSecretRef);
|
|
3106
|
+
} else if (input.feishu !== void 0) {
|
|
3107
|
+
if (input.feishu.appSecret !== void 0) appSecretRef = await deps.storeSecret(id, input.feishu.appSecret);
|
|
3108
|
+
else appSecretRef = input.feishu.appSecretRef;
|
|
3109
|
+
if (existing.feishu !== void 0 && existing.feishu.appSecretRef !== appSecretRef) await deps.deleteSecret(existing.feishu.appSecretRef);
|
|
3110
|
+
}
|
|
3111
|
+
const merged = { ...existing };
|
|
3112
|
+
if (input.name !== void 0) merged.name = input.name;
|
|
3113
|
+
merged.project = project;
|
|
3114
|
+
merged.updatedAt = deps.now();
|
|
3115
|
+
if (input.feishu === null) {
|
|
3116
|
+
delete merged.channel;
|
|
3117
|
+
delete merged.feishu;
|
|
3118
|
+
} else if (input.feishu !== void 0 && appSecretRef !== void 0) {
|
|
3119
|
+
merged.channel = "feishu";
|
|
3120
|
+
merged.feishu = {
|
|
3121
|
+
appId: input.feishu.appId,
|
|
3122
|
+
appSecretRef
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3074
3125
|
if (input.persona === null) delete merged.persona;
|
|
3075
3126
|
else if (input.persona !== void 0) merged.persona = input.persona;
|
|
3076
3127
|
if (input.agentRef === null) delete merged.agentRef;
|
|
@@ -3097,7 +3148,7 @@ function createApiHandler(deps) {
|
|
|
3097
3148
|
}
|
|
3098
3149
|
await deps.runtime.stopBot(id);
|
|
3099
3150
|
await deps.bots.delete(id);
|
|
3100
|
-
await deps.deleteSecret(existing.feishu.appSecretRef);
|
|
3151
|
+
if (existing.feishu !== void 0) await deps.deleteSecret(existing.feishu.appSecretRef);
|
|
3101
3152
|
json(res, 200, { ok: true });
|
|
3102
3153
|
return;
|
|
3103
3154
|
}
|
|
@@ -3321,6 +3372,7 @@ function setupBots(ctx, config, deps) {
|
|
|
3321
3372
|
tunables,
|
|
3322
3373
|
maxErrorDetailChars: config.errorDetailMaxChars,
|
|
3323
3374
|
attachments: attachmentsOf,
|
|
3375
|
+
injectSender: config.injectSender,
|
|
3324
3376
|
resolveSecret: async (ref) => (await ctx.credentials.resolve(credentialRef(ref)))?.value,
|
|
3325
3377
|
validateProject: (path) => existsSync(path),
|
|
3326
3378
|
log
|
|
@@ -3546,14 +3598,16 @@ const Config = z.object({
|
|
|
3546
3598
|
processMaxBytes: z.number().default(8e3),
|
|
3547
3599
|
registerAppTimeoutMs: z.number().default(6e5),
|
|
3548
3600
|
processingReactionEmoji: z.string().default("OneSecond"),
|
|
3549
|
-
errorDetailMaxChars: z.number().default(500)
|
|
3601
|
+
errorDetailMaxChars: z.number().default(500),
|
|
3602
|
+
injectSender: z.boolean().default(true)
|
|
3550
3603
|
}).default({
|
|
3551
3604
|
cardUpdateThrottleMs: 500,
|
|
3552
3605
|
cardMaxBytes: 28e3,
|
|
3553
3606
|
processMaxBytes: 8e3,
|
|
3554
3607
|
registerAppTimeoutMs: 6e5,
|
|
3555
3608
|
processingReactionEmoji: "OneSecond",
|
|
3556
|
-
errorDetailMaxChars: 500
|
|
3609
|
+
errorDetailMaxChars: 500,
|
|
3610
|
+
injectSender: true
|
|
3557
3611
|
}),
|
|
3558
3612
|
agentTeamPreset: z.object({
|
|
3559
3613
|
enabled: z.boolean().default(true),
|