@vui-design/openclaw-plugin-feishu-progress 0.4.2 → 0.5.0
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/package.json +1 -1
- package/src/index.ts +36 -9
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -42,8 +42,15 @@ function buildToolLabel(toolName: string, args: Record<string, unknown>): string
|
|
|
42
42
|
return '⚡ 执行命令';
|
|
43
43
|
}
|
|
44
44
|
case 'process': {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// 尝试多种字段名,兼容不同 OpenClaw 版本
|
|
46
|
+
const cmd = s(
|
|
47
|
+
args?.command ?? args?.cmd ?? args?.executable ??
|
|
48
|
+
args?.program ?? args?.bin ?? args?.process,
|
|
49
|
+
80,
|
|
50
|
+
);
|
|
51
|
+
const argList = s(args?.args ?? args?.arguments ?? args?.argv, 40);
|
|
52
|
+
if (cmd) return `⚡ ${cmd.slice(0, 40)}${argList ? ' ' + argList : ''}`;
|
|
53
|
+
return '⚡ 执行进程';
|
|
47
54
|
}
|
|
48
55
|
case 'read':
|
|
49
56
|
case 'read_file': {
|
|
@@ -90,6 +97,16 @@ function buildToolLabel(toolName: string, args: Record<string, unknown>): string
|
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
|
|
100
|
+
// 判断是否为"动作前言"型 AI 短句(适合作进度提示)
|
|
101
|
+
// 规则:长度 ≤ 30,以":" 结尾,或以动作动词开头
|
|
102
|
+
// 排除超过 30 字的长句,避免把完整回复内容当作进度消息发送
|
|
103
|
+
function isActionSnippet(text: string): boolean {
|
|
104
|
+
if (!text || text.length > 30) return false;
|
|
105
|
+
if (text.endsWith(':') || text.endsWith(':')) return true;
|
|
106
|
+
if (/^(正在|开始|准备|即将|继续|尝试|检查|分析|搜索|获取|处理|生成|执行|读取|写入|删除|更新)/.test(text)) return true;
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
// 从 AI 输出文本提取最末一句(去掉 markdown 标记符)
|
|
94
111
|
function extractSnippet(text: string): string {
|
|
95
112
|
const lines = text
|
|
@@ -115,6 +132,8 @@ interface RunState {
|
|
|
115
132
|
agentId: string;
|
|
116
133
|
sessionKey: string;
|
|
117
134
|
assistantBuffer: string;
|
|
135
|
+
/** 上一条已发送标签,防止连续相同标签重复推送 */
|
|
136
|
+
lastLabel: string;
|
|
118
137
|
/** 已发送过的 snippet,防止重复推送 */
|
|
119
138
|
sentSnippets: Set<string>;
|
|
120
139
|
startedAt: number;
|
|
@@ -160,7 +179,7 @@ export default {
|
|
|
160
179
|
|
|
161
180
|
// ── Hooks ───────────────────────────────────────────────────────────────
|
|
162
181
|
|
|
163
|
-
// 收到飞书消息 → 记录 accountId → chatId(兜底索引)
|
|
182
|
+
// 收到飞书消息 → 立即发送"思考中"确认 + 记录 accountId → chatId(兜底索引)
|
|
164
183
|
api.on('message_received', async (_event: any, ctx: any) => {
|
|
165
184
|
if (ctx.channelId !== 'feishu') return;
|
|
166
185
|
if (!ctx.conversationId) return;
|
|
@@ -169,6 +188,9 @@ export default {
|
|
|
169
188
|
? ctx.conversationId.split(':').pop()!
|
|
170
189
|
: ctx.conversationId;
|
|
171
190
|
if (ctx.accountId) accountChatMap.set(ctx.accountId, rawId);
|
|
191
|
+
|
|
192
|
+
// 立即向用户发送确认,让用户知道机器人已收到消息
|
|
193
|
+
client.sendText(rawId, '🤔 思考中,请稍候...').catch(() => {});
|
|
172
194
|
});
|
|
173
195
|
|
|
174
196
|
// llm_input → 建立 runId → RunState,启动心跳
|
|
@@ -188,6 +210,7 @@ export default {
|
|
|
188
210
|
agentId: ctx.agentId ?? '',
|
|
189
211
|
sessionKey: ctx.sessionKey ?? '',
|
|
190
212
|
assistantBuffer: '',
|
|
213
|
+
lastLabel: '',
|
|
191
214
|
sentSnippets: new Set(),
|
|
192
215
|
startedAt,
|
|
193
216
|
lastSentAt: startedAt,
|
|
@@ -224,8 +247,8 @@ export default {
|
|
|
224
247
|
state.assistantBuffer = ''; // 每次工具调用前消费
|
|
225
248
|
|
|
226
249
|
let label: string;
|
|
227
|
-
if (snippet && !state.sentSnippets.has(snippet)) {
|
|
228
|
-
// AI
|
|
250
|
+
if (snippet && isActionSnippet(snippet) && !state.sentSnippets.has(snippet)) {
|
|
251
|
+
// AI 文本优先,但仅限"动作前言"型短句,且去重
|
|
229
252
|
state.sentSnippets.add(snippet);
|
|
230
253
|
label = snippet;
|
|
231
254
|
} else {
|
|
@@ -235,6 +258,10 @@ export default {
|
|
|
235
258
|
);
|
|
236
259
|
}
|
|
237
260
|
|
|
261
|
+
// 连续相同标签去重(同一工具被连续调用多次时只发一次)
|
|
262
|
+
if (label === state.lastLabel) return;
|
|
263
|
+
state.lastLabel = label;
|
|
264
|
+
|
|
238
265
|
state.lastSentAt = Date.now();
|
|
239
266
|
client.sendText(state.chatId, label).catch((err: any) => {
|
|
240
267
|
api.logger?.warn(`[feishu-progress] 推送失败: ${err?.message}`);
|
|
@@ -248,12 +275,12 @@ export default {
|
|
|
248
275
|
}
|
|
249
276
|
});
|
|
250
277
|
|
|
251
|
-
// agent_end →
|
|
278
|
+
// agent_end → 兜底清理(lifecycle:end 未触发时的保障)
|
|
279
|
+
// 只按 sessionKey 匹配,避免同 agentId 多并发 run 时误清其他 run
|
|
252
280
|
api.on('agent_end', async (_event: any, ctx: any) => {
|
|
281
|
+
if (!ctx.sessionKey) return; // 无 sessionKey 无法精确匹配,跳过
|
|
253
282
|
for (const [runId, state] of runStateMap) {
|
|
254
|
-
|
|
255
|
-
const matchByAgent = !ctx.sessionKey && ctx.agentId && state.agentId === ctx.agentId;
|
|
256
|
-
if (matchBySession || matchByAgent) cleanupRun(runId);
|
|
283
|
+
if (state.sessionKey === ctx.sessionKey) cleanupRun(runId);
|
|
257
284
|
}
|
|
258
285
|
});
|
|
259
286
|
},
|