@ai-setting/roy-agent-core 1.7.85 → 1.7.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/dist/config/index.js +7 -7
- package/dist/env/agent/agent-component.d.ts +1 -1
- package/dist/env/agent/agent-component.js +13 -12
- package/dist/env/agent/index.js +13 -12
- package/dist/env/agent/types.d.ts +14 -0
- package/dist/env/commands/index.js +7 -7
- package/dist/env/context/index.js +3 -3
- package/dist/env/debug/index.js +7 -7
- package/dist/env/error-reply.js +1 -1
- package/dist/env/event-source/event-source-handlers.d.ts +18 -2
- package/dist/env/event-source/event-source-handlers.js +26 -21
- package/dist/env/event-source/event-store.js +2 -2
- package/dist/env/event-source/index.js +25 -22
- package/dist/env/event-source/timer-handler.d.ts +98 -0
- package/dist/env/event-source/types.d.ts +53 -1
- package/dist/env/event-source/wecom-card-action-handler.d.ts +235 -3
- package/dist/env/event-source/wecom-card-builder.d.ts +207 -1
- package/dist/env/event-source/wecom-cli-handler.d.ts +25 -0
- package/dist/{shared/@ai-setting/roy-agent-core-vyr6svt6.js → env/event-source/wecom-respond-registry.js} +8 -6
- package/dist/env/event-source/wecom-session-card-sender.d.ts +135 -0
- package/dist/env/event-source/wecom-session-card-sender.js +153 -0
- package/dist/env/hook/index.js +5 -5
- package/dist/env/index.js +49 -45
- package/dist/env/llm/index.js +9 -8
- package/dist/env/log-trace/index.js +7 -7
- package/dist/env/log-trace/trace-filters.d.ts +6 -1
- package/dist/env/mcp/index.js +7 -7
- package/dist/env/memory/index.js +7 -7
- package/dist/env/plugin/index.js +7 -7
- package/dist/env/prompt/index.js +8 -8
- package/dist/env/prompt/prompts-index.d.ts +1 -1
- package/dist/env/session/group-session-link.d.ts +99 -0
- package/dist/env/session/index.js +11 -10
- package/dist/env/session/session-component.d.ts +89 -0
- package/dist/env/session/storage/index.js +5 -4
- package/dist/env/session/types.d.ts +10 -0
- package/dist/env/skill/index.js +7 -7
- package/dist/env/task/delegate/delegate-channel-hint.d.ts +39 -0
- package/dist/env/task/delegate/delegate-tool.d.ts +71 -6
- package/dist/env/task/delegate/delegate-tool.js +11 -9
- package/dist/env/task/delegate/index.js +8 -8
- package/dist/env/task/index.js +18 -18
- package/dist/env/task/mcp/index.js +4 -4
- package/dist/env/task/plugins/card-subject.d.ts +117 -0
- package/dist/env/task/plugins/index.js +19 -17
- package/dist/env/task/plugins/lark-card-action-handler.d.ts +261 -6
- package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +501 -0
- package/dist/env/task/plugins/lark-card-patch-notifier.js +12 -2
- package/dist/env/task/plugins/lark-card-state-store.d.ts +60 -0
- package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +45 -0
- package/dist/env/task/plugins/sub-session-activity.d.ts +415 -0
- package/dist/env/task/plugins/wecom-session-card-store.d.ts +134 -0
- package/dist/env/task/plugins/wecom-session-card-store.js +23 -0
- package/dist/env/task/plugins/wecom-task-notify-plugin.d.ts +40 -0
- package/dist/env/task/storage/index.js +3 -3
- package/dist/env/task/tools/index.js +4 -4
- package/dist/env/tool/built-in/index.js +6 -4
- package/dist/env/tool/index.js +14 -10
- package/dist/env/tool/types.d.ts +25 -0
- package/dist/env/tool/validator.d.ts +8 -0
- package/dist/env/workflow/engine/index.js +12 -12
- package/dist/env/workflow/hooks/index.js +7 -7
- package/dist/env/workflow/index.js +24 -23
- package/dist/env/workflow/nodes/index.js +2 -2
- package/dist/env/workflow/plugins/index.js +10 -8
- package/dist/env/workflow/service/index.js +2 -2
- package/dist/env/workflow/storage/index.js +3 -3
- package/dist/env/workflow/tools/index.js +16 -15
- package/dist/env/workflow/utils/index.js +4 -4
- package/dist/index.js +65 -60
- package/dist/shared/@ai-setting/{roy-agent-core-dc9zq75z.js → roy-agent-core-0crmsc12.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-cha0jc7n.js → roy-agent-core-0sbmdz8d.js} +137 -16
- package/dist/shared/@ai-setting/{roy-agent-core-87942bw4.js → roy-agent-core-1mhyh4c5.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-v6vr6za7.js → roy-agent-core-1vv4fx15.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-nwbfepba.js → roy-agent-core-3e040tc5.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-418zc9ha.js +288 -0
- package/dist/shared/@ai-setting/{roy-agent-core-r5wt1h3c.js → roy-agent-core-4fkxy2gv.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-na18zwj0.js → roy-agent-core-4t3yskp8.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-5pr4zp88.js → roy-agent-core-5kzhaap1.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-qq96p0tj.js → roy-agent-core-5s2t8z00.js} +22 -1
- package/dist/shared/@ai-setting/{roy-agent-core-6rh8k6ba.js → roy-agent-core-6gj1hc5z.js} +7 -7
- package/dist/shared/@ai-setting/{roy-agent-core-ezvpreqz.js → roy-agent-core-6k8mb7zx.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-g8htfwxj.js → roy-agent-core-7f10p7m2.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-7kvpc0w5.js +177 -0
- package/dist/shared/@ai-setting/{roy-agent-core-h21gngxf.js → roy-agent-core-7yh5wps0.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-efkjyh69.js → roy-agent-core-9710zwwz.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-rykf7c04.js → roy-agent-core-9c565frx.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-fghc2m8z.js → roy-agent-core-aa8kv18f.js} +34 -2
- package/dist/shared/@ai-setting/{roy-agent-core-p6ks90tb.js → roy-agent-core-an35dzbf.js} +6 -6
- package/dist/shared/@ai-setting/{roy-agent-core-7bpj859q.js → roy-agent-core-azyz78ky.js} +7 -7
- package/dist/shared/@ai-setting/{roy-agent-core-264qexac.js → roy-agent-core-b7h39vrc.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-dqxec05w.js → roy-agent-core-bjtc9d35.js} +2 -2
- package/dist/shared/@ai-setting/roy-agent-core-c4ec7k0p.js +426 -0
- package/dist/shared/@ai-setting/{roy-agent-core-ddyk1rc0.js → roy-agent-core-cre2y2ct.js} +160 -4
- package/dist/shared/@ai-setting/{roy-agent-core-tr3ep81v.js → roy-agent-core-d6zdbgtc.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-0qyrk5k0.js → roy-agent-core-ds6qzmf8.js} +14 -5
- package/dist/shared/@ai-setting/{roy-agent-core-cgtq6427.js → roy-agent-core-e08bvpaq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-12n08pg5.js → roy-agent-core-e69qxdyy.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-qk9hd89p.js → roy-agent-core-fan8g0z1.js} +10 -10
- package/dist/shared/@ai-setting/{roy-agent-core-xcp8vwax.js → roy-agent-core-fepm835e.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-6j02sbee.js → roy-agent-core-fhg03v43.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-dvtg4sn0.js → roy-agent-core-g8yr733d.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-xf6z96qy.js → roy-agent-core-gr5h1dyr.js} +818 -346
- package/dist/shared/@ai-setting/{roy-agent-core-m3vjepzz.js → roy-agent-core-h1e1fm9b.js} +38 -202
- package/dist/shared/@ai-setting/{roy-agent-core-c2gbt68e.js → roy-agent-core-h83zdsrq.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-tk98f92b.js → roy-agent-core-k3gcvybs.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-pgvcf91v.js → roy-agent-core-m39v7qpx.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-tm1mxttk.js → roy-agent-core-mf5p38yf.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-83bewqx8.js → roy-agent-core-mvh0td7p.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-ze5cqpf2.js → roy-agent-core-mx855td4.js} +17 -38
- package/dist/shared/@ai-setting/{roy-agent-core-0dh8npnz.js → roy-agent-core-n197medr.js} +13 -13
- package/dist/shared/@ai-setting/{roy-agent-core-cmprygrp.js → roy-agent-core-nqkfkpf4.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-7cbpmk4q.js → roy-agent-core-p31ftgq9.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-vffccgf6.js → roy-agent-core-qwd2dq5q.js} +10 -3
- package/dist/shared/@ai-setting/{roy-agent-core-b31v3qpv.js → roy-agent-core-ra66sf50.js} +990 -211
- package/dist/shared/@ai-setting/{roy-agent-core-1v4g0d4x.js → roy-agent-core-rm7seqwb.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-7dd0s3xb.js → roy-agent-core-shb1qsyg.js} +1027 -14
- package/dist/shared/@ai-setting/{roy-agent-core-ax9e9szb.js → roy-agent-core-smf94h73.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-acj8ag08.js → roy-agent-core-tjnabyvq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-qqaak24r.js → roy-agent-core-tkpp1ba0.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-dkrvmjsh.js → roy-agent-core-tpm1dd1g.js} +23 -15
- package/dist/shared/@ai-setting/{roy-agent-core-ed233k5z.js → roy-agent-core-v1n0106v.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-zxz5fkwj.js → roy-agent-core-v6yshv0z.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-mcy5apay.js → roy-agent-core-vf9sp6s3.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-q813vafd.js → roy-agent-core-vpcxv1ty.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-xvt7kf7p.js → roy-agent-core-w9ryeg2p.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-es203w7p.js → roy-agent-core-wqs6yypq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-d1n4jbza.js → roy-agent-core-wr1qexzq.js} +5 -5
- package/dist/shared/@ai-setting/roy-agent-core-wxrkcb17.js +56 -0
- package/dist/shared/@ai-setting/{roy-agent-core-0snm5hc5.js → roy-agent-core-y6shd91r.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-9vxh6f8h.js → roy-agent-core-y7fqtzk2.js} +7 -7
- package/dist/shared/@ai-setting/{roy-agent-core-cpcd24n2.js → roy-agent-core-yet36q5k.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-3fh1f7s7.js → roy-agent-core-yfpnkc4z.js} +5 -5
- package/dist/shared/@ai-setting/{roy-agent-core-begm7ytz.js → roy-agent-core-yh68z1bg.js} +3 -3
- package/package.json +9 -1
|
@@ -4,57 +4,76 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
cliCommandHandler
|
|
6
6
|
} from "./roy-agent-core-v0hf14bj.js";
|
|
7
|
+
import {
|
|
8
|
+
getSharedWecomSessionCardStore
|
|
9
|
+
} from "./roy-agent-core-7kvpc0w5.js";
|
|
7
10
|
import {
|
|
8
11
|
guardConfigWrite
|
|
9
12
|
} from "./roy-agent-core-2stt4rp5.js";
|
|
10
13
|
import {
|
|
11
14
|
TaskTagPlugin,
|
|
15
|
+
createGroupChatViaLarkCli,
|
|
12
16
|
createLarkCliTaskNotifyHook,
|
|
13
17
|
createPerTaskGroupChatPlugin,
|
|
14
18
|
createWecomCardStateStore,
|
|
15
19
|
createWecomTaskNotifyHook,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "./roy-agent-core-
|
|
20
|
+
deleteGroupChatViaLarkCli,
|
|
21
|
+
handleCardAction
|
|
22
|
+
} from "./roy-agent-core-ra66sf50.js";
|
|
19
23
|
import {
|
|
20
|
-
WecomCardActionKey,
|
|
21
|
-
buildWecomCardUpdateFrame,
|
|
22
24
|
registerWecomRespondTarget,
|
|
25
|
+
sendWecomCliMarkdown,
|
|
23
26
|
unregisterWecomRespondTarget
|
|
24
|
-
} from "./roy-agent-core-
|
|
27
|
+
} from "./roy-agent-core-h1e1fm9b.js";
|
|
28
|
+
import {
|
|
29
|
+
ROY_SESSION_PREFIX,
|
|
30
|
+
WECOM_SESSION_TASK_SEP,
|
|
31
|
+
WecomCardActionKey,
|
|
32
|
+
buildWecomCardUpdateFrame,
|
|
33
|
+
buildWecomSessionUpdateFrame
|
|
34
|
+
} from "./roy-agent-core-c4ec7k0p.js";
|
|
25
35
|
import {
|
|
26
36
|
getSharedEventStore
|
|
27
|
-
} from "./roy-agent-core-
|
|
37
|
+
} from "./roy-agent-core-4t3yskp8.js";
|
|
28
38
|
import {
|
|
29
39
|
createLarkCliWorkflowNotifyHook
|
|
30
|
-
} from "./roy-agent-core-
|
|
40
|
+
} from "./roy-agent-core-wr1qexzq.js";
|
|
41
|
+
import {
|
|
42
|
+
DEFAULT_APP_LINK_BASE,
|
|
43
|
+
getSharedLarkCardNotifier,
|
|
44
|
+
getSharedLarkCardStateStore
|
|
45
|
+
} from "./roy-agent-core-shb1qsyg.js";
|
|
31
46
|
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
RECENT_MESSAGE_LIMIT,
|
|
48
|
+
createActivityProbe,
|
|
49
|
+
createSessionActivityProbe,
|
|
50
|
+
extractToolCallsFromMessages,
|
|
51
|
+
mapBgTaskStatusToSessionLifecycle,
|
|
52
|
+
toActivityMessage
|
|
53
|
+
} from "./roy-agent-core-418zc9ha.js";
|
|
35
54
|
import {
|
|
36
55
|
notifyReactFailure
|
|
37
|
-
} from "./roy-agent-core-
|
|
56
|
+
} from "./roy-agent-core-9c565frx.js";
|
|
38
57
|
import {
|
|
39
58
|
normalizeLarkAtMentions
|
|
40
59
|
} from "./roy-agent-core-m880qz3y.js";
|
|
41
60
|
import {
|
|
42
61
|
globalHookManager,
|
|
43
62
|
init_global_hook_manager
|
|
44
|
-
} from "./roy-agent-core-
|
|
63
|
+
} from "./roy-agent-core-yet36q5k.js";
|
|
45
64
|
import {
|
|
46
65
|
init_context
|
|
47
|
-
} from "./roy-agent-core-
|
|
66
|
+
} from "./roy-agent-core-tkpp1ba0.js";
|
|
48
67
|
import {
|
|
49
68
|
createEnvContext,
|
|
50
69
|
normalizeParentTaskId,
|
|
51
70
|
runWithEnvContext
|
|
52
|
-
} from "./roy-agent-core-
|
|
71
|
+
} from "./roy-agent-core-p31ftgq9.js";
|
|
53
72
|
import {
|
|
54
73
|
TracedAs,
|
|
55
74
|
init_decorator,
|
|
56
75
|
wrapFunction
|
|
57
|
-
} from "./roy-agent-core-
|
|
76
|
+
} from "./roy-agent-core-5s2t8z00.js";
|
|
58
77
|
import {
|
|
59
78
|
createLogger,
|
|
60
79
|
getTracerProvider,
|
|
@@ -67,7 +86,7 @@ import {
|
|
|
67
86
|
|
|
68
87
|
// src/env/event-source/event-source-handlers.ts
|
|
69
88
|
import { spawn as spawn3, exec as execSync } from "child_process";
|
|
70
|
-
import { promisify
|
|
89
|
+
import { promisify } from "node:util";
|
|
71
90
|
init_global_hook_manager();
|
|
72
91
|
init_context();
|
|
73
92
|
|
|
@@ -193,245 +212,12 @@ function unregisterPluginHooksShared(pluginName, hookPoints) {
|
|
|
193
212
|
}
|
|
194
213
|
}
|
|
195
214
|
|
|
196
|
-
// src/env/task/plugins/lark-card-action-handler.ts
|
|
197
|
-
init_logger();
|
|
198
|
-
import { exec } from "node:child_process";
|
|
199
|
-
import { promisify } from "node:util";
|
|
200
|
-
var logger = createLogger("lark-card-action-handler");
|
|
201
|
-
var execAsync = promisify(exec);
|
|
202
|
-
var CTX_FIELD_CHAT_ID = "larkPerTaskChatId";
|
|
203
|
-
var CTX_FIELD_CHAT_NAME = "larkPerTaskChatName";
|
|
204
|
-
var CTX_FIELD_CHAT_CREATED_AT = "larkPerTaskChatCreatedAt";
|
|
205
|
-
var CTX_FIELD_CHAT_TASK_ID = "larkPerTaskChatTaskId";
|
|
206
|
-
var CTX_FIELD_CHAT_INVITEES = "larkPerTaskChatInvitees";
|
|
207
|
-
var cardActionInflight = new Map;
|
|
208
|
-
async function handleCardAction(event, ports) {
|
|
209
|
-
const { action, taskId: taskIdRaw } = event.actionValue;
|
|
210
|
-
const taskId = typeof taskIdRaw === "string" ? parseInt(taskIdRaw, 10) : taskIdRaw;
|
|
211
|
-
if (!taskId || Number.isNaN(taskId)) {
|
|
212
|
-
return { ok: false, action, error: `Invalid taskId: ${taskIdRaw}` };
|
|
213
|
-
}
|
|
214
|
-
if (action !== "create_group" && action !== "disband_group") {
|
|
215
|
-
return { ok: false, action, error: `Unknown action: ${action}` };
|
|
216
|
-
}
|
|
217
|
-
const inflightKey = `${event.profile ?? "-"}:${event.messageId || String(taskId)}:${action}`;
|
|
218
|
-
const inflight = cardActionInflight.get(inflightKey);
|
|
219
|
-
if (inflight) {
|
|
220
|
-
logger.warn(`handleCardAction: ${inflightKey} 已有同操作在处理中(疑似用户重复点击),本次忽略。`);
|
|
221
|
-
return { ok: false, action, error: `重复点击:${inflightKey} 正在处理中,已忽略` };
|
|
222
|
-
}
|
|
223
|
-
const pending = handleCardActionInner(event, ports, taskId);
|
|
224
|
-
cardActionInflight.set(inflightKey, pending);
|
|
225
|
-
try {
|
|
226
|
-
return await pending;
|
|
227
|
-
} finally {
|
|
228
|
-
cardActionInflight.delete(inflightKey);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
async function handleCardActionInner(event, ports, taskId) {
|
|
232
|
-
const { action } = event.actionValue;
|
|
233
|
-
if (!ports.getTask) {
|
|
234
|
-
return { ok: false, action, error: "ports.getTask not injected" };
|
|
235
|
-
}
|
|
236
|
-
const task = await ports.getTask(taskId);
|
|
237
|
-
if (!task) {
|
|
238
|
-
return { ok: false, action, error: `Task #${taskId} not found` };
|
|
239
|
-
}
|
|
240
|
-
const existingChatId = extractPerTaskChatId(task.context);
|
|
241
|
-
if (action === "create_group") {
|
|
242
|
-
return await handleCreateGroup(event, ports, task, existingChatId);
|
|
243
|
-
} else {
|
|
244
|
-
return await handleDisbandGroup(event, ports, task, existingChatId);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
function buildCardAfterGroupChange(event, ports, task, perTaskChatId, perTaskChatName) {
|
|
248
|
-
const meta = buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName);
|
|
249
|
-
const preserved = ports.notifier?.updateMetaAndBuildCard?.(event.messageId, meta, { clearPerTaskChat: !perTaskChatId });
|
|
250
|
-
if (preserved) {
|
|
251
|
-
logger.debug(`buildCardAfterGroupChange: 复用 notifier 累积状态(里程碑历史保留)messageId=${event.messageId}`);
|
|
252
|
-
return preserved;
|
|
253
|
-
}
|
|
254
|
-
logger.warn(`buildCardAfterGroupChange: notifier 无 messageId=${event.messageId} 的状态,` + `fallback 重建卡片(该卡片的历史里程碑可能丢失)`);
|
|
255
|
-
return buildTaskCard({
|
|
256
|
-
status: "running",
|
|
257
|
-
meta,
|
|
258
|
-
milestones: [
|
|
259
|
-
{ title: "Task 创建", timestamp: new Date().toISOString(), description: task.current_status }
|
|
260
|
-
]
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
async function handleCreateGroup(event, ports, task, existingChatId) {
|
|
264
|
-
if (existingChatId) {
|
|
265
|
-
const existingName = extractPerTaskChatName(task.context);
|
|
266
|
-
logger.info(`handleCreateGroup: Task #${task.id} 已有专属群 ${existingChatId}(幂等命中,不重复建群)`);
|
|
267
|
-
return {
|
|
268
|
-
ok: true,
|
|
269
|
-
action: "create_group",
|
|
270
|
-
newChatId: existingChatId,
|
|
271
|
-
cardUpdate: buildCardAfterGroupChange(event, ports, task, existingChatId, existingName)
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
if (!ports.createGroupChat) {
|
|
275
|
-
return { ok: false, action: "create_group", error: "ports.createGroupChat not injected" };
|
|
276
|
-
}
|
|
277
|
-
try {
|
|
278
|
-
const renderedName = truncateText(`Task #${task.id} - ${task.title ?? "(无标题)"}`, 60);
|
|
279
|
-
const description = truncateText(`roy-agent task #${task.id} - ${task.project_path ?? ""}`, 100);
|
|
280
|
-
const operatorOpenId = extractOperatorOpenId(event.operatorId);
|
|
281
|
-
const initialUserIds = operatorOpenId ? [operatorOpenId] : undefined;
|
|
282
|
-
const chatId = await ports.createGroupChat({
|
|
283
|
-
name: renderedName,
|
|
284
|
-
description,
|
|
285
|
-
profile: event.profile,
|
|
286
|
-
initialUserIds
|
|
287
|
-
});
|
|
288
|
-
let invitedOpenIds = [];
|
|
289
|
-
if (operatorOpenId) {
|
|
290
|
-
if (initialUserIds) {
|
|
291
|
-
invitedOpenIds = [operatorOpenId];
|
|
292
|
-
logger.debug(`handleCreateGroup: ${operatorOpenId} 已在建群时邀请(initialUserIds 路径)`);
|
|
293
|
-
} else if (ports.inviteUser) {
|
|
294
|
-
try {
|
|
295
|
-
await ports.inviteUser(chatId, operatorOpenId, event.profile);
|
|
296
|
-
invitedOpenIds = [operatorOpenId];
|
|
297
|
-
logger.debug(`handleCreateGroup: 走 inviteUser 端口邀请 ${operatorOpenId} 进 ${chatId}`);
|
|
298
|
-
} catch (err) {
|
|
299
|
-
logger.warn(`handleCreateGroup: inviteUser(${chatId}, ${operatorOpenId}) 失败(不阻断主流程): ${err.message}`);
|
|
300
|
-
}
|
|
301
|
-
} else {
|
|
302
|
-
logger.warn(`handleCreateGroup: createGroupChat 不支持 initialUserIds 且 inviteUser 未注入,` + `${operatorOpenId} 未被拉进群 ${chatId}。` + `需升级 caller(bridge/real impl)使用 ports.inviteUser 或实现 createGroupChat.initialUserIds 参数。`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
const newContext = patchContext(task.context ?? "{}", {
|
|
306
|
-
[CTX_FIELD_CHAT_ID]: chatId,
|
|
307
|
-
[CTX_FIELD_CHAT_NAME]: renderedName,
|
|
308
|
-
[CTX_FIELD_CHAT_CREATED_AT]: new Date().toISOString(),
|
|
309
|
-
[CTX_FIELD_CHAT_TASK_ID]: task.id,
|
|
310
|
-
[CTX_FIELD_CHAT_INVITEES]: invitedOpenIds.length > 0 ? invitedOpenIds : undefined
|
|
311
|
-
});
|
|
312
|
-
if (ports.updateTaskContext) {
|
|
313
|
-
await ports.updateTaskContext(task.id, newContext);
|
|
314
|
-
}
|
|
315
|
-
const cardUpdate = buildCardAfterGroupChange(event, ports, task, chatId, renderedName);
|
|
316
|
-
return { ok: true, action: "create_group", newChatId: chatId, cardUpdate };
|
|
317
|
-
} catch (err) {
|
|
318
|
-
logger.error(`handleCardAction.create_group failed: ${err.message}`);
|
|
319
|
-
return { ok: false, action: "create_group", error: err.message };
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
function extractOperatorOpenId(raw) {
|
|
323
|
-
if (!raw)
|
|
324
|
-
return;
|
|
325
|
-
if (typeof raw === "string")
|
|
326
|
-
return raw;
|
|
327
|
-
if (typeof raw === "object" && typeof raw.open_id === "string")
|
|
328
|
-
return raw.open_id;
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
async function handleDisbandGroup(event, ports, task, existingChatId) {
|
|
332
|
-
if (!existingChatId) {
|
|
333
|
-
return {
|
|
334
|
-
ok: false,
|
|
335
|
-
action: "disband_group",
|
|
336
|
-
error: `Task #${task.id} 无专属群(context.larkPerTaskChatId 空),无需解散`
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
if (!ports.deleteGroupChat) {
|
|
340
|
-
return { ok: false, action: "disband_group", error: "ports.deleteGroupChat not injected" };
|
|
341
|
-
}
|
|
342
|
-
try {
|
|
343
|
-
await ports.deleteGroupChat(existingChatId, event.profile);
|
|
344
|
-
const newContext = patchContext(task.context ?? "{}", {
|
|
345
|
-
[CTX_FIELD_CHAT_ID]: undefined,
|
|
346
|
-
[CTX_FIELD_CHAT_NAME]: undefined,
|
|
347
|
-
[CTX_FIELD_CHAT_CREATED_AT]: undefined,
|
|
348
|
-
[CTX_FIELD_CHAT_TASK_ID]: undefined
|
|
349
|
-
});
|
|
350
|
-
if (ports.updateTaskContext) {
|
|
351
|
-
await ports.updateTaskContext(task.id, newContext);
|
|
352
|
-
}
|
|
353
|
-
const cardUpdate = buildCardAfterGroupChange(event, ports, task, undefined, undefined);
|
|
354
|
-
return { ok: true, action: "disband_group", cardUpdate };
|
|
355
|
-
} catch (err) {
|
|
356
|
-
logger.error(`handleCardAction.disband_group failed: ${err.message}`);
|
|
357
|
-
return { ok: false, action: "disband_group", error: err.message };
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
function patchContext(contextStr, changes) {
|
|
361
|
-
let obj = {};
|
|
362
|
-
try {
|
|
363
|
-
const parsed = JSON.parse(contextStr);
|
|
364
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
365
|
-
obj = parsed;
|
|
366
|
-
}
|
|
367
|
-
} catch {
|
|
368
|
-
obj = {};
|
|
369
|
-
}
|
|
370
|
-
for (const [k, v] of Object.entries(changes)) {
|
|
371
|
-
if (v === undefined) {
|
|
372
|
-
delete obj[k];
|
|
373
|
-
} else {
|
|
374
|
-
obj[k] = v;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return JSON.stringify(obj);
|
|
378
|
-
}
|
|
379
|
-
function buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName) {
|
|
380
|
-
return {
|
|
381
|
-
id: task.id,
|
|
382
|
-
title: task.title,
|
|
383
|
-
description: task.description,
|
|
384
|
-
goals: task.goals,
|
|
385
|
-
parentTaskId: typeof task.parent_task_id === "number" && task.parent_task_id > 0 ? task.parent_task_id : undefined,
|
|
386
|
-
priority: task.priority,
|
|
387
|
-
type: task.type,
|
|
388
|
-
projectPath: task.project_path,
|
|
389
|
-
context: task.context,
|
|
390
|
-
currentStatus: task.current_status,
|
|
391
|
-
perTaskChatId,
|
|
392
|
-
perTaskChatName
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
async function deleteGroupChatViaLarkCli(chatId, profile) {
|
|
396
|
-
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
397
|
-
try {
|
|
398
|
-
const { stdout: authStdout } = await execAsync(`lark-cli ${profileFlag}auth status --json`, { timeout: 15000 });
|
|
399
|
-
const appId = JSON.parse(authStdout).appId;
|
|
400
|
-
if (!appId) {
|
|
401
|
-
throw new Error(`lark-cli auth status 未返回 appId: ${authStdout.slice(0, 200)}`);
|
|
402
|
-
}
|
|
403
|
-
const cmd = `lark-cli ${profileFlag}api DELETE /open-apis/im/v1/chats/${chatId}/members ` + `--params '{"member_id_type":"app_id"}' ` + `--data ${shellQuote(JSON.stringify({ id_list: [appId] }))} ` + `--as bot`;
|
|
404
|
-
const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
|
|
405
|
-
logger.debug(`deleteGroupChatViaLarkCli(${chatId}) bot 退群 ok: ` + `stdout=${stdout?.slice(0, 300)} stderr=${stderr?.slice(0, 300)}`);
|
|
406
|
-
} catch (err) {
|
|
407
|
-
logger.warn(`deleteGroupChatViaLarkCli(${chatId}) bot 退群失败(task.context 仍会清掉,` + `飞书侧群会保留):${err.message}`);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
function shellQuote(s) {
|
|
411
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
412
|
-
}
|
|
413
|
-
function truncateText(text, maxLen) {
|
|
414
|
-
if (!text)
|
|
415
|
-
return "";
|
|
416
|
-
if (text.length <= maxLen)
|
|
417
|
-
return text;
|
|
418
|
-
return text.substring(0, Math.max(0, maxLen - 3)) + "...";
|
|
419
|
-
}
|
|
420
|
-
async function createGroupChatViaLarkCli(opts) {
|
|
421
|
-
const profileFlag = opts.profile ? `--profile ${opts.profile} ` : "";
|
|
422
|
-
const usersFlag = opts.initialUserIds && opts.initialUserIds.length > 0 ? `--users ${shellQuote(opts.initialUserIds.join(","))} ` : "";
|
|
423
|
-
const cmd = `lark-cli ${profileFlag}im +chat-create --name ${shellQuote(truncateText(opts.name, 60))} ` + usersFlag + `--description ${shellQuote(truncateText(opts.description, 100))} --as bot`;
|
|
424
|
-
const { stdout } = await execAsync(cmd, { timeout: 60000 });
|
|
425
|
-
const m = stdout.match(/"chat_id"\s*:\s*"(oc_[a-f0-9]+)"/);
|
|
426
|
-
if (!m) {
|
|
427
|
-
throw new Error(`lark-cli +chat-create 返回无法解析: ${stdout.slice(0, 300)}`);
|
|
428
|
-
}
|
|
429
|
-
return m[1];
|
|
430
|
-
}
|
|
431
|
-
|
|
432
215
|
// src/env/event-source/timer-handler.ts
|
|
433
216
|
init_decorator();
|
|
217
|
+
init_logger();
|
|
434
218
|
var DEFAULT_TIMER_INTERVAL_MS = 10 * 60 * 1000;
|
|
219
|
+
var SKIPPED_TICK_LOG_EVERY = 20;
|
|
220
|
+
var timerLogger = createLogger("timer:event-source");
|
|
435
221
|
var SELF_REMINDER_MESSAGE = `[Timer Self-Reminder]
|
|
436
222
|
请读取最近 1 周处理的 task 信息,分析用户当前的工作目标和重点任务。
|
|
437
223
|
基于分析结果,自主构建有价值的任务来推进用户目标。
|
|
@@ -449,6 +235,19 @@ var TIMER_OPTIONS_KEY_SCHEDULE = "schedule";
|
|
|
449
235
|
var TIMER_OPTIONS_KEY_SESSION_ID = "sessionId";
|
|
450
236
|
var TIMER_OPTIONS_KEY_SKIP_WHEN_RUNNING = "skipWhenRunning";
|
|
451
237
|
var TIME_PATTERN = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
238
|
+
function buildFireSpanAttributes(ctx) {
|
|
239
|
+
const attrs = {
|
|
240
|
+
mode: ctx.mode,
|
|
241
|
+
sourceId: ctx.sourceId,
|
|
242
|
+
skippedSinceLastFire: ctx.skippedSinceLastFire
|
|
243
|
+
};
|
|
244
|
+
if (ctx.entry !== undefined) {
|
|
245
|
+
attrs.scheduleTime = ctx.entry.time;
|
|
246
|
+
if (ctx.entry.label !== undefined)
|
|
247
|
+
attrs.scheduleLabel = ctx.entry.label;
|
|
248
|
+
}
|
|
249
|
+
return attrs;
|
|
250
|
+
}
|
|
452
251
|
function isValidScheduleTime(time) {
|
|
453
252
|
return typeof time === "string" && TIME_PATTERN.test(time);
|
|
454
253
|
}
|
|
@@ -539,6 +338,9 @@ class TimerInstance {
|
|
|
539
338
|
scheduleSessionId;
|
|
540
339
|
skipWhenRunning;
|
|
541
340
|
firedKeys = new Set;
|
|
341
|
+
firedTicks = 0;
|
|
342
|
+
skippedTicks = 0;
|
|
343
|
+
skippedSinceLastFire = 0;
|
|
542
344
|
now;
|
|
543
345
|
status = "created";
|
|
544
346
|
timerId;
|
|
@@ -594,6 +396,13 @@ class TimerInstance {
|
|
|
594
396
|
getStatus() {
|
|
595
397
|
return this.status;
|
|
596
398
|
}
|
|
399
|
+
getTickStats() {
|
|
400
|
+
return {
|
|
401
|
+
firedTicks: this.firedTicks,
|
|
402
|
+
skippedTicks: this.skippedTicks,
|
|
403
|
+
skippedSinceLastFire: this.skippedSinceLastFire
|
|
404
|
+
};
|
|
405
|
+
}
|
|
597
406
|
onEvent(handler) {
|
|
598
407
|
this.eventHandler = handler;
|
|
599
408
|
}
|
|
@@ -610,13 +419,31 @@ class TimerInstance {
|
|
|
610
419
|
allTasks = this.getRunningTasks();
|
|
611
420
|
} catch (error) {
|
|
612
421
|
console.warn(`[TimerInstance] Failed to get running tasks, skipping tick: ${error}`);
|
|
422
|
+
this.recordSkip();
|
|
613
423
|
return;
|
|
614
424
|
}
|
|
615
425
|
const hasRunning = Array.isArray(allTasks) && allTasks.some((t) => t.status === "running");
|
|
616
426
|
if (hasRunning) {
|
|
427
|
+
this.recordSkip();
|
|
617
428
|
return;
|
|
618
429
|
}
|
|
619
|
-
|
|
430
|
+
this.fireTick({
|
|
431
|
+
mode: "interval",
|
|
432
|
+
sourceId: this.config.id,
|
|
433
|
+
skippedSinceLastFire: this.skippedSinceLastFire
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
recordSkip() {
|
|
437
|
+
this.skippedTicks++;
|
|
438
|
+
this.skippedSinceLastFire++;
|
|
439
|
+
if (this.skippedTicks % SKIPPED_TICK_LOG_EVERY === 0) {
|
|
440
|
+
timerLogger.debug(`[TimerInstance] ${this.config.id}: ${this.skippedTicks} ticks skipped ` + `(${this.skippedSinceLastFire} since last fire, mode=${this.isScheduleMode ? "schedule" : "interval"})`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
fireTick(ctx) {
|
|
444
|
+
this.firedTicks++;
|
|
445
|
+
this.skippedSinceLastFire = 0;
|
|
446
|
+
const event = this.buildSelfReminderEvent(ctx.entry);
|
|
620
447
|
this.eventHandler?.(event);
|
|
621
448
|
}
|
|
622
449
|
tickSchedule() {
|
|
@@ -625,6 +452,7 @@ class TimerInstance {
|
|
|
625
452
|
this.invalidScheduleWarned = true;
|
|
626
453
|
console.warn(`[TimerInstance] Timer ${this.config.id} has an invalid or empty schedule configuration; ` + `no events will be fired. Check options.schedule (expected [{ time: "HH:MM", prompt: "..." }]).`);
|
|
627
454
|
}
|
|
455
|
+
this.recordSkip();
|
|
628
456
|
return;
|
|
629
457
|
}
|
|
630
458
|
const now = new Date(this.now());
|
|
@@ -646,6 +474,7 @@ class TimerInstance {
|
|
|
646
474
|
hasRunning = Array.isArray(allTasks) && allTasks.some((t) => t.status === "running");
|
|
647
475
|
} catch (error) {
|
|
648
476
|
console.warn(`[TimerInstance] Failed to get running tasks, skipping tick: ${error}`);
|
|
477
|
+
this.recordSkip();
|
|
649
478
|
return;
|
|
650
479
|
}
|
|
651
480
|
if (hasRunning) {
|
|
@@ -654,10 +483,15 @@ class TimerInstance {
|
|
|
654
483
|
}
|
|
655
484
|
this.firedKeys.add(firedKey);
|
|
656
485
|
this.pruneFiredKeys(dateKey);
|
|
657
|
-
|
|
658
|
-
|
|
486
|
+
this.fireTick({
|
|
487
|
+
mode: "schedule",
|
|
488
|
+
sourceId: this.config.id,
|
|
489
|
+
skippedSinceLastFire: this.skippedSinceLastFire,
|
|
490
|
+
entry
|
|
491
|
+
});
|
|
659
492
|
return;
|
|
660
493
|
}
|
|
494
|
+
this.recordSkip();
|
|
661
495
|
}
|
|
662
496
|
pruneFiredKeys(currentDateKey) {
|
|
663
497
|
if (this.firedKeys.size <= 32)
|
|
@@ -714,14 +548,14 @@ class TimerInstance {
|
|
|
714
548
|
}
|
|
715
549
|
}
|
|
716
550
|
__legacyDecorateClassTS([
|
|
717
|
-
TracedAs("timer.tick", {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
], TimerInstance.prototype, "
|
|
551
|
+
TracedAs("timer.tick.fire", {
|
|
552
|
+
recordParams: true,
|
|
553
|
+
recordResult: false,
|
|
554
|
+
log: false,
|
|
555
|
+
paramFilter: (args) => buildFireSpanAttributes(args[0]),
|
|
556
|
+
flattenParamFilter: true
|
|
557
|
+
})
|
|
558
|
+
], TimerInstance.prototype, "fireTick", null);
|
|
725
559
|
var timerHandler = {
|
|
726
560
|
type: "timer",
|
|
727
561
|
validateConfig(config) {
|
|
@@ -1711,9 +1545,17 @@ class WecomWsBridge {
|
|
|
1711
1545
|
|
|
1712
1546
|
// src/env/event-source/wecom-card-action-handler.ts
|
|
1713
1547
|
init_logger();
|
|
1714
|
-
var
|
|
1715
|
-
var
|
|
1548
|
+
var logger = createLogger("wecom:card-action");
|
|
1549
|
+
var cardActionInflight = new Map;
|
|
1716
1550
|
var ROY_TASK_PREFIX = "roy-task-";
|
|
1551
|
+
function parseRoySessionId(cardTaskId) {
|
|
1552
|
+
if (!cardTaskId.startsWith(ROY_SESSION_PREFIX))
|
|
1553
|
+
return null;
|
|
1554
|
+
const rest = cardTaskId.slice(ROY_SESSION_PREFIX.length);
|
|
1555
|
+
const sepIdx = rest.indexOf(WECOM_SESSION_TASK_SEP);
|
|
1556
|
+
const id = (sepIdx >= 0 ? rest.slice(0, sepIdx) : rest).trim();
|
|
1557
|
+
return id || null;
|
|
1558
|
+
}
|
|
1717
1559
|
function parseRoyTaskId(cardTaskId) {
|
|
1718
1560
|
if (!cardTaskId.startsWith(ROY_TASK_PREFIX))
|
|
1719
1561
|
return null;
|
|
@@ -1745,6 +1587,7 @@ function extractWecomCardAction(frame) {
|
|
|
1745
1587
|
eventKey: String(tce.event_key ?? ""),
|
|
1746
1588
|
cardTaskId,
|
|
1747
1589
|
taskId: parseRoyTaskId(cardTaskId),
|
|
1590
|
+
sessionId: parseRoySessionId(cardTaskId),
|
|
1748
1591
|
userId: from.userid ?? "",
|
|
1749
1592
|
reqId: f.headers?.req_id ?? ""
|
|
1750
1593
|
};
|
|
@@ -1761,6 +1604,10 @@ var ACTION_CARD_LABEL = {
|
|
|
1761
1604
|
reopen: {
|
|
1762
1605
|
statusLabel: "\uD83D\uDD04 进行中",
|
|
1763
1606
|
note: "用户已重新打开任务"
|
|
1607
|
+
},
|
|
1608
|
+
refresh_activity: {
|
|
1609
|
+
statusLabel: "⚡ 活性已刷新",
|
|
1610
|
+
note: "已读取子会话最新活性"
|
|
1764
1611
|
}
|
|
1765
1612
|
};
|
|
1766
1613
|
async function handleWecomCardAction(frame, ports) {
|
|
@@ -1769,17 +1616,26 @@ async function handleWecomCardAction(frame, ports) {
|
|
|
1769
1616
|
return { ok: false, error: "not a template_card_event frame" };
|
|
1770
1617
|
}
|
|
1771
1618
|
const inflightKey = `${action.cardTaskId}:${action.eventKey}`;
|
|
1772
|
-
const inflight =
|
|
1619
|
+
const inflight = cardActionInflight.get(inflightKey);
|
|
1773
1620
|
if (inflight) {
|
|
1774
|
-
|
|
1621
|
+
logger.warn(`handleWecomCardAction: ${inflightKey} 已有同操作在处理中` + `(疑似用户重复点击),本次忽略。`);
|
|
1775
1622
|
return { ok: false, error: "同操作处理中,已忽略重复点击" };
|
|
1776
1623
|
}
|
|
1777
1624
|
const job = doHandle(action, ports).finally(() => {
|
|
1778
|
-
|
|
1625
|
+
cardActionInflight.delete(inflightKey);
|
|
1779
1626
|
});
|
|
1780
|
-
|
|
1627
|
+
cardActionInflight.set(inflightKey, job);
|
|
1781
1628
|
return job;
|
|
1782
1629
|
}
|
|
1630
|
+
async function probeSessionActivityByAssociatedTask(taskId, ports) {
|
|
1631
|
+
if (!ports.findSessionCardByTaskId || !ports.loadSessionActivity) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
const card = await ports.findSessionCardByTaskId(taskId);
|
|
1635
|
+
if (!card?.sessionId)
|
|
1636
|
+
return;
|
|
1637
|
+
return await ports.loadSessionActivity(card.sessionId);
|
|
1638
|
+
}
|
|
1783
1639
|
async function doHandle(action, ports) {
|
|
1784
1640
|
let actionType;
|
|
1785
1641
|
if (action.eventKey === WecomCardActionKey.MARK_DONE) {
|
|
@@ -1788,9 +1644,25 @@ async function doHandle(action, ports) {
|
|
|
1788
1644
|
actionType = "reopen";
|
|
1789
1645
|
} else if (action.eventKey === WecomCardActionKey.VIEW) {
|
|
1790
1646
|
actionType = "view";
|
|
1647
|
+
} else if (action.eventKey === WecomCardActionKey.REFRESH_ACTIVITY) {
|
|
1648
|
+
actionType = "refresh_activity";
|
|
1649
|
+
} else if (action.eventKey === WecomCardActionKey.CREATE_LARK_GROUP) {
|
|
1650
|
+
actionType = "create_lark_group";
|
|
1791
1651
|
} else {
|
|
1792
1652
|
return { ok: false, error: `unknown event_key: ${action.eventKey}` };
|
|
1793
1653
|
}
|
|
1654
|
+
if (action.sessionId !== null) {
|
|
1655
|
+
if (actionType === "refresh_activity") {
|
|
1656
|
+
return await doHandleSessionActivity(action, ports);
|
|
1657
|
+
}
|
|
1658
|
+
if (actionType === "create_lark_group") {
|
|
1659
|
+
return await doHandleSessionCreateLarkGroup(action, ports);
|
|
1660
|
+
}
|
|
1661
|
+
return {
|
|
1662
|
+
ok: false,
|
|
1663
|
+
error: `action ${actionType} requires a task card (got session ${action.sessionId})`
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1794
1666
|
if (action.taskId === null && actionType !== "view") {
|
|
1795
1667
|
return {
|
|
1796
1668
|
ok: false,
|
|
@@ -1806,20 +1678,20 @@ async function doHandle(action, ports) {
|
|
|
1806
1678
|
}
|
|
1807
1679
|
} catch (err) {
|
|
1808
1680
|
opError = err instanceof Error ? err.message : String(err);
|
|
1809
|
-
|
|
1681
|
+
logger.error(`handleWecomCardAction: task op failed for #${action.taskId}: ${opError}`);
|
|
1810
1682
|
}
|
|
1811
1683
|
const label = ACTION_CARD_LABEL[actionType];
|
|
1812
|
-
const note = actionType === "view" ? label.note : opError ? `${label.note}(操作失败:${opError})` : label.note;
|
|
1684
|
+
const note = actionType === "view" || actionType === "refresh_activity" ? label.note : opError ? `${label.note}(操作失败:${opError})` : label.note;
|
|
1813
1685
|
let timeline;
|
|
1814
1686
|
if ((actionType === "view" || actionType === "reopen") && action.taskId !== null) {
|
|
1815
1687
|
try {
|
|
1816
1688
|
timeline = ports.loadTimeline?.(action.taskId) ?? [];
|
|
1817
1689
|
if (timeline.length === 0) {
|
|
1818
|
-
|
|
1690
|
+
logger.warn(`handleWecomCardAction: task #${action.taskId} 无时间线记录,` + `降级为简单按钮态更新`);
|
|
1819
1691
|
}
|
|
1820
1692
|
} catch (err) {
|
|
1821
1693
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1822
|
-
|
|
1694
|
+
logger.warn(`handleWecomCardAction: loadTimeline failed for ` + `task #${action.taskId}: ${msg}(降级为简单按钮态更新)`);
|
|
1823
1695
|
timeline = [];
|
|
1824
1696
|
}
|
|
1825
1697
|
}
|
|
@@ -1831,13 +1703,43 @@ async function doHandle(action, ports) {
|
|
|
1831
1703
|
}
|
|
1832
1704
|
} catch (err) {
|
|
1833
1705
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1834
|
-
|
|
1706
|
+
logger.warn(`handleWecomCardAction: loadTaskTitle failed for ` + `task #${action.taskId}: ${msg}(降级为无标题更新)`);
|
|
1707
|
+
}
|
|
1708
|
+
let activity;
|
|
1709
|
+
let activityTerminal;
|
|
1710
|
+
if (actionType === "refresh_activity" && action.taskId !== null) {
|
|
1711
|
+
try {
|
|
1712
|
+
const r = await ports.loadSubSessionActivity?.(action.taskId);
|
|
1713
|
+
activity = r?.activity ?? null;
|
|
1714
|
+
activityTerminal = r?.terminal === true;
|
|
1715
|
+
} catch (err) {
|
|
1716
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1717
|
+
logger.warn(`handleWecomCardAction: loadSubSessionActivity failed for ` + `task #${action.taskId}: ${msg}(降级为「暂无数据」)`);
|
|
1718
|
+
activity = null;
|
|
1719
|
+
}
|
|
1720
|
+
if (activity === null) {
|
|
1721
|
+
try {
|
|
1722
|
+
const fallback = await probeSessionActivityByAssociatedTask(action.taskId, ports);
|
|
1723
|
+
if (fallback?.activity) {
|
|
1724
|
+
activity = fallback.activity;
|
|
1725
|
+
activityTerminal = fallback.terminal === true;
|
|
1726
|
+
logger.info(`handleWecomCardAction: task #${action.taskId} bgTask 反查失败,` + `已回退 session store probe(messageCount=${fallback.activity.messageCount})`);
|
|
1727
|
+
}
|
|
1728
|
+
} catch (err) {
|
|
1729
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1730
|
+
logger.warn(`handleWecomCardAction: session probe fallback failed for ` + `task #${action.taskId}: ${msg}(保持「暂无数据」)`);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1835
1733
|
}
|
|
1836
1734
|
try {
|
|
1837
|
-
await ports.updateCard(action.reqId, action.cardTaskId, label.statusLabel, note, timeline, actionType, taskExtra
|
|
1735
|
+
await ports.updateCard(action.reqId, action.cardTaskId, label.statusLabel, note, timeline, actionType, activity === undefined ? taskExtra : {
|
|
1736
|
+
...taskExtra ?? {},
|
|
1737
|
+
activity,
|
|
1738
|
+
...activityTerminal !== undefined ? { activityTerminal } : {}
|
|
1739
|
+
});
|
|
1838
1740
|
} catch (err) {
|
|
1839
1741
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1840
|
-
|
|
1742
|
+
logger.error(`handleWecomCardAction: updateCard failed: ${msg}`);
|
|
1841
1743
|
return {
|
|
1842
1744
|
ok: false,
|
|
1843
1745
|
action: actionType,
|
|
@@ -1847,11 +1749,164 @@ async function doHandle(action, ports) {
|
|
|
1847
1749
|
if (opError) {
|
|
1848
1750
|
return { ok: false, action: actionType, error: opError };
|
|
1849
1751
|
}
|
|
1850
|
-
|
|
1752
|
+
logger.info(`handleWecomCardAction: task #${action.taskId} → ${actionType} ` + `(by ${action.userId})`);
|
|
1851
1753
|
return { ok: true, action: actionType };
|
|
1852
1754
|
}
|
|
1755
|
+
async function doHandleSessionActivity(action, ports) {
|
|
1756
|
+
const sessionId = action.sessionId;
|
|
1757
|
+
let state;
|
|
1758
|
+
try {
|
|
1759
|
+
state = await ports.loadSessionCardState?.(sessionId);
|
|
1760
|
+
} catch (err) {
|
|
1761
|
+
logger.warn(`handleWecomCardAction: loadSessionCardState failed for ${sessionId}: ` + `${err instanceof Error ? err.message : String(err)}(降级为仅活性)`);
|
|
1762
|
+
state = undefined;
|
|
1763
|
+
}
|
|
1764
|
+
if (!state) {
|
|
1765
|
+
logger.warn(`handleWecomCardAction: 无 session 卡持久层记录(${sessionId})` + ` ⇒ 刷新卡降级为仅活性(生命周期/操作记录不可用)`);
|
|
1766
|
+
}
|
|
1767
|
+
let activity = null;
|
|
1768
|
+
let probeTerminal;
|
|
1769
|
+
try {
|
|
1770
|
+
const r = await ports.loadSessionActivity?.(sessionId);
|
|
1771
|
+
activity = r?.activity ?? null;
|
|
1772
|
+
probeTerminal = r?.terminal;
|
|
1773
|
+
} catch (err) {
|
|
1774
|
+
logger.warn(`handleWecomCardAction: loadSessionActivity failed for ${sessionId}: ` + `${err instanceof Error ? err.message : String(err)}(降级为「暂无数据」)`);
|
|
1775
|
+
activity = null;
|
|
1776
|
+
}
|
|
1777
|
+
let sessionTask;
|
|
1778
|
+
try {
|
|
1779
|
+
const t = await ports.loadSessionTaskInfo?.(sessionId);
|
|
1780
|
+
if (t && typeof t.id === "number") {
|
|
1781
|
+
sessionTask = t;
|
|
1782
|
+
}
|
|
1783
|
+
} catch (err) {
|
|
1784
|
+
logger.warn(`handleWecomCardAction: loadSessionTaskInfo failed for ${sessionId}: ` + `${err instanceof Error ? err.message : String(err)}(降级为无任务行)`);
|
|
1785
|
+
}
|
|
1786
|
+
let linkedChatId;
|
|
1787
|
+
try {
|
|
1788
|
+
const g = await ports.findGroupChatForSession?.(sessionId);
|
|
1789
|
+
if (g && typeof g.chatId === "string" && g.chatId.trim()) {
|
|
1790
|
+
linkedChatId = g.chatId.trim();
|
|
1791
|
+
}
|
|
1792
|
+
} catch (err) {
|
|
1793
|
+
logger.warn(`handleWecomCardAction: findGroupChatForSession failed for ${sessionId}: ` + `${err instanceof Error ? err.message : String(err)}(当作没有群)`);
|
|
1794
|
+
}
|
|
1795
|
+
if (ports.updateSessionCard) {
|
|
1796
|
+
try {
|
|
1797
|
+
await ports.updateSessionCard(action.reqId, {
|
|
1798
|
+
sessionId,
|
|
1799
|
+
cardTaskId: action.cardTaskId,
|
|
1800
|
+
state,
|
|
1801
|
+
activity,
|
|
1802
|
+
...sessionTask ? { task: sessionTask } : {},
|
|
1803
|
+
...linkedChatId ? { perTaskChatId: linkedChatId } : {}
|
|
1804
|
+
});
|
|
1805
|
+
logger.info(`handleWecomCardAction: session ${sessionId} → refresh ` + `(lifecycle=${state?.lifecycle ?? "unknown"}, by ${action.userId})`);
|
|
1806
|
+
return { ok: true, action: "refresh_activity" };
|
|
1807
|
+
} catch (err) {
|
|
1808
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1809
|
+
logger.error(`handleWecomCardAction: session card update failed for ${sessionId}: ${msg}`);
|
|
1810
|
+
return {
|
|
1811
|
+
ok: false,
|
|
1812
|
+
action: "refresh_activity",
|
|
1813
|
+
error: `card update failed: ${msg}`
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
try {
|
|
1818
|
+
await ports.updateCard(action.reqId, action.cardTaskId, ACTION_CARD_LABEL.refresh_activity.statusLabel, ACTION_CARD_LABEL.refresh_activity.note, undefined, "refresh_activity", {
|
|
1819
|
+
activity,
|
|
1820
|
+
activityTerminal: state?.lifecycle !== undefined ? state.lifecycle !== "running" : probeTerminal ?? false
|
|
1821
|
+
});
|
|
1822
|
+
} catch (err) {
|
|
1823
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1824
|
+
logger.error(`handleWecomCardAction: session card update failed for ${sessionId}: ${msg}`);
|
|
1825
|
+
return { ok: false, action: "refresh_activity", error: `card update failed: ${msg}` };
|
|
1826
|
+
}
|
|
1827
|
+
logger.info(`handleWecomCardAction: session ${sessionId} → refresh ` + `(lifecycle=${state?.lifecycle ?? "unknown"}, by ${action.userId})`);
|
|
1828
|
+
return { ok: true, action: "refresh_activity" };
|
|
1829
|
+
}
|
|
1830
|
+
async function doHandleSessionCreateLarkGroup(action, ports) {
|
|
1831
|
+
const sessionId = action.sessionId;
|
|
1832
|
+
let existingChatId;
|
|
1833
|
+
try {
|
|
1834
|
+
const g = await ports.findGroupChatForSession?.(sessionId);
|
|
1835
|
+
if (g && typeof g.chatId === "string" && g.chatId.trim()) {
|
|
1836
|
+
existingChatId = g.chatId.trim();
|
|
1837
|
+
}
|
|
1838
|
+
} catch (err) {
|
|
1839
|
+
logger.warn(`handleWecomCardAction: findGroupChatForSession(${sessionId}) 失败` + `(当作没有群): ${err instanceof Error ? err.message : String(err)}`);
|
|
1840
|
+
}
|
|
1841
|
+
if (existingChatId) {
|
|
1842
|
+
logger.info(`handleWecomCardAction: session ${sessionId} 已有飞书群 ` + `${existingChatId}(幂等命中,不重复建群)`);
|
|
1843
|
+
await updateSessionCardWithGroup(ports, action, sessionId, existingChatId);
|
|
1844
|
+
return { ok: true, action: "create_lark_group", newChatId: existingChatId };
|
|
1845
|
+
}
|
|
1846
|
+
if (!ports.createLarkGroupForSession) {
|
|
1847
|
+
return {
|
|
1848
|
+
ok: false,
|
|
1849
|
+
action: "create_lark_group",
|
|
1850
|
+
error: "ports.createLarkGroupForSession not injected " + "(wecom-cli-handler wiring missing)"
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
let chatId;
|
|
1854
|
+
try {
|
|
1855
|
+
chatId = await ports.createLarkGroupForSession(sessionId);
|
|
1856
|
+
} catch (err) {
|
|
1857
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1858
|
+
logger.error(`handleWecomCardAction: createLarkGroupForSession(${sessionId}) ` + `failed: ${msg}`);
|
|
1859
|
+
await updateSessionCardWithGroup(ports, action, sessionId, undefined, `⚠️ 建群失败:${msg.slice(0, 80)}`);
|
|
1860
|
+
return { ok: false, action: "create_lark_group", error: msg };
|
|
1861
|
+
}
|
|
1862
|
+
logger.info(`handleWecomCardAction: session ${sessionId} → 飞书群 ${chatId} ` + `创建成功(by ${action.userId})`);
|
|
1863
|
+
await updateSessionCardWithGroup(ports, action, sessionId, chatId);
|
|
1864
|
+
if (ports.notifyGroupLink) {
|
|
1865
|
+
try {
|
|
1866
|
+
await ports.notifyGroupLink({
|
|
1867
|
+
sessionId,
|
|
1868
|
+
chatId,
|
|
1869
|
+
...action.userId ? { userId: action.userId } : {}
|
|
1870
|
+
});
|
|
1871
|
+
} catch (err) {
|
|
1872
|
+
logger.warn(`handleWecomCardAction: notifyGroupLink(${sessionId} → ${chatId}) ` + `失败(不阻断建群结果): ` + `${err instanceof Error ? err.message : String(err)}`);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
return { ok: true, action: "create_lark_group", newChatId: chatId };
|
|
1876
|
+
}
|
|
1877
|
+
async function updateSessionCardWithGroup(ports, action, sessionId, chatId, errorHint) {
|
|
1878
|
+
if (!ports.updateSessionCard)
|
|
1879
|
+
return;
|
|
1880
|
+
try {
|
|
1881
|
+
let state;
|
|
1882
|
+
try {
|
|
1883
|
+
state = await ports.loadSessionCardState?.(sessionId);
|
|
1884
|
+
} catch {
|
|
1885
|
+
state = undefined;
|
|
1886
|
+
}
|
|
1887
|
+
let activity = null;
|
|
1888
|
+
try {
|
|
1889
|
+
const r = await ports.loadSessionActivity?.(sessionId);
|
|
1890
|
+
activity = r?.activity ?? null;
|
|
1891
|
+
} catch {
|
|
1892
|
+
activity = null;
|
|
1893
|
+
}
|
|
1894
|
+
await ports.updateSessionCard(action.reqId, {
|
|
1895
|
+
sessionId,
|
|
1896
|
+
cardTaskId: action.cardTaskId,
|
|
1897
|
+
...state ? { state } : {},
|
|
1898
|
+
activity,
|
|
1899
|
+
...chatId ? { perTaskChatId: chatId } : {},
|
|
1900
|
+
...errorHint ? { errorHint } : {}
|
|
1901
|
+
});
|
|
1902
|
+
logger.info(`handleWecomCardAction: session card updated after group change ` + `(session=${sessionId}, chatId=${chatId ?? "none"})`);
|
|
1903
|
+
} catch (err) {
|
|
1904
|
+
logger.warn(`handleWecomCardAction: session card update after group change ` + `failed for ${sessionId}(不阻断建群结果): ` + `${err instanceof Error ? err.message : String(err)}`);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1853
1907
|
function buildWecomCardUpdateBody(input) {
|
|
1854
1908
|
const refreshable = input.action === "view" || input.action === "reopen";
|
|
1909
|
+
const activityRefreshable = input.action === "refresh_activity";
|
|
1855
1910
|
const frame = buildWecomCardUpdateFrame({
|
|
1856
1911
|
reqId: "placeholder",
|
|
1857
1912
|
taskId: input.cardTaskId,
|
|
@@ -1860,7 +1915,44 @@ function buildWecomCardUpdateBody(input) {
|
|
|
1860
1915
|
timeline: input.timeline,
|
|
1861
1916
|
taskTitle: input.taskTitle,
|
|
1862
1917
|
taskNumber: input.taskNumber,
|
|
1863
|
-
buttonOverrides: refreshable ? [{ text: "刷新详情", key: WecomCardActionKey.VIEW, style: 1 }] :
|
|
1918
|
+
buttonOverrides: refreshable ? [{ text: "刷新详情", key: WecomCardActionKey.VIEW, style: 1 }] : activityRefreshable ? [
|
|
1919
|
+
{ text: "刷新详情", key: WecomCardActionKey.VIEW, style: 2 },
|
|
1920
|
+
{ text: "刷新活性", key: WecomCardActionKey.REFRESH_ACTIVITY, style: 1 }
|
|
1921
|
+
] : [{ text: "已处理", key: "task_done_ack", style: 4 }],
|
|
1922
|
+
...input.activity !== undefined ? { activity: input.activity } : {},
|
|
1923
|
+
...input.activityTerminal !== undefined ? { activityTerminal: input.activityTerminal } : {}
|
|
1924
|
+
});
|
|
1925
|
+
return { cmd: frame.cmd, body: frame.body };
|
|
1926
|
+
}
|
|
1927
|
+
function buildWecomSessionCardUpdateBody(input) {
|
|
1928
|
+
const state = input.state;
|
|
1929
|
+
const lifecycle = state?.lifecycle ?? "unknown";
|
|
1930
|
+
let milestones;
|
|
1931
|
+
if (state?.milestones) {
|
|
1932
|
+
try {
|
|
1933
|
+
const arr = JSON.parse(state.milestones);
|
|
1934
|
+
if (Array.isArray(arr) && arr.length > 0) {
|
|
1935
|
+
milestones = arr;
|
|
1936
|
+
}
|
|
1937
|
+
} catch {
|
|
1938
|
+
milestones = undefined;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
const task = input.task ?? (state?.associatedTaskId !== undefined ? { id: state.associatedTaskId } : undefined);
|
|
1942
|
+
const frame = buildWecomSessionUpdateFrame({
|
|
1943
|
+
reqId: input.reqIdForTest ?? "placeholder",
|
|
1944
|
+
cardTaskId: input.cardTaskId,
|
|
1945
|
+
sessionId: input.sessionId,
|
|
1946
|
+
lifecycle,
|
|
1947
|
+
...state?.title ? { title: state.title } : {},
|
|
1948
|
+
...state?.agentType ? { agentType: state.agentType } : {},
|
|
1949
|
+
...state?.startedAt ? { startedAt: state.startedAt } : {},
|
|
1950
|
+
...task ? { task } : {},
|
|
1951
|
+
...milestones ? { milestones } : {},
|
|
1952
|
+
...input.perTaskChatId ? { perTaskChatId: input.perTaskChatId } : {},
|
|
1953
|
+
...input.errorHint ? { errorHint: input.errorHint } : {},
|
|
1954
|
+
...input.activity !== undefined ? { activity: input.activity } : {},
|
|
1955
|
+
...input.now !== undefined ? { now: input.now } : {}
|
|
1864
1956
|
});
|
|
1865
1957
|
return { cmd: frame.cmd, body: frame.body };
|
|
1866
1958
|
}
|
|
@@ -2046,7 +2138,8 @@ function buildWecomReplyChannel(input) {
|
|
|
2046
2138
|
wecomChatId: rawChatId,
|
|
2047
2139
|
wecomUserId: userId,
|
|
2048
2140
|
...input.sourceId ? { sourceId: input.sourceId } : {},
|
|
2049
|
-
...input.reqId ? { reqId: input.reqId } : {}
|
|
2141
|
+
...input.reqId ? { reqId: input.reqId } : {},
|
|
2142
|
+
statusNotifications: input.statusNotifications === true
|
|
2050
2143
|
}
|
|
2051
2144
|
};
|
|
2052
2145
|
}
|
|
@@ -2161,6 +2254,22 @@ function parseWecomFrame(raw) {
|
|
|
2161
2254
|
reqId: frame.headers?.req_id
|
|
2162
2255
|
};
|
|
2163
2256
|
}
|
|
2257
|
+
var WECOM_DEFAULT_EXTEND_TOOLS = ["wecom_respond"];
|
|
2258
|
+
function resolveWecomExtendTools(config) {
|
|
2259
|
+
const out = [];
|
|
2260
|
+
const push = (name) => {
|
|
2261
|
+
if (typeof name !== "string")
|
|
2262
|
+
return;
|
|
2263
|
+
const trimmed = name.trim();
|
|
2264
|
+
if (trimmed.length === 0)
|
|
2265
|
+
return;
|
|
2266
|
+
if (!out.includes(trimmed))
|
|
2267
|
+
out.push(trimmed);
|
|
2268
|
+
};
|
|
2269
|
+
WECOM_DEFAULT_EXTEND_TOOLS.forEach(push);
|
|
2270
|
+
(config?.extendTools ?? []).forEach(push);
|
|
2271
|
+
return out;
|
|
2272
|
+
}
|
|
2164
2273
|
var WECOM_CHANNEL_PROMPT_SECTION_TITLE = "⭐ 企业微信回复规范(system prompt 注入版)";
|
|
2165
2274
|
function buildWecomChannelSystemPrompt(opts) {
|
|
2166
2275
|
const botLine = opts.botId ? `本渠道智能机器人 BotID:${opts.botId}。` : `本渠道未配置 BotID(使用事件回调里的 aibotid)。`;
|
|
@@ -2292,15 +2401,82 @@ class WecomCliInstance {
|
|
|
2292
2401
|
pluginInstances = new Map;
|
|
2293
2402
|
loadedPlugins = [];
|
|
2294
2403
|
cardActionPorts;
|
|
2404
|
+
sessionCardStore;
|
|
2295
2405
|
constructor(config) {
|
|
2296
2406
|
this.config = config;
|
|
2297
2407
|
}
|
|
2298
2408
|
setCardActionPorts(ports) {
|
|
2299
2409
|
this.cardActionPorts = ports;
|
|
2410
|
+
this.applySessionCardPorts();
|
|
2300
2411
|
}
|
|
2301
2412
|
hasCardActionPorts() {
|
|
2302
2413
|
return this.cardActionPorts !== undefined;
|
|
2303
2414
|
}
|
|
2415
|
+
setSessionCardStore(store) {
|
|
2416
|
+
this.sessionCardStore = store;
|
|
2417
|
+
this.applySessionCardPorts();
|
|
2418
|
+
}
|
|
2419
|
+
hasSessionCardStore() {
|
|
2420
|
+
return this.sessionCardStore !== undefined;
|
|
2421
|
+
}
|
|
2422
|
+
applySessionCardPorts() {
|
|
2423
|
+
if (!this.cardActionPorts || !this.sessionCardStore)
|
|
2424
|
+
return;
|
|
2425
|
+
const store = this.sessionCardStore;
|
|
2426
|
+
this.cardActionPorts.loadSessionCardState ??= (sessionId) => store.get(sessionId);
|
|
2427
|
+
this.cardActionPorts.findSessionCardByTaskId ??= (taskId) => {
|
|
2428
|
+
const hits = store.list().filter((s) => s.associatedTaskId === taskId);
|
|
2429
|
+
if (hits.length === 0)
|
|
2430
|
+
return;
|
|
2431
|
+
const open = hits.filter((s) => s.closedAt === undefined);
|
|
2432
|
+
const pool = open.length > 0 ? open : hits;
|
|
2433
|
+
return pool.reduce((a, b) => b.updatedAt > a.updatedAt ? b : a);
|
|
2434
|
+
};
|
|
2435
|
+
this.cardActionPorts.updateSessionCard ??= async (reqId, input) => {
|
|
2436
|
+
const { cmd, body } = buildWecomSessionCardUpdateBody({
|
|
2437
|
+
cardTaskId: input.cardTaskId,
|
|
2438
|
+
sessionId: input.sessionId,
|
|
2439
|
+
...input.state ? { state: input.state } : {},
|
|
2440
|
+
activity: input.activity,
|
|
2441
|
+
...input.task ? { task: input.task } : {},
|
|
2442
|
+
...input.perTaskChatId ? { perTaskChatId: input.perTaskChatId } : {},
|
|
2443
|
+
...input.errorHint ? { errorHint: input.errorHint } : {},
|
|
2444
|
+
...input.now !== undefined ? { now: input.now } : {}
|
|
2445
|
+
});
|
|
2446
|
+
const err = this.wsBridge?.sendFrameWithReqId({
|
|
2447
|
+
cmd,
|
|
2448
|
+
headers: { req_id: reqId },
|
|
2449
|
+
body
|
|
2450
|
+
});
|
|
2451
|
+
if (err)
|
|
2452
|
+
throw err;
|
|
2453
|
+
};
|
|
2454
|
+
this.cardActionPorts.loadSessionTaskInfo ??= async (sessionId) => {
|
|
2455
|
+
const taskComponent = this.environment?.getComponent?.("task");
|
|
2456
|
+
const getter = taskComponent?.getTaskIdBySessionId;
|
|
2457
|
+
if (typeof getter !== "function")
|
|
2458
|
+
return;
|
|
2459
|
+
const taskId = await getter.call(taskComponent, sessionId);
|
|
2460
|
+
if (typeof taskId !== "number")
|
|
2461
|
+
return;
|
|
2462
|
+
const withOps = await taskComponent?.getTaskWithOperations?.(taskId);
|
|
2463
|
+
if (!withOps)
|
|
2464
|
+
return { id: taskId };
|
|
2465
|
+
const ops = Array.isArray(withOps.operations) ? withOps.operations : [];
|
|
2466
|
+
return {
|
|
2467
|
+
id: taskId,
|
|
2468
|
+
...typeof withOps.title === "string" ? { title: withOps.title } : {},
|
|
2469
|
+
...typeof withOps.status === "string" ? { status: withOps.status } : {},
|
|
2470
|
+
...typeof withOps.progress === "number" ? { progress: withOps.progress } : {},
|
|
2471
|
+
...ops.length > 0 ? {
|
|
2472
|
+
milestones: ops.slice(-10).map((o) => ({
|
|
2473
|
+
title: String(o.milestoneTitle ?? o.milestoneType ?? ""),
|
|
2474
|
+
timestamp: String(o.timestamp ?? "")
|
|
2475
|
+
}))
|
|
2476
|
+
} : {}
|
|
2477
|
+
};
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2304
2480
|
environment;
|
|
2305
2481
|
setEnvironment(env) {
|
|
2306
2482
|
this.environment = env;
|
|
@@ -2322,6 +2498,7 @@ class WecomCliInstance {
|
|
|
2322
2498
|
const metadata = {
|
|
2323
2499
|
...parsed.metadata,
|
|
2324
2500
|
systemPrompt: channelPrompt,
|
|
2501
|
+
extendTools: resolveWecomExtendTools(this.config),
|
|
2325
2502
|
reqId: parsed.reqId,
|
|
2326
2503
|
plugins: this.config.handleRule?.plugins || [],
|
|
2327
2504
|
pluginEnabled: Object.fromEntries((this.config.handleRule?.plugins || []).map((p) => [
|
|
@@ -2335,7 +2512,8 @@ class WecomCliInstance {
|
|
|
2335
2512
|
chatType: metadata.chatType,
|
|
2336
2513
|
userId: metadata.senderId,
|
|
2337
2514
|
sourceId: this.config.id,
|
|
2338
|
-
reqId: parsed.reqId
|
|
2515
|
+
reqId: parsed.reqId,
|
|
2516
|
+
statusNotifications: this.config.statusNotifications === true
|
|
2339
2517
|
});
|
|
2340
2518
|
const event = {
|
|
2341
2519
|
id: `es-${this.config.id}-${Date.now()}`,
|
|
@@ -2408,18 +2586,29 @@ class WecomCliInstance {
|
|
|
2408
2586
|
if (this.cardActionPorts)
|
|
2409
2587
|
return;
|
|
2410
2588
|
const taskComponent = this.environment?.getComponent?.("task");
|
|
2411
|
-
if (!
|
|
2412
|
-
console.warn(`[wecom-cli] cardActionPorts 未注入:
|
|
2589
|
+
if (!this.wsBridge) {
|
|
2590
|
+
console.warn(`[wecom-cli] cardActionPorts 未注入:wsBridge 缺失(没有通道发更新帧)。` + `卡片按钮点击会被跳过。`);
|
|
2413
2591
|
return;
|
|
2414
2592
|
}
|
|
2593
|
+
const taskOpsAvailable = typeof taskComponent?.updateTask === "function";
|
|
2594
|
+
if (!taskOpsAvailable) {
|
|
2595
|
+
console.warn(`[wecom-cli] taskComponent.updateTask 不可用 ⇒ 「标记完成 / 重新打开」` + `降级为报错;session 卡「刷新」不受影响(Task #5364)。`);
|
|
2596
|
+
}
|
|
2597
|
+
const requireTaskOps = () => {
|
|
2598
|
+
if (!taskOpsAvailable) {
|
|
2599
|
+
throw new Error("task ops unavailable (taskComponent.updateTask missing)");
|
|
2600
|
+
}
|
|
2601
|
+
};
|
|
2415
2602
|
this.cardActionPorts = {
|
|
2416
2603
|
markTaskDone: async (taskId) => {
|
|
2604
|
+
requireTaskOps();
|
|
2417
2605
|
await taskComponent.updateTask(taskId, {
|
|
2418
2606
|
status: "completed",
|
|
2419
2607
|
current_status: "用户在企微卡片点「标记完成」确认完成"
|
|
2420
2608
|
});
|
|
2421
2609
|
},
|
|
2422
2610
|
reopenTask: async (taskId) => {
|
|
2611
|
+
requireTaskOps();
|
|
2423
2612
|
await taskComponent.updateTask(taskId, {
|
|
2424
2613
|
status: "active",
|
|
2425
2614
|
current_status: "用户在企微卡片点「重新打开」"
|
|
@@ -2433,7 +2622,9 @@ class WecomCliInstance {
|
|
|
2433
2622
|
timeline,
|
|
2434
2623
|
action,
|
|
2435
2624
|
taskTitle: extra?.taskTitle,
|
|
2436
|
-
taskNumber: extra?.taskNumber
|
|
2625
|
+
taskNumber: extra?.taskNumber,
|
|
2626
|
+
...extra?.activity !== undefined ? { activity: extra.activity } : {},
|
|
2627
|
+
...extra?.activityTerminal !== undefined ? { activityTerminal: extra.activityTerminal } : {}
|
|
2437
2628
|
});
|
|
2438
2629
|
const frame = {
|
|
2439
2630
|
cmd,
|
|
@@ -2489,7 +2680,114 @@ class WecomCliInstance {
|
|
|
2489
2680
|
}
|
|
2490
2681
|
}
|
|
2491
2682
|
};
|
|
2492
|
-
|
|
2683
|
+
this.cardActionPorts.loadSubSessionActivity = createActivityProbe({
|
|
2684
|
+
listBackgroundTasks: () => {
|
|
2685
|
+
const tc = this.environment?.getComponent?.("task");
|
|
2686
|
+
return tc?.getBackgroundTaskManager?.()?.listTasks?.();
|
|
2687
|
+
},
|
|
2688
|
+
getSubSessionActivity: (bgId) => {
|
|
2689
|
+
const tc = this.environment?.getComponent?.("task");
|
|
2690
|
+
return tc?.getBackgroundTaskManager?.()?.getSubSessionActivity?.(bgId);
|
|
2691
|
+
},
|
|
2692
|
+
getLastMessage: async (subSessionId) => {
|
|
2693
|
+
const sc = this.environment?.getComponent?.("session");
|
|
2694
|
+
if (!sc?.getMessages)
|
|
2695
|
+
return;
|
|
2696
|
+
const tail = await sc.getMessages(subSessionId, { offset: -1, limit: 1 });
|
|
2697
|
+
return toActivityMessage(tail?.[tail.length - 1]);
|
|
2698
|
+
},
|
|
2699
|
+
isTerminalTask: async (taskId) => {
|
|
2700
|
+
const tc = this.environment?.getComponent?.("task");
|
|
2701
|
+
const raw = tc?.getTask?.call(tc, taskId);
|
|
2702
|
+
const t = raw && typeof raw.then === "function" ? await raw : raw;
|
|
2703
|
+
return t?.status === "completed" || t?.status === "cancelled" || t?.status === "failed" || t?.status === "archived";
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
this.cardActionPorts.loadSessionActivity = createSessionActivityProbe({
|
|
2707
|
+
getSession: async (sessionId) => {
|
|
2708
|
+
const sc = this.environment?.getComponent?.("session");
|
|
2709
|
+
if (!sc?.get)
|
|
2710
|
+
return;
|
|
2711
|
+
const s = await sc.get(sessionId);
|
|
2712
|
+
if (!s)
|
|
2713
|
+
return;
|
|
2714
|
+
return {
|
|
2715
|
+
...typeof s.messageCount === "number" ? { messageCount: s.messageCount } : {},
|
|
2716
|
+
...typeof s.updatedAt === "number" ? { updatedAt: s.updatedAt } : {}
|
|
2717
|
+
};
|
|
2718
|
+
},
|
|
2719
|
+
getLastMessage: async (sessionId) => {
|
|
2720
|
+
const sc = this.environment?.getComponent?.("session");
|
|
2721
|
+
if (!sc?.getMessages)
|
|
2722
|
+
return;
|
|
2723
|
+
const tail = await sc.getMessages(sessionId, { offset: -1, limit: 1 });
|
|
2724
|
+
return toActivityMessage(tail?.[tail.length - 1]);
|
|
2725
|
+
},
|
|
2726
|
+
getLifecycle: (sessionId) => {
|
|
2727
|
+
const tc = this.environment?.getComponent?.("task");
|
|
2728
|
+
const tasks = tc?.getBackgroundTaskManager?.()?.listTasks?.() ?? [];
|
|
2729
|
+
const hit = tasks.find((t) => t?.subSessionId === sessionId);
|
|
2730
|
+
return mapBgTaskStatusToSessionLifecycle(hit?.status);
|
|
2731
|
+
}
|
|
2732
|
+
});
|
|
2733
|
+
this.sessionCardStore ??= getSharedWecomSessionCardStore();
|
|
2734
|
+
this.applySessionCardPorts();
|
|
2735
|
+
if (this.cardActionPorts) {
|
|
2736
|
+
const sessionComponent = () => this.environment?.getComponent?.("session");
|
|
2737
|
+
this.cardActionPorts.findGroupChatForSession ??= async (sessionId) => {
|
|
2738
|
+
const sc = sessionComponent();
|
|
2739
|
+
if (typeof sc?.findGroupChatForSession !== "function") {
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
return sc.findGroupChatForSession(sessionId);
|
|
2743
|
+
};
|
|
2744
|
+
this.cardActionPorts.createLarkGroupForSession ??= async (sessionId) => {
|
|
2745
|
+
const sc = sessionComponent();
|
|
2746
|
+
let title;
|
|
2747
|
+
try {
|
|
2748
|
+
const s = await sc?.get?.(sessionId);
|
|
2749
|
+
if (typeof s?.title === "string" && s.title.trim()) {
|
|
2750
|
+
title = s.title.trim();
|
|
2751
|
+
}
|
|
2752
|
+
} catch {}
|
|
2753
|
+
const name = title ?? sessionId.slice(-20);
|
|
2754
|
+
const chatId = await createGroupChatViaLarkCli({
|
|
2755
|
+
name: `会话 ${name}`.slice(0, 60),
|
|
2756
|
+
description: `roy-agent session ${sessionId}`.slice(0, 100)
|
|
2757
|
+
});
|
|
2758
|
+
try {
|
|
2759
|
+
await sc?.attachGroupSession?.({
|
|
2760
|
+
chatId,
|
|
2761
|
+
originSessionId: sessionId,
|
|
2762
|
+
title: name
|
|
2763
|
+
});
|
|
2764
|
+
} catch (err) {
|
|
2765
|
+
console.warn(`[wecom-cli] attachGroupSession(${chatId} ← ${sessionId}) ` + `失败(不阻断建群): ${err}`);
|
|
2766
|
+
}
|
|
2767
|
+
return chatId;
|
|
2768
|
+
};
|
|
2769
|
+
this.cardActionPorts.notifyGroupLink ??= async (input) => {
|
|
2770
|
+
const link = `${DEFAULT_APP_LINK_BASE}/client/chat/open?openChatId=${input.chatId}`;
|
|
2771
|
+
const text = `\uD83D\uDCE8 会话 ${input.sessionId} 的关联飞书群已建立:` + `[点击进入群](${link})`;
|
|
2772
|
+
let target = input.userId || "";
|
|
2773
|
+
if (!target) {
|
|
2774
|
+
try {
|
|
2775
|
+
target = this.sessionCardStore?.get(input.sessionId)?.chatId ?? "";
|
|
2776
|
+
} catch {
|
|
2777
|
+
target = "";
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
if (!target) {
|
|
2781
|
+
console.warn(`[wecom-cli] notifyGroupLink: 无法定位企微会话 ` + `(userId 缺失且 session store 无 chatId),跳过通知`);
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
const r = await sendWecomCliMarkdown({ chatId: target, content: text });
|
|
2785
|
+
if (!r.ok) {
|
|
2786
|
+
console.warn(`[wecom-cli] notifyGroupLink cli failed: ${r.ok ? "unknown" : r.error}`);
|
|
2787
|
+
}
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
console.info(`[wecom-cli] cardActionPorts 已注入(task ops + bridge.respond 更新 + timeline + taskTitle + activity + session task info + lark group)`);
|
|
2493
2791
|
}
|
|
2494
2792
|
getWecomCardStateStore() {
|
|
2495
2793
|
const plugin = this.pluginInstances.get("WecomTaskNotifyPlugin");
|
|
@@ -2567,6 +2865,7 @@ class WecomCliInstance {
|
|
|
2567
2865
|
const plugin = importPluginShared(pluginName, getWecomPluginFactories(), {
|
|
2568
2866
|
sourceId: this.config.id,
|
|
2569
2867
|
pluginName,
|
|
2868
|
+
statusNotifications: this.config.statusNotifications === true,
|
|
2570
2869
|
...pluginConfig
|
|
2571
2870
|
});
|
|
2572
2871
|
if (!plugin)
|
|
@@ -2625,7 +2924,8 @@ class WecomCliInstance {
|
|
|
2625
2924
|
chatType: parsed.metadata.chatType,
|
|
2626
2925
|
userId: parsed.metadata.senderId,
|
|
2627
2926
|
sourceId: this.config.id,
|
|
2628
|
-
reqId: parsed.reqId
|
|
2927
|
+
reqId: parsed.reqId,
|
|
2928
|
+
statusNotifications: this.config.statusNotifications === true
|
|
2629
2929
|
});
|
|
2630
2930
|
notifyReactFailure({
|
|
2631
2931
|
error: err,
|
|
@@ -2745,7 +3045,8 @@ class WecomCliInstance {
|
|
|
2745
3045
|
chatType: parsed.metadata.chatType,
|
|
2746
3046
|
userId: parsed.metadata.senderId,
|
|
2747
3047
|
sourceId: this.config.id,
|
|
2748
|
-
reqId: parsed.reqId
|
|
3048
|
+
reqId: parsed.reqId,
|
|
3049
|
+
statusNotifications: this.config.statusNotifications === true
|
|
2749
3050
|
});
|
|
2750
3051
|
const event = {
|
|
2751
3052
|
metadata: parsed.metadata,
|
|
@@ -3606,7 +3907,7 @@ ${USAGE2}`
|
|
|
3606
3907
|
}
|
|
3607
3908
|
|
|
3608
3909
|
// src/env/event-source/command-registry.ts
|
|
3609
|
-
var
|
|
3910
|
+
var logger2 = createLogger("command-registry");
|
|
3610
3911
|
var COMMAND_PREFIX = "##command##:";
|
|
3611
3912
|
var COMMAND_PREFIX_LOWER = COMMAND_PREFIX.toLowerCase();
|
|
3612
3913
|
function stripLeadingMentions(text) {
|
|
@@ -3679,7 +3980,7 @@ ${this.renderHelp()}`
|
|
|
3679
3980
|
});
|
|
3680
3981
|
} catch (err) {
|
|
3681
3982
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3682
|
-
|
|
3983
|
+
logger2.error(`[CommandRegistry] command "${subcommand}" failed: ${msg}`);
|
|
3683
3984
|
return { ok: false, error: `命令执行失败:${msg}` };
|
|
3684
3985
|
}
|
|
3685
3986
|
}
|
|
@@ -3710,7 +4011,7 @@ async function handleCommandMessage(text, opts = {}) {
|
|
|
3710
4011
|
if (!invocation)
|
|
3711
4012
|
return null;
|
|
3712
4013
|
const registry = opts.registry ?? getGlobalCommandRegistry();
|
|
3713
|
-
|
|
4014
|
+
logger2.info(`[handleCommandMessage] dispatching "${invocation.subcommand}" args=${JSON.stringify(invocation.args)}`);
|
|
3714
4015
|
return registry.dispatch(invocation.subcommand, invocation.args, {
|
|
3715
4016
|
rawText: text,
|
|
3716
4017
|
configPath: opts.configPath
|
|
@@ -3719,7 +4020,7 @@ async function handleCommandMessage(text, opts = {}) {
|
|
|
3719
4020
|
|
|
3720
4021
|
// src/env/event-source/command-card-action.ts
|
|
3721
4022
|
init_logger();
|
|
3722
|
-
var
|
|
4023
|
+
var logger3 = createLogger("command-card-action");
|
|
3723
4024
|
function isCommandCardAction(event) {
|
|
3724
4025
|
const av = event.actionValue;
|
|
3725
4026
|
if (av && typeof av === "object") {
|
|
@@ -3776,7 +4077,7 @@ async function handleCommandCardAction(event, ports) {
|
|
|
3776
4077
|
return { ok: false, error: "无法识别的 command 卡片动作" };
|
|
3777
4078
|
}
|
|
3778
4079
|
const executed = `${call.subcommand} ${call.args.join(" ")}`;
|
|
3779
|
-
|
|
4080
|
+
logger3.info(`[handleCommandCardAction] 执行 ${executed}`);
|
|
3780
4081
|
let result;
|
|
3781
4082
|
try {
|
|
3782
4083
|
result = await ports.executeCommand(call.subcommand, call.args, {
|
|
@@ -3784,7 +4085,7 @@ async function handleCommandCardAction(event, ports) {
|
|
|
3784
4085
|
});
|
|
3785
4086
|
} catch (err) {
|
|
3786
4087
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3787
|
-
|
|
4088
|
+
logger3.error(`[handleCommandCardAction] ${executed} 执行异常:${msg}`);
|
|
3788
4089
|
return { ok: false, executed, error: `命令执行异常:${msg}` };
|
|
3789
4090
|
}
|
|
3790
4091
|
if (!result.ok) {
|
|
@@ -3797,7 +4098,7 @@ async function handleCommandCardAction(event, ports) {
|
|
|
3797
4098
|
await ports.patchCard(event.token, card, event.profile);
|
|
3798
4099
|
} catch (err) {
|
|
3799
4100
|
patchError = err.message;
|
|
3800
|
-
|
|
4101
|
+
logger3.error(`[handleCommandCardAction] patch 卡片失败:${patchError}`);
|
|
3801
4102
|
}
|
|
3802
4103
|
}
|
|
3803
4104
|
return { ok: true, executed, card, patchError };
|
|
@@ -3805,8 +4106,8 @@ async function handleCommandCardAction(event, ports) {
|
|
|
3805
4106
|
|
|
3806
4107
|
// src/env/event-source/event-source-handlers.ts
|
|
3807
4108
|
init_logger();
|
|
3808
|
-
var
|
|
3809
|
-
var
|
|
4109
|
+
var execAsync = promisify(execSync);
|
|
4110
|
+
var logger4 = createLogger("lark-cli:event-source");
|
|
3810
4111
|
var CHILD_READY_TIMEOUT_MS = 5000;
|
|
3811
4112
|
var GRACEFUL_STOP_TIMEOUT_MS = 5000;
|
|
3812
4113
|
var CARD_ACTION_EVENT_TYPE = "card.action.trigger";
|
|
@@ -3830,18 +4131,52 @@ function matchEventType(eventType, patterns) {
|
|
|
3830
4131
|
}
|
|
3831
4132
|
return false;
|
|
3832
4133
|
}
|
|
4134
|
+
function buildSlimRawData(rawEvent) {
|
|
4135
|
+
const event = rawEvent ?? {};
|
|
4136
|
+
const innerEvent = event.event;
|
|
4137
|
+
const message = innerEvent?.message ?? event.message;
|
|
4138
|
+
const sender = innerEvent?.sender ?? event.sender;
|
|
4139
|
+
const slim = {};
|
|
4140
|
+
const chatId = message?.chat_id ?? event.chat_id;
|
|
4141
|
+
if (chatId !== undefined)
|
|
4142
|
+
slim.chat_id = chatId;
|
|
4143
|
+
const messageId = message?.message_id ?? event.message_id ?? event.id;
|
|
4144
|
+
if (messageId !== undefined)
|
|
4145
|
+
slim.message_id = messageId;
|
|
4146
|
+
const rawSenderId = sender?.sender_id ?? event.sender_id;
|
|
4147
|
+
let senderId;
|
|
4148
|
+
if (typeof rawSenderId === "string") {
|
|
4149
|
+
senderId = rawSenderId;
|
|
4150
|
+
} else if (rawSenderId && typeof rawSenderId === "object") {
|
|
4151
|
+
const obj = rawSenderId;
|
|
4152
|
+
senderId = obj.open_id ?? obj.user_id ?? obj.union_id;
|
|
4153
|
+
}
|
|
4154
|
+
if (senderId !== undefined)
|
|
4155
|
+
slim.sender_id = senderId;
|
|
4156
|
+
const chatType = message?.chat_type ?? event.chat_type;
|
|
4157
|
+
if (chatType !== undefined)
|
|
4158
|
+
slim.chat_type = chatType;
|
|
4159
|
+
const content = message?.content ?? event.content;
|
|
4160
|
+
if (content !== undefined)
|
|
4161
|
+
slim.content = content;
|
|
4162
|
+
const mentions = message?.mentions ?? event.mentions;
|
|
4163
|
+
if (mentions !== undefined)
|
|
4164
|
+
slim.mentions = mentions;
|
|
4165
|
+
return slim;
|
|
4166
|
+
}
|
|
3833
4167
|
function buildLarkRecommendedAction(opts) {
|
|
3834
|
-
const { profile, chatType, appId, chatId, messageId } = opts;
|
|
4168
|
+
const { profile, chatType, appId, chatId, messageId, senderId } = opts;
|
|
3835
4169
|
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
3836
4170
|
const profileWarn = profile ? `(⚠️ 必须用 profile="${profile}" 调 lark-cli;openId per-app 隔离,换 profile 会失败)` : "";
|
|
3837
4171
|
const isGroup = chatType === "group";
|
|
3838
|
-
const
|
|
4172
|
+
const senderIdForPrompt = senderId || "<senderId>";
|
|
4173
|
+
const scopeHint = isGroup ? `群聊消息;回复到 replyTo.chatId。如需 @发件人,用结构化 at 节点 {"tag":"at","user_id":"${senderIdForPrompt}"}(不要写 <at> 文本标签)` : "p2p 私聊消息;回复到 replyTo.chatId";
|
|
3839
4174
|
const scopeWord = isGroup ? "群聊" : "p2p 私聊";
|
|
3840
4175
|
const action = [
|
|
3841
4176
|
`处理飞书 ${scopeWord}消息并以机器人身份回复${profileWarn}。`,
|
|
3842
4177
|
`scope: ${scopeHint}。`,
|
|
3843
4178
|
``,
|
|
3844
|
-
...larkStaticRuleLines(profileFlag),
|
|
4179
|
+
...larkStaticRuleLines(profileFlag, senderIdForPrompt),
|
|
3845
4180
|
``,
|
|
3846
4181
|
`## 必须使用 replyTo 里给定的值`,
|
|
3847
4182
|
`profile / chatId / messageId / --as bot 必须原样取自 recommendedAction.replyTo;不要凭印象自己拼接。`
|
|
@@ -3863,7 +4198,7 @@ function buildLarkRecommendedAction(opts) {
|
|
|
3863
4198
|
};
|
|
3864
4199
|
}
|
|
3865
4200
|
var LARK_CHANNEL_PROMPT_SECTION_TITLE = "⭐ 飞书回复规范(system prompt 注入版)";
|
|
3866
|
-
var LARK_PROMPT_LENGTH_BUDGET =
|
|
4201
|
+
var LARK_PROMPT_LENGTH_BUDGET = 5900;
|
|
3867
4202
|
function larkReplyContextSection(profileFlag) {
|
|
3868
4203
|
return [
|
|
3869
4204
|
``,
|
|
@@ -3873,7 +4208,7 @@ function larkReplyContextSection(profileFlag) {
|
|
|
3873
4208
|
].join(`
|
|
3874
4209
|
`);
|
|
3875
4210
|
}
|
|
3876
|
-
function larkStaticRuleLines(profileFlag) {
|
|
4211
|
+
function larkStaticRuleLines(profileFlag, senderIdForPrompt) {
|
|
3877
4212
|
return [
|
|
3878
4213
|
`## ⭐ 关键 0:整次回复只发一条结构化 post(禁分段)`,
|
|
3879
4214
|
`> 指令优先级:本 prompt 与 lark-* skill 文档冲突时**以本 prompt 为准**`,
|
|
@@ -3909,6 +4244,13 @@ function larkStaticRuleLines(profileFlag) {
|
|
|
3909
4244
|
` ]}}`,
|
|
3910
4245
|
``,
|
|
3911
4246
|
`- @mention:用 {"tag":"at","user_id":"ou_xxx"}(user_id 填对方 open_id;@所有人填 "all")`,
|
|
4247
|
+
`- **群聊必须 @ sender**:若接收的消息是 \`chat_type: group\`,回复时 content **必须**保留结构化 at 节点 @ 发送者(user_id 填 senderId,即消息发送者的 open_id);**禁止只用引用回复**(quote/reply 不算 @,群聊里对方收不到提醒);私聊(p2p)不需要。群聊可跑示例(照抄,--arg uid 填消息发送者 open_id):`,
|
|
4248
|
+
` body_file="$(mktemp /tmp/lark-ask.XXXXXX.md)"`,
|
|
4249
|
+
` cat > "$body_file" <<'EOF2'`,
|
|
4250
|
+
` 回复正文……`,
|
|
4251
|
+
` EOF2`,
|
|
4252
|
+
` content="$(jq -nc --rawfile md "$body_file" --arg uid "${senderIdForPrompt}" '{zh_cn:{content:[[{tag:"at",user_id:$uid}],[{tag:"md",text:$md}]]}}')"`,
|
|
4253
|
+
` lark-cli ${profileFlag}im +messages-reply --message-id <replyTo.messageId> --as bot --msg-type post --content "$content"`,
|
|
3912
4254
|
`- 正文:全部放进 {"tag":"md","text":"..."} 这一个节点里`,
|
|
3913
4255
|
`- 不需要 @ 时,content 里只放 md 节点:{"zh_cn":{"content":[[{"tag":"md","text":"..."}]]}}`,
|
|
3914
4256
|
`- md 节点支持 CommonMark 0.31 + GFM:ATX 标题(#~######)/ 表格 / 任务列表 / 加粗 / 围栏代码块 / 超链接`,
|
|
@@ -3926,7 +4268,7 @@ function larkStaticRuleLines(profileFlag) {
|
|
|
3926
4268
|
` cat > "$body_file" <<'LARK_REPLY_EOF'`,
|
|
3927
4269
|
` ...(回复的 Markdown 正文,可多行 / 含表格 / 含特殊字符;不要写 <at> 标签)`,
|
|
3928
4270
|
` LARK_REPLY_EOF`,
|
|
3929
|
-
` content="$(jq -nc --rawfile md "$body_file" --arg uid "
|
|
4271
|
+
` content="$(jq -nc --rawfile md "$body_file" --arg uid "${senderIdForPrompt}" '{zh_cn:{content:[[{tag:"at",user_id:$uid}],[{tag:"md",text:$md}]]}}')"`,
|
|
3930
4272
|
` lark-cli ${profileFlag}im +messages-reply --message-id <取自 replyTo.messageId> --as bot --msg-type post --content "$content"`,
|
|
3931
4273
|
``,
|
|
3932
4274
|
`不需要 @ 某人时,把 jq 表达式换成 '{zh_cn:{content:[[{tag:"md",text:$md}]]}}'。`,
|
|
@@ -3999,12 +4341,13 @@ function buildLarkChannelSystemPrompt(opts) {
|
|
|
3999
4341
|
larkReplyContextSection(profileFlag)
|
|
4000
4342
|
].join(`
|
|
4001
4343
|
`);
|
|
4002
|
-
return header + larkStaticRuleLines(profileFlag).join(`
|
|
4344
|
+
return header + larkStaticRuleLines(profileFlag, "<senderId>").join(`
|
|
4003
4345
|
`) + footer;
|
|
4004
4346
|
}
|
|
4005
|
-
function buildLarkLeanAction(chatType) {
|
|
4347
|
+
function buildLarkLeanAction(chatType, senderId) {
|
|
4006
4348
|
const isGroup = chatType === "group";
|
|
4007
|
-
const
|
|
4349
|
+
const senderIdForPrompt = senderId || "<senderId>";
|
|
4350
|
+
const scopeHint = isGroup ? `群聊消息;回复到 replyTo.chatId。如需 @发件人,用结构化 at 节点 {"tag":"at","user_id":"${senderIdForPrompt}"}(不要写 <at> 文本标签)` : "p2p 私聊消息;回复到 replyTo.chatId";
|
|
4008
4351
|
return [
|
|
4009
4352
|
`处理飞书${isGroup ? "群聊" : "p2p 私聊"}消息并回复。回复规范见 system prompt「${LARK_CHANNEL_PROMPT_SECTION_TITLE}」章节。`,
|
|
4010
4353
|
`scope: ${scopeHint}。`,
|
|
@@ -4087,11 +4430,12 @@ function extractMetadata(rawEvent, eventType) {
|
|
|
4087
4430
|
chatType: metadata.chatType,
|
|
4088
4431
|
appId: metadata.appId,
|
|
4089
4432
|
chatId: metadata.chatId,
|
|
4090
|
-
messageId: metadata.messageId
|
|
4433
|
+
messageId: metadata.messageId,
|
|
4434
|
+
senderId: metadata.senderId
|
|
4091
4435
|
});
|
|
4092
4436
|
} else {
|
|
4093
4437
|
recommendedAction = {
|
|
4094
|
-
action: buildLarkLeanAction(metadata.chatType),
|
|
4438
|
+
action: buildLarkLeanAction(metadata.chatType, metadata.senderId),
|
|
4095
4439
|
replyTo: {
|
|
4096
4440
|
appId: metadata.appId,
|
|
4097
4441
|
profile: profile2,
|
|
@@ -4117,7 +4461,8 @@ function extractMetadata(rawEvent, eventType) {
|
|
|
4117
4461
|
chatId: messageData?.chat_id,
|
|
4118
4462
|
messageId: messageData?.message_id,
|
|
4119
4463
|
params: {
|
|
4120
|
-
userId: senderOpenId
|
|
4464
|
+
userId: senderOpenId,
|
|
4465
|
+
statusNotifications: event.statusNotifications === true
|
|
4121
4466
|
}
|
|
4122
4467
|
};
|
|
4123
4468
|
return { metadata, replyChannel, recommendedAction };
|
|
@@ -4135,8 +4480,8 @@ class LarkCliInstance {
|
|
|
4135
4480
|
cardActionPorts;
|
|
4136
4481
|
setCardActionPorts(ports) {
|
|
4137
4482
|
this.cardActionPorts = ports;
|
|
4138
|
-
if (typeof
|
|
4139
|
-
|
|
4483
|
+
if (typeof logger4 !== "undefined" && logger4?.debug) {
|
|
4484
|
+
logger4.debug(`[LarkCliInstance] CardActionPorts injected: ` + `getTask=${typeof ports.getTask}, updateTaskContext=${typeof ports.updateTaskContext}, ` + `createGroupChat=${typeof ports.createGroupChat}, deleteGroupChat=${typeof ports.deleteGroupChat}, ` + `notifier=${typeof ports.notifier}`);
|
|
4140
4485
|
}
|
|
4141
4486
|
}
|
|
4142
4487
|
hasCardActionPorts() {
|
|
@@ -4172,7 +4517,7 @@ class LarkCliInstance {
|
|
|
4172
4517
|
if (this.status === "running")
|
|
4173
4518
|
return;
|
|
4174
4519
|
if (this.starting) {
|
|
4175
|
-
|
|
4520
|
+
logger4.warn(`[LarkCliInstance] start() 已在执行中,忽略重复调用`);
|
|
4176
4521
|
return;
|
|
4177
4522
|
}
|
|
4178
4523
|
this.starting = true;
|
|
@@ -4202,9 +4547,9 @@ class LarkCliInstance {
|
|
|
4202
4547
|
if (!consumesCardAction)
|
|
4203
4548
|
return;
|
|
4204
4549
|
if (this.config.eventTypes?.length && !matchEventType(CARD_ACTION_EVENT_TYPE, this.config.eventTypes)) {
|
|
4205
|
-
|
|
4550
|
+
logger4.warn(`[LarkCliInstance] eventKeys 含 ${CARD_ACTION_EVENT_TYPE},但 eventTypes=` + `${JSON.stringify(this.config.eventTypes)} 不含它 → 卡片点击会被**静默丢弃**。` + `请把 ${CARD_ACTION_EVENT_TYPE} 加进 eventTypes,或清空 eventTypes。`);
|
|
4206
4551
|
}
|
|
4207
|
-
|
|
4552
|
+
logger4.info(`[LarkCliInstance] 已监听 ${CARD_ACTION_EVENT_TYPE}。` + `⚠️ 若点击卡片无反应,先确认飞书开发者后台「事件与回调 → 回调配置」已开启该事件。`);
|
|
4208
4553
|
}
|
|
4209
4554
|
spawnConsumeChild(command) {
|
|
4210
4555
|
return spawn3("exec " + command, [], {
|
|
@@ -4244,14 +4589,14 @@ class LarkCliInstance {
|
|
|
4244
4589
|
const conflicts = keys.filter((k) => busy.has(k));
|
|
4245
4590
|
if (conflicts.length > 0) {
|
|
4246
4591
|
const profile = this.config.profile;
|
|
4247
|
-
|
|
4592
|
+
logger4.warn(`[LarkCliInstance] eventKey 已被其它 consumer 占用(lark-cli 一个 key 只允许一个 consumer):` + `${conflicts.join(", ")}。本 event-source 启动后可能 exit 2。` + `用 \`lark-cli${profile ? ` --profile ${profile}` : ""} event status\` 查看,必要时 \`event stop\`。`);
|
|
4248
4593
|
}
|
|
4249
4594
|
}
|
|
4250
4595
|
async probeBusyEventKeys() {
|
|
4251
4596
|
const profile = this.config.profile;
|
|
4252
4597
|
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
4253
4598
|
try {
|
|
4254
|
-
const { stdout } = await
|
|
4599
|
+
const { stdout } = await execAsync(`lark-cli ${profileFlag}event status --json`, {
|
|
4255
4600
|
timeout: 5000
|
|
4256
4601
|
});
|
|
4257
4602
|
const parsed = JSON.parse(stdout);
|
|
@@ -4295,16 +4640,16 @@ class LarkCliInstance {
|
|
|
4295
4640
|
});
|
|
4296
4641
|
const handleExit = (code, source) => {
|
|
4297
4642
|
if (code !== 0 && code !== null) {
|
|
4298
|
-
|
|
4643
|
+
logger4.warn(`[LarkCliInstance] consume 子进程异常退出 code=${code} (${source}) cmd="${command}"`);
|
|
4299
4644
|
this.status = "error";
|
|
4300
4645
|
} else {
|
|
4301
|
-
|
|
4646
|
+
logger4.info(`[LarkCliInstance] consume 子进程已退出 code=${code} (${source}) cmd="${command}"`);
|
|
4302
4647
|
}
|
|
4303
4648
|
settle();
|
|
4304
4649
|
queueMicrotask(() => {
|
|
4305
4650
|
if (this.children.length > 0 && this.children.every((c) => !isChildAlive(c))) {
|
|
4306
4651
|
if (this.status !== "error") {
|
|
4307
|
-
|
|
4652
|
+
logger4.warn(`[LarkCliInstance] 所有 consume 子进程都已退出,状态从 running 改为 stopped`);
|
|
4308
4653
|
this.status = "stopped";
|
|
4309
4654
|
}
|
|
4310
4655
|
}
|
|
@@ -4313,12 +4658,12 @@ class LarkCliInstance {
|
|
|
4313
4658
|
child.on("exit", (code) => handleExit(code, "exit"));
|
|
4314
4659
|
child.on("close", (code) => handleExit(code, "close"));
|
|
4315
4660
|
child.on("error", (error) => {
|
|
4316
|
-
|
|
4661
|
+
logger4.error(`[LarkCliInstance] consume 子进程 error: ${error.message}`);
|
|
4317
4662
|
this.status = "error";
|
|
4318
4663
|
settle();
|
|
4319
4664
|
});
|
|
4320
4665
|
const timer = setTimeout(() => {
|
|
4321
|
-
|
|
4666
|
+
logger4.warn(`[LarkCliInstance] consume 子进程 ${CHILD_READY_TIMEOUT_MS}ms 内未见 ready marker: cmd="${command}"`);
|
|
4322
4667
|
settle();
|
|
4323
4668
|
}, CHILD_READY_TIMEOUT_MS);
|
|
4324
4669
|
ready.then(() => clearTimeout(timer));
|
|
@@ -4341,7 +4686,7 @@ class LarkCliInstance {
|
|
|
4341
4686
|
try {
|
|
4342
4687
|
child.kill("SIGTERM");
|
|
4343
4688
|
} catch (error) {
|
|
4344
|
-
|
|
4689
|
+
logger4.warn(`[LarkCliInstance] Error sending SIGTERM to ${child.pid}: ${error}`);
|
|
4345
4690
|
}
|
|
4346
4691
|
}
|
|
4347
4692
|
const allExited = Promise.all(children.map((child) => new Promise((resolve2) => {
|
|
@@ -4396,15 +4741,15 @@ class LarkCliInstance {
|
|
|
4396
4741
|
}
|
|
4397
4742
|
wireCardActionPorts() {
|
|
4398
4743
|
if (this.cardActionPorts) {
|
|
4399
|
-
|
|
4744
|
+
logger4.debug("[LarkCliInstance] cardActionPorts 已由外部注入,跳过默认 cross-wire");
|
|
4400
4745
|
return;
|
|
4401
4746
|
}
|
|
4402
4747
|
const taskComponent = this.environment?.getComponent?.("task");
|
|
4403
4748
|
if (!taskComponent?.getTask || !taskComponent?.updateTask) {
|
|
4404
|
-
|
|
4749
|
+
logger4.debug(`[LarkCliInstance] card.action.trigger 端口未注入:taskComponent 不可用` + `(getTask=${typeof taskComponent?.getTask}, updateTask=${typeof taskComponent?.updateTask})。` + `卡片按钮点击会被跳过。`);
|
|
4405
4750
|
return;
|
|
4406
4751
|
}
|
|
4407
|
-
const sharedNotifier = this.pluginInstances.get("LarkCliTaskNotifyPlugin")?.cardNotifier ?? this.pluginInstances.get("PerTaskGroupChatPlugin")?.cardNotifier;
|
|
4752
|
+
const sharedNotifier = this.pluginInstances.get("LarkCliTaskNotifyPlugin")?.cardNotifier ?? this.pluginInstances.get("PerTaskGroupChatPlugin")?.cardNotifier ?? getSharedLarkCardNotifier();
|
|
4408
4753
|
this.setCardActionPorts({
|
|
4409
4754
|
getTask: async (id) => {
|
|
4410
4755
|
const t = await taskComponent.getTask(id);
|
|
@@ -4428,9 +4773,119 @@ class LarkCliInstance {
|
|
|
4428
4773
|
},
|
|
4429
4774
|
createGroupChat: (opts) => createGroupChatViaLarkCli(opts),
|
|
4430
4775
|
deleteGroupChat: (chatId, profile) => deleteGroupChatViaLarkCli(chatId, profile),
|
|
4431
|
-
notifier: sharedNotifier ??
|
|
4776
|
+
notifier: sharedNotifier ?? getSharedLarkCardNotifier(),
|
|
4777
|
+
resolveSessionIdByTask: async (taskId) => {
|
|
4778
|
+
const fromNotifier = sharedNotifier?.getSessionIdByTask?.(taskId);
|
|
4779
|
+
if (fromNotifier)
|
|
4780
|
+
return fromNotifier;
|
|
4781
|
+
const tc = taskComponent;
|
|
4782
|
+
try {
|
|
4783
|
+
const rec = getSharedLarkCardStateStore().get(taskId);
|
|
4784
|
+
if (rec?.sessionId)
|
|
4785
|
+
return rec.sessionId;
|
|
4786
|
+
} catch {}
|
|
4787
|
+
return;
|
|
4788
|
+
},
|
|
4789
|
+
loadSessionActivity: createSessionActivityProbe({
|
|
4790
|
+
getSession: async (sessionId) => {
|
|
4791
|
+
const sc = this.environment?.getComponent?.("session");
|
|
4792
|
+
if (!sc?.get)
|
|
4793
|
+
return;
|
|
4794
|
+
const s = await sc.get(sessionId);
|
|
4795
|
+
if (!s)
|
|
4796
|
+
return;
|
|
4797
|
+
return {
|
|
4798
|
+
...typeof s.messageCount === "number" ? { messageCount: s.messageCount } : {},
|
|
4799
|
+
...typeof s.updatedAt === "number" ? { updatedAt: s.updatedAt } : {}
|
|
4800
|
+
};
|
|
4801
|
+
},
|
|
4802
|
+
getLastMessage: async (sessionId) => {
|
|
4803
|
+
const sc = this.environment?.getComponent?.("session");
|
|
4804
|
+
if (!sc?.getMessages)
|
|
4805
|
+
return;
|
|
4806
|
+
const tail = await sc.getMessages(sessionId, { offset: -1, limit: 1 });
|
|
4807
|
+
return toActivityMessage(tail?.[tail.length - 1]);
|
|
4808
|
+
},
|
|
4809
|
+
getLifecycle: (sessionId) => {
|
|
4810
|
+
const bgTasks = taskComponent.getBackgroundTaskManager?.()?.listTasks?.() ?? [];
|
|
4811
|
+
const hit = bgTasks.find((t) => t?.subSessionId === sessionId);
|
|
4812
|
+
return mapBgTaskStatusToSessionLifecycle(hit?.status);
|
|
4813
|
+
}
|
|
4814
|
+
}),
|
|
4815
|
+
loadSubSessionActivity: createActivityProbe({
|
|
4816
|
+
listBackgroundTasks: () => taskComponent.getBackgroundTaskManager?.()?.listTasks?.(),
|
|
4817
|
+
getSubSessionActivity: (bgId) => taskComponent.getBackgroundTaskManager?.()?.getSubSessionActivity?.(bgId),
|
|
4818
|
+
getLastMessage: async (subSessionId) => {
|
|
4819
|
+
const sessionComponent = this.environment?.getComponent?.("session");
|
|
4820
|
+
if (!sessionComponent?.getMessages)
|
|
4821
|
+
return;
|
|
4822
|
+
const tail = await sessionComponent.getMessages(subSessionId, {
|
|
4823
|
+
offset: -1,
|
|
4824
|
+
limit: 1
|
|
4825
|
+
});
|
|
4826
|
+
return toActivityMessage(tail?.[tail.length - 1]);
|
|
4827
|
+
},
|
|
4828
|
+
isTerminalTask: async (taskId) => {
|
|
4829
|
+
const t = await taskComponent.getTask?.(taskId);
|
|
4830
|
+
const status = t?.status;
|
|
4831
|
+
return status === "completed" || status === "cancelled" || status === "failed" || status === "archived";
|
|
4832
|
+
}
|
|
4833
|
+
}),
|
|
4834
|
+
loadSessionMessages: async (sessionId, limit) => {
|
|
4835
|
+
const sc = this.environment?.getComponent?.("session");
|
|
4836
|
+
if (!sc?.getMessages)
|
|
4837
|
+
return;
|
|
4838
|
+
const n = typeof limit === "number" && limit > 0 ? limit : RECENT_MESSAGE_LIMIT;
|
|
4839
|
+
const tail = await sc.getMessages(sessionId, {
|
|
4840
|
+
offset: -1,
|
|
4841
|
+
limit: n * 3
|
|
4842
|
+
});
|
|
4843
|
+
const calls = extractToolCallsFromMessages(tail);
|
|
4844
|
+
return calls.slice(-n);
|
|
4845
|
+
},
|
|
4846
|
+
attachGroupSession: async (args) => {
|
|
4847
|
+
const sessionComponent = this.environment?.getComponent?.("session");
|
|
4848
|
+
if (typeof sessionComponent?.attachGroupSession !== "function") {
|
|
4849
|
+
logger4.warn(`[LarkCliInstance] attachGroupSession 跳过:sessionComponent.attachGroupSession 不可用`);
|
|
4850
|
+
return;
|
|
4851
|
+
}
|
|
4852
|
+
await sessionComponent.attachGroupSession(args);
|
|
4853
|
+
logger4.info(`[LarkCliInstance] 群 session 已初始化:${args.chatId} ← ${args.originSessionId}` + `(独立会话 + 1 条 user 事件背景提示)`);
|
|
4854
|
+
},
|
|
4855
|
+
findGroupChatForSession: async (sessionId) => {
|
|
4856
|
+
const sessionComponent = this.environment?.getComponent?.("session");
|
|
4857
|
+
if (typeof sessionComponent?.findGroupChatForSession !== "function") {
|
|
4858
|
+
logger4.warn(`[LarkCliInstance] findGroupChatForSession 跳过:sessionComponent 不支持`);
|
|
4859
|
+
return;
|
|
4860
|
+
}
|
|
4861
|
+
return await sessionComponent.findGroupChatForSession(sessionId);
|
|
4862
|
+
},
|
|
4863
|
+
detachGroupSession: async (chatId) => {
|
|
4864
|
+
const sessionComponent = this.environment?.getComponent?.("session");
|
|
4865
|
+
if (typeof sessionComponent?.detachGroupSession !== "function") {
|
|
4866
|
+
logger4.warn(`[LarkCliInstance] detachGroupSession 跳过:sessionComponent.detachGroupSession 不可用`);
|
|
4867
|
+
return;
|
|
4868
|
+
}
|
|
4869
|
+
const r = await sessionComponent.detachGroupSession(chatId);
|
|
4870
|
+
if (!r.ok) {
|
|
4871
|
+
logger4.warn(`[LarkCliInstance] detachGroupSession 失败: ${chatId}`);
|
|
4872
|
+
return;
|
|
4873
|
+
}
|
|
4874
|
+
logger4.info(`[LarkCliInstance] 群 session 已标记解散:${chatId}`);
|
|
4875
|
+
},
|
|
4876
|
+
getSessionTitle: async (sessionId) => {
|
|
4877
|
+
const sessionComponent = this.environment?.getComponent?.("session");
|
|
4878
|
+
if (typeof sessionComponent?.get !== "function")
|
|
4879
|
+
return;
|
|
4880
|
+
try {
|
|
4881
|
+
const s = await sessionComponent.get(sessionId);
|
|
4882
|
+
return typeof s?.title === "string" && s.title.trim() ? s.title.trim() : undefined;
|
|
4883
|
+
} catch {
|
|
4884
|
+
return;
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4432
4887
|
});
|
|
4433
|
-
|
|
4888
|
+
logger4.info(`[LarkCliInstance] card.action.trigger 端口已注入` + `(notifier ${sharedNotifier ? "复用插件共享实例" : "新建实例"})`);
|
|
4434
4889
|
}
|
|
4435
4890
|
getPluginHookPoints(plugin) {
|
|
4436
4891
|
if (Array.isArray(plugin.hooks)) {
|
|
@@ -4589,6 +5044,12 @@ class LarkCliInstance {
|
|
|
4589
5044
|
getTask: taskComponent?.getTask ? (id) => taskComponent.getTask(id) : undefined
|
|
4590
5045
|
};
|
|
4591
5046
|
plugin.setCardRoutingPorts({ ...this.cardRoutingPorts });
|
|
5047
|
+
if (typeof plugin === "object" && "syncGroupCardFromOriginFn" in plugin) {
|
|
5048
|
+
const sessionComponent = this.environment?.getComponent?.("session");
|
|
5049
|
+
if (typeof sessionComponent?.findGroupChatForSession === "function") {
|
|
5050
|
+
plugin.syncGroupCardFromOriginFn = (sid) => sessionComponent.findGroupChatForSession(sid);
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
4592
5053
|
}
|
|
4593
5054
|
injectSessionComponent(plugin) {
|
|
4594
5055
|
if (typeof plugin.setSessionComponent !== "function") {
|
|
@@ -4628,7 +5089,7 @@ class LarkCliInstance {
|
|
|
4628
5089
|
}
|
|
4629
5090
|
return null;
|
|
4630
5091
|
} catch (err) {
|
|
4631
|
-
|
|
5092
|
+
logger4.warn(`[LarkCliInstance] resolveChatTaskBinding failed for chatId=${chatId}: ${err}. ` + `群消息将不会注入 parentTaskId。`);
|
|
4632
5093
|
return null;
|
|
4633
5094
|
}
|
|
4634
5095
|
}
|
|
@@ -4650,7 +5111,7 @@ class LarkCliInstance {
|
|
|
4650
5111
|
processStreamWith(data, buffer, opts = {}) {
|
|
4651
5112
|
buffer.value += data;
|
|
4652
5113
|
if (buffer.value.length > 2 * 1024 * 1024) {
|
|
4653
|
-
|
|
5114
|
+
logger4.warn(`[LarkCliInstance] stream 行缓冲超过 2MB 仍无换行,丢弃(疑似非 NDJSON 输出)`);
|
|
4654
5115
|
buffer.value = "";
|
|
4655
5116
|
return;
|
|
4656
5117
|
}
|
|
@@ -4686,12 +5147,12 @@ class LarkCliInstance {
|
|
|
4686
5147
|
const now = Date.now();
|
|
4687
5148
|
const lastShared = LarkCliInstance.SHARED_SEEN.get(dedupKey);
|
|
4688
5149
|
if (lastShared !== undefined && now - lastShared < LarkCliInstance.SHARED_MSG_TTL_MS) {
|
|
4689
|
-
|
|
5150
|
+
logger4.warn(`[LarkCliInstance] duplicate lark-cli event suppressed across instances (event_id=${dedupKey}, ` + `lastSeen=${now - lastShared}ms ago). ` + `Task #4137: SHARED_SEEN 跨实例去重。`);
|
|
4690
5151
|
return;
|
|
4691
5152
|
}
|
|
4692
5153
|
const last = this.recentEventIds.get(dedupKey);
|
|
4693
5154
|
if (last !== undefined && now - last < this.DEDUP_WINDOW_MS) {
|
|
4694
|
-
|
|
5155
|
+
logger4.warn(`[LarkCliInstance] duplicate lark-cli event suppressed (event_id=${dedupKey}, ` + `lastSeen=${now - last}ms ago). ` + `Task #3977/3978: 防止同一飞书事件触发两次任务创建。`);
|
|
4695
5156
|
return;
|
|
4696
5157
|
}
|
|
4697
5158
|
LarkCliInstance.SHARED_SEEN.set(dedupKey, now);
|
|
@@ -4750,13 +5211,17 @@ class LarkCliInstance {
|
|
|
4750
5211
|
...rawEvent,
|
|
4751
5212
|
profile: this.config.profile,
|
|
4752
5213
|
eventSourceId: this.config.id,
|
|
4753
|
-
channelPromptMode: this.config.channelPromptMode ?? "lean"
|
|
5214
|
+
channelPromptMode: this.config.channelPromptMode ?? "lean",
|
|
5215
|
+
statusNotifications: this.config.statusNotifications === true
|
|
4754
5216
|
};
|
|
4755
5217
|
const { metadata, replyChannel, recommendedAction } = extractMetadata(enrichedEvent, eventType);
|
|
4756
5218
|
const channelSystemPrompt = this.getChannelSystemPrompt();
|
|
4757
5219
|
if (channelSystemPrompt !== undefined) {
|
|
4758
5220
|
metadata.systemPrompt = channelSystemPrompt;
|
|
4759
5221
|
}
|
|
5222
|
+
if (this.config.extendTools?.length) {
|
|
5223
|
+
metadata.extendTools = [...this.config.extendTools];
|
|
5224
|
+
}
|
|
4760
5225
|
failureReplyChannel = replyChannel;
|
|
4761
5226
|
const commandText = isCardAction ? undefined : extractCommandText(enrichedEvent);
|
|
4762
5227
|
const parentTaskId = isCardAction ? null : await this.resolveChatTaskBinding(metadata?.chatId);
|
|
@@ -4785,8 +5250,9 @@ class LarkCliInstance {
|
|
|
4785
5250
|
console.error(`[LarkCliInstance] emitTriggerEvent failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4786
5251
|
}
|
|
4787
5252
|
}
|
|
5253
|
+
const slimRawData = buildSlimRawData(rawEvent);
|
|
4788
5254
|
const message = JSON.stringify({
|
|
4789
|
-
rawData,
|
|
5255
|
+
rawData: slimRawData,
|
|
4790
5256
|
recommendedAction,
|
|
4791
5257
|
...triggerEventId ? { triggerEventId } : {}
|
|
4792
5258
|
});
|
|
@@ -4810,6 +5276,7 @@ class LarkCliInstance {
|
|
|
4810
5276
|
replyChannel,
|
|
4811
5277
|
agent,
|
|
4812
5278
|
...channelSystemPrompt !== undefined ? { systemPrompt: channelSystemPrompt } : {},
|
|
5279
|
+
...this.config.extendTools?.length ? { extendTools: [...this.config.extendTools] } : {},
|
|
4813
5280
|
parentTaskId: parentTaskId ?? undefined,
|
|
4814
5281
|
...triggerEventId ? { triggerEventId } : {},
|
|
4815
5282
|
traceContext: eventTraceContext
|
|
@@ -4886,11 +5353,11 @@ class LarkCliInstance {
|
|
|
4886
5353
|
const chatId = event.chat_id || "";
|
|
4887
5354
|
const operatorId = event.operator_id?.open_id ?? event.operator_id ?? undefined;
|
|
4888
5355
|
if (!token || !messageId || !chatId) {
|
|
4889
|
-
|
|
5356
|
+
logger4.warn(`[LarkCliInstance] handleCardActionEvent: missing token/messageId/chatId, skip`);
|
|
4890
5357
|
return;
|
|
4891
5358
|
}
|
|
4892
5359
|
if (!this.cardActionPorts) {
|
|
4893
|
-
|
|
5360
|
+
logger4.warn(`[LarkCliInstance] handleCardActionEvent: cardActionPorts 未注入,` + `card.action.trigger 事件被跳过。` + `调 instance.setCardActionPorts({getTask, updateTaskContext, createGroupChat, deleteGroupChat, notifier}) 注入。`);
|
|
4894
5361
|
return;
|
|
4895
5362
|
}
|
|
4896
5363
|
const result = await handleCardAction({
|
|
@@ -4903,12 +5370,17 @@ class LarkCliInstance {
|
|
|
4903
5370
|
}, this.cardActionPorts);
|
|
4904
5371
|
if (result.ok && result.cardUpdate) {
|
|
4905
5372
|
const patch = this.cardActionPorts.patchByToken ?? patchCardByLarkCli;
|
|
4906
|
-
|
|
5373
|
+
try {
|
|
5374
|
+
await patch(token, result.cardUpdate, profile);
|
|
5375
|
+
logger4.info(`[LarkCliInstance] 卡片 patch 成功: messageId=${messageId} action=${String(result.action)} chatId=${chatId}`);
|
|
5376
|
+
} catch (patchErr) {
|
|
5377
|
+
logger4.warn(`[LarkCliInstance] 卡片 patch 失败: messageId=${messageId} ` + `action=${String(result.action)} error=${patchErr.message}` + `(token 30min 有效,过期即失败——需用新卡重试)`);
|
|
5378
|
+
}
|
|
4907
5379
|
} else {
|
|
4908
|
-
|
|
5380
|
+
logger4.warn(`[LarkCliInstance] handleCardActionEvent failed: ${result.error ?? "unknown"}`);
|
|
4909
5381
|
}
|
|
4910
5382
|
} catch (err) {
|
|
4911
|
-
|
|
5383
|
+
logger4.error(`[LarkCliInstance] handleCardActionEvent error: ${err.message}`);
|
|
4912
5384
|
}
|
|
4913
5385
|
}
|
|
4914
5386
|
isCommandCardActionEvent(event) {
|
|
@@ -5024,7 +5496,7 @@ class LarkCliInstance {
|
|
|
5024
5496
|
}
|
|
5025
5497
|
await this.sendLarkText(output.text, profile, messageId, chatId);
|
|
5026
5498
|
} catch (err) {
|
|
5027
|
-
|
|
5499
|
+
logger4.error(`[LarkCliInstance] handleCommandMessage error: ${err.message}`);
|
|
5028
5500
|
try {
|
|
5029
5501
|
const card = renderTextCard("⚠️ 命令执行异常", `命令处理时发生异常:${err.message}`);
|
|
5030
5502
|
await this.sendLarkCard(card, profile, messageId, chatId);
|
|
@@ -5314,7 +5786,7 @@ var larkCliHandler = {
|
|
|
5314
5786
|
},
|
|
5315
5787
|
async emitTriggerEvent(payload, store) {
|
|
5316
5788
|
const eventId = await store.insertTriggerEvent(payload, "lark-cli");
|
|
5317
|
-
|
|
5789
|
+
logger4.info(`[larkCliHandler] emitTriggerEvent: id=${eventId}`, {
|
|
5318
5790
|
chatId: payload.metadata?.chatId,
|
|
5319
5791
|
senderId: payload.senderId
|
|
5320
5792
|
});
|
|
@@ -5329,7 +5801,7 @@ var larkCliHandler = {
|
|
|
5329
5801
|
try {
|
|
5330
5802
|
const result = await runner.run(command);
|
|
5331
5803
|
if (result.code !== 0) {
|
|
5332
|
-
|
|
5804
|
+
logger4.error(`[larkCliHandler] replyMessage non-zero exit`, {
|
|
5333
5805
|
code: result.code,
|
|
5334
5806
|
stderr: result.stderr
|
|
5335
5807
|
});
|
|
@@ -5343,7 +5815,7 @@ var larkCliHandler = {
|
|
|
5343
5815
|
return { success: true, messageId: replyMessageId };
|
|
5344
5816
|
} catch (err) {
|
|
5345
5817
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5346
|
-
|
|
5818
|
+
logger4.error(`[larkCliHandler] replyMessage spawn error`, { error: errMsg });
|
|
5347
5819
|
return { success: false, error: errMsg };
|
|
5348
5820
|
}
|
|
5349
5821
|
},
|
|
@@ -5392,7 +5864,7 @@ ${headline}
|
|
|
5392
5864
|
- **chatId: ${chatId || "(缺失)"}** ← 关键!必须发到这里
|
|
5393
5865
|
- **messageId: ${messageId || "(缺失)"}** ← ${messageId ? "reply 这条,保持话题线索" : "无触发消息,用 +messages-send"}
|
|
5394
5866
|
- sender: ${senderId || "(未知)"}
|
|
5395
|
-
${input.notifyEvent.subSessionId ? `- 子 session: ${input.notifyEvent.subSessionId} ← 如需续跑:delegate_task(
|
|
5867
|
+
${input.notifyEvent.subSessionId ? `- 子 session: ${input.notifyEvent.subSessionId} ← 如需续跑:delegate_task(sub_session_id="${input.notifyEvent.subSessionId}", prompt=<新要求>)
|
|
5396
5868
|
` : ""}${profile ? `- profile: ${profile} ← 调用 lark-cli 必须带 --profile
|
|
5397
5869
|
` : ""}
|
|
5398
5870
|
### 系统回执(rule-based 直发)
|
|
@@ -5447,11 +5919,11 @@ async function patchCardByLarkCli(token, card, profile) {
|
|
|
5447
5919
|
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
5448
5920
|
const cmd = `lark-cli ${profileFlag}api POST /open-apis/interactive/v1/card/update ` + `--data '${JSON.stringify({ token, card }).replace(/'/g, "'\\''")}' --as bot`;
|
|
5449
5921
|
try {
|
|
5450
|
-
const { stdout } = await
|
|
5451
|
-
|
|
5922
|
+
const { stdout } = await execAsync(cmd, { timeout: 30000 });
|
|
5923
|
+
logger4.debug(`patchCardByLarkCli ok: ${stdout?.slice(0, 200)}`);
|
|
5452
5924
|
} catch (err) {
|
|
5453
5925
|
const msg = err.message;
|
|
5454
|
-
|
|
5926
|
+
logger4.error(`patchCardByLarkCli failed: ${msg}`);
|
|
5455
5927
|
throw new Error(`卡片刷新失败:${msg}`);
|
|
5456
5928
|
}
|
|
5457
5929
|
}
|
|
@@ -5467,4 +5939,4 @@ function getBuiltInHandler(type) {
|
|
|
5467
5939
|
return builtInHandlers.find((h) => h.type === type);
|
|
5468
5940
|
}
|
|
5469
5941
|
|
|
5470
|
-
export { TimerInstance, timerHandler, buildDefaultBountyIMSystemPrompt, resolveBountyIMSystemPrompt, BountyIMInstance, bountyIMHandler, bountyCliHandler, sanitizeWecomFrame, WECOM_CLI_ES_TYPE, WECOM_CHAT_ID_PREFIX, WECOM_WS_DEFAULT_URL, readWecomCliConfig, WECOM_CREDENTIALS_DEFAULT_PATH, readWecomCredentialsFile, resolveWecomCredentials, buildWecomReplyChannel, parseWecomFrame, WECOM_CHANNEL_PROMPT_SECTION_TITLE, buildWecomChannelSystemPrompt, buildWecomNotifyPrompt, resolveWecomBinary, probeCommandBinary, wecomCliHandler, FALLBACK_MODEL_ACTION_NS, FALLBACK_MODEL_ACTIONS, isFallbackModelCard, renderTextCard, renderFallbackModelCard, DEFAULT_CONFIG_PATH, readFallbackModelList, readRawFallbackModelArray, writeFallbackModelList, fallbackModelCommand, listCommand, resolveEsFilePath, esCommand, COMMAND_PREFIX, isCommandMessage, extractCommandInvocation, CommandRegistry, getGlobalCommandRegistry, handleCommandMessage, isCommandCardAction, translateCardActionToCommand, handleCommandCardAction, buildLarkRecommendedAction, LARK_CHANNEL_PROMPT_SECTION_TITLE, LARK_PROMPT_LENGTH_BUDGET, buildLarkChannelSystemPrompt, resolveLarkChannelSystemPrompt, extractMetadata, LarkCliInstance, renderRuleNoticeSection, extractCommandText, larkCliHandler, patchCardByLarkCli, builtInHandlers, getBuiltInHandler };
|
|
5942
|
+
export { TimerInstance, timerHandler, buildDefaultBountyIMSystemPrompt, resolveBountyIMSystemPrompt, BountyIMInstance, bountyIMHandler, bountyCliHandler, sanitizeWecomFrame, WECOM_CLI_ES_TYPE, WECOM_CHAT_ID_PREFIX, WECOM_WS_DEFAULT_URL, readWecomCliConfig, WECOM_CREDENTIALS_DEFAULT_PATH, readWecomCredentialsFile, resolveWecomCredentials, buildWecomReplyChannel, parseWecomFrame, WECOM_CHANNEL_PROMPT_SECTION_TITLE, buildWecomChannelSystemPrompt, buildWecomNotifyPrompt, resolveWecomBinary, probeCommandBinary, wecomCliHandler, FALLBACK_MODEL_ACTION_NS, FALLBACK_MODEL_ACTIONS, isFallbackModelCard, renderTextCard, renderFallbackModelCard, DEFAULT_CONFIG_PATH, readFallbackModelList, readRawFallbackModelArray, writeFallbackModelList, fallbackModelCommand, listCommand, resolveEsFilePath, esCommand, COMMAND_PREFIX, isCommandMessage, extractCommandInvocation, CommandRegistry, getGlobalCommandRegistry, handleCommandMessage, isCommandCardAction, translateCardActionToCommand, handleCommandCardAction, buildSlimRawData, buildLarkRecommendedAction, LARK_CHANNEL_PROMPT_SECTION_TITLE, LARK_PROMPT_LENGTH_BUDGET, buildLarkChannelSystemPrompt, resolveLarkChannelSystemPrompt, extractMetadata, LarkCliInstance, renderRuleNoticeSection, extractCommandText, larkCliHandler, patchCardByLarkCli, builtInHandlers, getBuiltInHandler };
|