dsh-agent-toolkit 0.2.2 → 0.2.4
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 +109 -101
- package/lib/client.js.map +1 -1
- package/lib/index.js +156 -87
- package/lib/index.js.map +1 -1
- package/package.json +7 -1
package/lib/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { SessionId } from "@deepseek-ai/dsh-session";
|
|
|
12
12
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
13
13
|
import * as lark from "@larksuiteoapi/node-sdk";
|
|
14
14
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
15
|
+
import { bindScopeParent, createScope, scopeOf } from "@deepseek-ai/dsh-scope";
|
|
15
16
|
import { setupUsage } from "@dsh-agent-toolkit/token-usage";
|
|
16
17
|
//#region src/agents/store.ts
|
|
17
18
|
/** agents 注册表存储域声明:记录 schema + domain 布局的单一来源。 */
|
|
@@ -1668,18 +1669,15 @@ function setupCreateAgentCommand(ctx, deps) {
|
|
|
1668
1669
|
//#region src/channels/agent-setup.ts
|
|
1669
1670
|
/** bot 会话角色 persona 的 scoped 段名:与全局 persona 层同名,scoped 注册即 shadow 覆盖主 Agent persona。 */
|
|
1670
1671
|
const TOOLKIT_PERSONA_SECTION = "prompt-stack:persona";
|
|
1671
|
-
/** 默认加载器:复刻 preset mount 的模块解析——loader 的 unwrapExports 取 `default ?? 命名导出模块`。 */
|
|
1672
|
-
async function loadToolModule(specifier) {
|
|
1673
|
-
const mod = await import(specifier);
|
|
1674
|
-
return mod.default ?? mod;
|
|
1675
|
-
}
|
|
1676
1672
|
/**
|
|
1677
|
-
* 组合 agent
|
|
1678
|
-
*
|
|
1679
|
-
* 顺序敏感:restrict
|
|
1673
|
+
* 组合 agent 作用域:先把 agent scope 父链挂到基础工具行 standing scope(工具成为
|
|
1674
|
+
* 继承面),再叠 bot 的 persona 与 tools 白名单。
|
|
1675
|
+
* 顺序敏感:restrict 必须在 join 之后——宿主 tools.restrict() 只校验并过滤继承面
|
|
1676
|
+
* (global + 祖先 scope 层),own 层(agentCtx 直接挂载)的名字既不可 restrict 也
|
|
1677
|
+
* 不受 restrict 影响,直接挂进 agentCtx 的白名单会抛 unknown global tools。
|
|
1680
1678
|
*/
|
|
1681
|
-
async function setupAgentScope(agentCtx, hooks,
|
|
1682
|
-
|
|
1679
|
+
async function setupAgentScope(agentCtx, hooks, toolsScope) {
|
|
1680
|
+
await toolsScope.join(agentCtx);
|
|
1683
1681
|
if (hooks.persona !== void 0) agentCtx.systemPrompt.section({
|
|
1684
1682
|
name: TOOLKIT_PERSONA_SECTION,
|
|
1685
1683
|
order: 10,
|
|
@@ -2266,20 +2264,23 @@ const feishuChannel = {
|
|
|
2266
2264
|
appSecret: bot.secret
|
|
2267
2265
|
}));
|
|
2268
2266
|
const dedup = new MessageDedup();
|
|
2269
|
-
const dispatcher = new lark.EventDispatcher({}).register({
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2267
|
+
const dispatcher = new lark.EventDispatcher({}).register({
|
|
2268
|
+
"im.message.message_read_v1": async () => void 0,
|
|
2269
|
+
"im.message.receive_v1": async (data) => {
|
|
2270
|
+
const parsed = parseMessageEvent(data);
|
|
2271
|
+
if (parsed === null || !dedup.check(parsed.messageId)) return;
|
|
2272
|
+
const reply = new FeishuReplyHandle(api, parsed.chatId, tunables, log);
|
|
2273
|
+
io.onMessage({
|
|
2274
|
+
botId: bot.record.id,
|
|
2275
|
+
chatId: parsed.chatId,
|
|
2276
|
+
userId: parsed.userId,
|
|
2277
|
+
messageId: parsed.messageId,
|
|
2278
|
+
text: parsed.text,
|
|
2279
|
+
reply,
|
|
2280
|
+
ackProcessing: makeAck(api, parsed.messageId, tunables.processingReactionEmoji)
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2283
2284
|
const ws = new lark.WSClient({
|
|
2284
2285
|
appId,
|
|
2285
2286
|
appSecret: bot.secret,
|
|
@@ -2342,66 +2343,6 @@ const projectBotDomain = defineDomain({
|
|
|
2342
2343
|
function bindingKey(botId, chatId) {
|
|
2343
2344
|
return `${botId}:${chatId}`;
|
|
2344
2345
|
}
|
|
2345
|
-
//#endregion
|
|
2346
|
-
//#region src/channels/inbound.ts
|
|
2347
|
-
/** 入站:指令分流 → 路由 → 单 in-flight 准入 → 表情回复 → followup 投递。 */
|
|
2348
|
-
var Inbound = class {
|
|
2349
|
-
deps;
|
|
2350
|
-
constructor(deps) {
|
|
2351
|
-
this.deps = deps;
|
|
2352
|
-
}
|
|
2353
|
-
onMessage(msg) {
|
|
2354
|
-
this.handle(msg).catch(async (error) => {
|
|
2355
|
-
this.deps.onError(`[project-bot] 入站处理失败:${error instanceof Error ? error.message : String(error)}`);
|
|
2356
|
-
await msg.reply.notice("处理失败,请稍后再试").catch(() => void 0);
|
|
2357
|
-
});
|
|
2358
|
-
}
|
|
2359
|
-
async handle(msg) {
|
|
2360
|
-
const bot = this.deps.bots.get(msg.botId);
|
|
2361
|
-
if (bot === void 0) return;
|
|
2362
|
-
const directive = parseDirective(msg.text);
|
|
2363
|
-
if (directive === "new") {
|
|
2364
|
-
await this.deps.router.reset(bot, msg.chatId, msg.reply);
|
|
2365
|
-
await msg.reply.notice("已开启新会话");
|
|
2366
|
-
return;
|
|
2367
|
-
}
|
|
2368
|
-
if (directive === "stop") {
|
|
2369
|
-
const rt = this.deps.router.lookup(bot.id, msg.chatId);
|
|
2370
|
-
if (rt?.inflight !== void 0) {
|
|
2371
|
-
rt.agent.cancel();
|
|
2372
|
-
await msg.reply.notice("已请求停止当前任务");
|
|
2373
|
-
} else await msg.reply.notice("当前没有进行中的任务");
|
|
2374
|
-
return;
|
|
2375
|
-
}
|
|
2376
|
-
if (directive === "status") {
|
|
2377
|
-
const rt = this.deps.router.lookup(bot.id, msg.chatId);
|
|
2378
|
-
await msg.reply.notice(rt === void 0 ? `项目:${bot.project}\n会话:未创建(发送消息即创建)` : `项目:${bot.project}\n会话:${rt.sessionId}\n状态:${rt.inflight !== void 0 ? "处理中" : "空闲"}`);
|
|
2379
|
-
return;
|
|
2380
|
-
}
|
|
2381
|
-
const rt = await this.deps.router.ensure(bot, msg.chatId, msg.reply);
|
|
2382
|
-
if (rt.inflight !== void 0) {
|
|
2383
|
-
await msg.reply.notice("上一条还在处理中,请稍候(或发送 /stop 取消)");
|
|
2384
|
-
return;
|
|
2385
|
-
}
|
|
2386
|
-
rt.inflight = { ack: void 0 };
|
|
2387
|
-
rt.inflight.ack = await msg.ackProcessing().catch(() => void 0) ?? void 0;
|
|
2388
|
-
const message = createUserMessage({
|
|
2389
|
-
content: [{
|
|
2390
|
-
type: "text",
|
|
2391
|
-
text: msg.text
|
|
2392
|
-
}],
|
|
2393
|
-
source: { kind: "user" }
|
|
2394
|
-
});
|
|
2395
|
-
try {
|
|
2396
|
-
rt.agent.followup(message);
|
|
2397
|
-
} catch (error) {
|
|
2398
|
-
const ack = rt.inflight.ack;
|
|
2399
|
-
rt.inflight = void 0;
|
|
2400
|
-
await ack?.();
|
|
2401
|
-
throw error;
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
};
|
|
2405
2346
|
/** 向段序列追加内容:与尾段同类则合并,异类开新段。 */
|
|
2406
2347
|
function appendToSegments(segments, kind, text) {
|
|
2407
2348
|
const tail = segments[segments.length - 1];
|
|
@@ -2519,6 +2460,67 @@ var Outbound = class {
|
|
|
2519
2460
|
}
|
|
2520
2461
|
};
|
|
2521
2462
|
//#endregion
|
|
2463
|
+
//#region src/channels/inbound.ts
|
|
2464
|
+
/** 入站:指令分流 → 路由 → 单 in-flight 准入 → 表情回复 → followup 投递。 */
|
|
2465
|
+
var Inbound = class {
|
|
2466
|
+
deps;
|
|
2467
|
+
constructor(deps) {
|
|
2468
|
+
this.deps = deps;
|
|
2469
|
+
}
|
|
2470
|
+
onMessage(msg) {
|
|
2471
|
+
this.handle(msg).catch(async (error) => {
|
|
2472
|
+
const detail = truncateDetail(error instanceof Error ? error.message : String(error), this.deps.maxErrorDetailChars);
|
|
2473
|
+
this.deps.onError(`[project-bot] 入站处理失败:${detail}`);
|
|
2474
|
+
await msg.reply.notice(`处理失败:${detail}`).catch(() => void 0);
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
async handle(msg) {
|
|
2478
|
+
const bot = this.deps.bots.get(msg.botId);
|
|
2479
|
+
if (bot === void 0) return;
|
|
2480
|
+
const directive = parseDirective(msg.text);
|
|
2481
|
+
if (directive === "new") {
|
|
2482
|
+
await this.deps.router.reset(bot, msg.chatId, msg.reply);
|
|
2483
|
+
await msg.reply.notice("已开启新会话");
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
if (directive === "stop") {
|
|
2487
|
+
const rt = this.deps.router.lookup(bot.id, msg.chatId);
|
|
2488
|
+
if (rt?.inflight !== void 0) {
|
|
2489
|
+
rt.agent.cancel();
|
|
2490
|
+
await msg.reply.notice("已请求停止当前任务");
|
|
2491
|
+
} else await msg.reply.notice("当前没有进行中的任务");
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
if (directive === "status") {
|
|
2495
|
+
const rt = this.deps.router.lookup(bot.id, msg.chatId);
|
|
2496
|
+
await msg.reply.notice(rt === void 0 ? `项目:${bot.project}\n会话:未创建(发送消息即创建)` : `项目:${bot.project}\n会话:${rt.sessionId}\n状态:${rt.inflight !== void 0 ? "处理中" : "空闲"}`);
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
const rt = await this.deps.router.ensure(bot, msg.chatId, msg.reply);
|
|
2500
|
+
if (rt.inflight !== void 0) {
|
|
2501
|
+
await msg.reply.notice("上一条还在处理中,请稍候(或发送 /stop 取消)");
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
rt.inflight = { ack: void 0 };
|
|
2505
|
+
rt.inflight.ack = await msg.ackProcessing().catch(() => void 0) ?? void 0;
|
|
2506
|
+
const message = createUserMessage({
|
|
2507
|
+
content: [{
|
|
2508
|
+
type: "text",
|
|
2509
|
+
text: msg.text
|
|
2510
|
+
}],
|
|
2511
|
+
source: { kind: "user" }
|
|
2512
|
+
});
|
|
2513
|
+
try {
|
|
2514
|
+
rt.agent.followup(message);
|
|
2515
|
+
} catch (error) {
|
|
2516
|
+
const ack = rt.inflight.ack;
|
|
2517
|
+
rt.inflight = void 0;
|
|
2518
|
+
await ack?.();
|
|
2519
|
+
throw error;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
};
|
|
2523
|
+
//#endregion
|
|
2522
2524
|
//#region src/channels/ports.ts
|
|
2523
2525
|
/** 从 bot 记录提取创作期注入(主 Agent 绑定形态)。 */
|
|
2524
2526
|
function hooksOf(bot) {
|
|
@@ -2659,6 +2661,7 @@ var BotRuntime = class {
|
|
|
2659
2661
|
this.inbound = new Inbound({
|
|
2660
2662
|
router: this.router,
|
|
2661
2663
|
bots: deps.bots,
|
|
2664
|
+
maxErrorDetailChars: deps.maxErrorDetailChars,
|
|
2662
2665
|
onError: (m) => deps.log.warn(m)
|
|
2663
2666
|
});
|
|
2664
2667
|
this.outbound = new Outbound(this.sessions, (m) => deps.log.warn(m), deps.maxErrorDetailChars);
|
|
@@ -2742,6 +2745,55 @@ var BotRuntime = class {
|
|
|
2742
2745
|
}
|
|
2743
2746
|
};
|
|
2744
2747
|
//#endregion
|
|
2748
|
+
//#region src/channels/tool-scope.ts
|
|
2749
|
+
/** 默认加载器:复刻 preset mount 的模块解析——loader 的 unwrapExports 取 `default ?? 命名导出模块`。 */
|
|
2750
|
+
async function loadToolModule(specifier) {
|
|
2751
|
+
const mod = await import(specifier);
|
|
2752
|
+
return mod.default ?? mod;
|
|
2753
|
+
}
|
|
2754
|
+
/** 建一个 bot 会话共用的工具行 standing scope(所有 bot 会话挂同一套 BASIC_TOOLS)。 */
|
|
2755
|
+
function createToolsScope(ctx, loadTool = loadToolModule) {
|
|
2756
|
+
let scope;
|
|
2757
|
+
let mount;
|
|
2758
|
+
let generation = 0;
|
|
2759
|
+
function ensure() {
|
|
2760
|
+
mount ??= (async () => {
|
|
2761
|
+
const key = {
|
|
2762
|
+
origin: "dsh-agent-toolkit",
|
|
2763
|
+
kind: "bots-tools",
|
|
2764
|
+
gen: ++generation
|
|
2765
|
+
};
|
|
2766
|
+
const created = createScope(ctx, key);
|
|
2767
|
+
scope = created;
|
|
2768
|
+
try {
|
|
2769
|
+
for (const tool of BASIC_TOOLS) await created.ctx.plugin(await loadTool(tool.id), tool.config);
|
|
2770
|
+
} catch (error) {
|
|
2771
|
+
if (scope === created) scope = void 0;
|
|
2772
|
+
mount = void 0;
|
|
2773
|
+
await created.dispose().catch(() => void 0);
|
|
2774
|
+
throw error;
|
|
2775
|
+
}
|
|
2776
|
+
return key;
|
|
2777
|
+
})();
|
|
2778
|
+
return mount;
|
|
2779
|
+
}
|
|
2780
|
+
return {
|
|
2781
|
+
async join(agentCtx) {
|
|
2782
|
+
const agentKey = scopeOf(agentCtx);
|
|
2783
|
+
if (agentKey === void 0) throw new Error("tools-scope: refusing to join an unscoped agent context; per-agent composition requires a scope key");
|
|
2784
|
+
const standing = await ensure();
|
|
2785
|
+
bindScopeParent(agentKey, standing);
|
|
2786
|
+
return standing;
|
|
2787
|
+
},
|
|
2788
|
+
async dispose() {
|
|
2789
|
+
mount = void 0;
|
|
2790
|
+
const current = scope;
|
|
2791
|
+
scope = void 0;
|
|
2792
|
+
await current?.dispose();
|
|
2793
|
+
}
|
|
2794
|
+
};
|
|
2795
|
+
}
|
|
2796
|
+
//#endregion
|
|
2745
2797
|
//#region src/bots/api.ts
|
|
2746
2798
|
/** 浏览器半 RPC:单前缀路由 /dsh-agent-toolkit/api/bots + 内部路径分发。 */
|
|
2747
2799
|
const MAX_BODY_BYTES = 65536;
|
|
@@ -2993,6 +3045,18 @@ function createApiHandler(deps) {
|
|
|
2993
3045
|
//#endregion
|
|
2994
3046
|
//#region src/bots/register-app.ts
|
|
2995
3047
|
/** 扫码一键创建飞书应用:lark.registerApp(OAuth 2.0 Device Authorization Grant)的状态机封装。 */
|
|
3048
|
+
/** 扫码创建应用时申请的权限/事件(流式卡片 + 收发消息 + 表情 + 通讯录基础信息)。
|
|
3049
|
+
* 故意不加 as const:readonly 元组不可赋值给 SDK AppAddons 的 mutable string[],
|
|
3050
|
+
* 否则 bots/index.ts 的 lark.registerApp(options) 透传会 typecheck 失败。 */
|
|
3051
|
+
const FEISHU_REGISTER_APP_ADDONS = {
|
|
3052
|
+
scopes: { tenant: [
|
|
3053
|
+
"im:message",
|
|
3054
|
+
"im:message:send_as_bot",
|
|
3055
|
+
"cardkit:card:write",
|
|
3056
|
+
"contact:user.base:readonly"
|
|
3057
|
+
] },
|
|
3058
|
+
events: { items: { tenant: ["im.message.receive_v1"] } }
|
|
3059
|
+
};
|
|
2996
3060
|
var RegisterAppService = class {
|
|
2997
3061
|
deps;
|
|
2998
3062
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -3013,6 +3077,7 @@ var RegisterAppService = class {
|
|
|
3013
3077
|
this.sessions.set(id, entry);
|
|
3014
3078
|
this.deps.registerApp({
|
|
3015
3079
|
signal: controller.signal,
|
|
3080
|
+
addons: FEISHU_REGISTER_APP_ADDONS,
|
|
3016
3081
|
onQRCodeReady: (info) => {
|
|
3017
3082
|
entry.state = {
|
|
3018
3083
|
status: "pending",
|
|
@@ -3071,21 +3136,22 @@ function setupBots(ctx, config, deps) {
|
|
|
3071
3136
|
await ctx.credentials.set(credentialRef(ref), secret);
|
|
3072
3137
|
return ref;
|
|
3073
3138
|
};
|
|
3074
|
-
/** 创作期注入已迁至 agent-setup.ts
|
|
3139
|
+
/** 创作期注入已迁至 agent-setup.ts + tool-scope.ts(基础工具行 standing scope 挂载 + persona/tools),preset 机制整体移除。 */
|
|
3140
|
+
const toolsScope = createToolsScope(ctx);
|
|
3075
3141
|
const agentsPort = {
|
|
3076
3142
|
async create(input) {
|
|
3077
3143
|
return adaptAgent(await ctx.agents.create({
|
|
3078
3144
|
sessionId: SessionId(input.sessionId),
|
|
3079
3145
|
meta: { cwd: input.cwd },
|
|
3080
3146
|
...input.agentOptions !== void 0 ? { agentOptions: input.agentOptions } : {},
|
|
3081
|
-
setup: (agentCtx) => setupAgentScope(agentCtx, input.hooks)
|
|
3147
|
+
setup: (agentCtx) => setupAgentScope(agentCtx, input.hooks, toolsScope)
|
|
3082
3148
|
}));
|
|
3083
3149
|
},
|
|
3084
3150
|
async resume(input) {
|
|
3085
3151
|
return adaptAgent(await ctx.agents.resume({
|
|
3086
3152
|
resumeSessionId: SessionId(input.sessionId),
|
|
3087
3153
|
...input.agentOptions !== void 0 ? { agentOptions: input.agentOptions } : {},
|
|
3088
|
-
setup: (agentCtx) => setupAgentScope(agentCtx, input.hooks)
|
|
3154
|
+
setup: (agentCtx) => setupAgentScope(agentCtx, input.hooks, toolsScope)
|
|
3089
3155
|
}));
|
|
3090
3156
|
}
|
|
3091
3157
|
};
|
|
@@ -3189,6 +3255,9 @@ function setupBots(ctx, config, deps) {
|
|
|
3189
3255
|
});
|
|
3190
3256
|
return () => dispose();
|
|
3191
3257
|
});
|
|
3258
|
+
ctx.effect(() => async () => {
|
|
3259
|
+
await toolsScope.dispose();
|
|
3260
|
+
});
|
|
3192
3261
|
ctx.effect(() => async () => {
|
|
3193
3262
|
registerAppService.dispose();
|
|
3194
3263
|
});
|