@ai-setting/roy-agent-core 1.7.61 → 1.7.62
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 +8 -8
- package/dist/env/agent/agent-component.js +41 -0
- package/dist/env/agent/index.js +18 -16
- package/dist/env/commands/index.js +8 -8
- package/dist/env/context/index.js +4 -4
- package/dist/env/debug/index.js +8 -8
- package/dist/env/error-reply.js +18 -0
- package/dist/env/event-source/cli-command-handler.js +17 -0
- package/dist/env/event-source/event-source-handlers.js +58 -0
- package/dist/env/event-source/event-store.js +18 -0
- package/dist/env/event-source/idempotency-key.js +20 -0
- package/dist/env/event-source/index.js +30 -25
- package/dist/env/hook/index.js +6 -6
- package/dist/env/index.js +52 -46
- package/dist/env/lark-mention-utils.js +9 -0
- package/dist/env/llm/index.js +9 -9
- package/dist/env/log-trace/index.js +8 -8
- package/dist/env/log-trace/logger.js +39 -0
- package/dist/env/mcp/index.js +9 -9
- package/dist/env/mcp/tool/index.js +2 -2
- package/dist/env/memory/index.js +9 -9
- package/dist/env/memory/plugin/index.js +2 -2
- package/dist/env/plugin/index.js +9 -9
- package/dist/env/prompt/index.js +8 -8
- package/dist/env/session/index.js +11 -11
- package/dist/env/session/storage/index.js +5 -5
- package/dist/env/skill/index.js +8 -8
- package/dist/env/task/delegate/delegate-tool.js +42 -0
- package/dist/env/task/delegate/index.js +13 -11
- package/dist/env/task/index.js +16 -15
- package/dist/env/task/mcp/index.js +4 -4
- package/dist/env/task/plugins/index.js +11 -11
- package/dist/env/task/plugins/lark-card-patch-notifier.js +42 -0
- package/dist/env/task/storage/index.js +4 -4
- package/dist/env/task/tools/index.js +4 -4
- package/dist/env/tool/built-in/index.js +4 -4
- package/dist/env/tool/index.js +12 -11
- package/dist/env/tool/types.js +1 -0
- package/dist/env/workflow/engine/index.js +13 -13
- package/dist/env/workflow/hooks/index.js +8 -8
- package/dist/env/workflow/index.js +23 -23
- package/dist/env/workflow/nodes/index.js +3 -3
- package/dist/env/workflow/plugins/index.js +9 -9
- package/dist/env/workflow/service/index.js +3 -3
- package/dist/env/workflow/storage/index.js +4 -4
- package/dist/env/workflow/tools/index.js +15 -15
- package/dist/env/workflow/utils/index.js +5 -5
- package/dist/index.js +70 -62
- package/dist/shared/@ai-setting/{roy-agent-core-dythsgkn.js → roy-agent-core-06jakebr.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-jsn21cb9.js → roy-agent-core-084zthqx.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-x1m0gs0q.js → roy-agent-core-2hkyp51n.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-q212q0w1.js → roy-agent-core-2pma868e.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-vmfk4m1v.js → roy-agent-core-2veqhrxa.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-mw0bc9g6.js → roy-agent-core-32ck9rqt.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-bdzdw1g3.js → roy-agent-core-33hn1beg.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-2qkrpc7m.js → roy-agent-core-386wa4wy.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-rnzm0tpf.js → roy-agent-core-48h4zh60.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-yr5eqyhz.js → roy-agent-core-5bj0xfjk.js} +13 -27
- package/dist/shared/@ai-setting/{roy-agent-core-khc23qs1.js → roy-agent-core-5mrynf9g.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-y0k9kaxz.js → roy-agent-core-6awgfh3x.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-ygfjgk9b.js → roy-agent-core-6wxww10t.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-xc0es9zc.js → roy-agent-core-79gyka2q.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-expvav8a.js → roy-agent-core-7tce05jt.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-9nvyffxg.js +414 -0
- package/dist/shared/@ai-setting/{roy-agent-core-m2tv4pj0.js → roy-agent-core-9qgrc9dy.js} +158 -158
- package/dist/shared/@ai-setting/{roy-agent-core-z51jktet.js → roy-agent-core-a08dpf71.js} +17 -2
- package/dist/shared/@ai-setting/{roy-agent-core-22r4zmkw.js → roy-agent-core-a6cz4d23.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-0f989bnp.js → roy-agent-core-anb53t3v.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-ymcvsdzn.js → roy-agent-core-as0dd70m.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-nd5171pv.js → roy-agent-core-bpvz1dqr.js} +8 -8
- package/dist/shared/@ai-setting/{roy-agent-core-aa514zm8.js → roy-agent-core-c4t4y18j.js} +5 -5
- package/dist/shared/@ai-setting/{roy-agent-core-sf9tsj0g.js → roy-agent-core-c57278kx.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-e6k448ka.js → roy-agent-core-c8xvy6hj.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-gtb4fj8j.js → roy-agent-core-cqpd41gf.js} +12 -10
- package/dist/shared/@ai-setting/{roy-agent-core-qgapy83x.js → roy-agent-core-cxpgd415.js} +6 -6
- package/dist/shared/@ai-setting/{roy-agent-core-g44nbgtt.js → roy-agent-core-dfqhc9ey.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-cndvj1hv.js → roy-agent-core-fqandv0a.js} +9 -107
- package/dist/shared/@ai-setting/{roy-agent-core-4m5spt7m.js → roy-agent-core-fr296a53.js} +5 -5
- package/dist/shared/@ai-setting/{roy-agent-core-3p59ap0x.js → roy-agent-core-gc6wcd7z.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-s7w47mnn.js → roy-agent-core-gjsvehfs.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-9s3payp1.js → roy-agent-core-gtdtzbs7.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-jrksyqb6.js → roy-agent-core-gz2jnaan.js} +7 -7
- package/dist/shared/@ai-setting/{roy-agent-core-6cyjps72.js → roy-agent-core-h3fwjbhp.js} +14 -14
- package/dist/shared/@ai-setting/{roy-agent-core-mhppywse.js → roy-agent-core-hczj1ryh.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-qrjs2500.js → roy-agent-core-hfyxv2kx.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-dg286rpd.js → roy-agent-core-hwkc0bg2.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-bc3v8pw4.js → roy-agent-core-ja4sr63d.js} +6 -6
- package/dist/shared/@ai-setting/roy-agent-core-jagkferh.js +16 -0
- package/dist/shared/@ai-setting/roy-agent-core-jm136jzh.js +99 -0
- package/dist/shared/@ai-setting/{roy-agent-core-g5sw19fa.js → roy-agent-core-jw63js86.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-8fy467qs.js → roy-agent-core-kmvw6b4v.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-3bpntd80.js → roy-agent-core-kvq4a4p6.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-yd85623y.js → roy-agent-core-m3w3ytxt.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-64f22h1g.js → roy-agent-core-m880qz3y.js} +7 -1
- package/dist/shared/@ai-setting/{roy-agent-core-5knpegzr.js → roy-agent-core-mbwaxqs0.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-tn96n4wz.js → roy-agent-core-mkpsz128.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-9k8ms63r.js → roy-agent-core-mrrv80jy.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-ytnedzks.js → roy-agent-core-nmn853z0.js} +333 -1332
- package/dist/shared/@ai-setting/roy-agent-core-p37kh0kt.js +155 -0
- package/dist/shared/@ai-setting/{roy-agent-core-60p2380d.js → roy-agent-core-p6a4v4fq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-hf0t8agb.js → roy-agent-core-p8rf4z89.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-pnj0zh25.js +0 -0
- package/dist/shared/@ai-setting/{roy-agent-core-dp1hp24k.js → roy-agent-core-prnry2mw.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-qw5fpa24.js → roy-agent-core-qmqe8ds3.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-j0x9yf96.js → roy-agent-core-sj4y3wdm.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-xeqp659x.js → roy-agent-core-th92f833.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-ajmgyawx.js → roy-agent-core-v7mwj8eq.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-5565cvw8.js → roy-agent-core-vvb4cyyy.js} +5 -5
- package/dist/shared/@ai-setting/{roy-agent-core-qpqqx1wd.js → roy-agent-core-vwzan9v0.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-pj5tfmn5.js → roy-agent-core-w2ev3z5q.js} +12 -14
- package/dist/shared/@ai-setting/roy-agent-core-w2h6xt6k.js +473 -0
- package/dist/shared/@ai-setting/{roy-agent-core-kfmrfaft.js → roy-agent-core-wzp4tbsn.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-jsdqzfbj.js → roy-agent-core-x1d6edkh.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-n1ck0x1r.js → roy-agent-core-ye4bfzqg.js} +11 -11
- package/dist/shared/@ai-setting/{roy-agent-core-p2dpcyrz.js → roy-agent-core-zjgckbdd.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-a5nh5q6v.js → roy-agent-core-zy8vkbnk.js} +1 -1
- package/dist/test-utils/child-process-mock.js +23 -0
- package/dist/utils/temp-file-manager/index.js +2 -2
- package/package.json +61 -1
|
@@ -1,59 +1,301 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defaultLarkCliRunner
|
|
3
3
|
} from "./roy-agent-core-bxkjk7zx.js";
|
|
4
|
+
import {
|
|
5
|
+
notifyReactFailure
|
|
6
|
+
} from "./roy-agent-core-9nvyffxg.js";
|
|
7
|
+
import {
|
|
8
|
+
cliCommandHandler
|
|
9
|
+
} from "./roy-agent-core-p37kh0kt.js";
|
|
4
10
|
import {
|
|
5
11
|
TaskTagPlugin,
|
|
6
12
|
createLarkCliTaskNotifyHook,
|
|
7
13
|
createPerTaskGroupChatPlugin,
|
|
8
14
|
extractPerTaskChatId,
|
|
9
15
|
extractPerTaskChatName
|
|
10
|
-
} from "./roy-agent-core-
|
|
16
|
+
} from "./roy-agent-core-ye4bfzqg.js";
|
|
11
17
|
import {
|
|
12
18
|
getSharedEventStore
|
|
13
|
-
} from "./roy-agent-core-
|
|
19
|
+
} from "./roy-agent-core-a08dpf71.js";
|
|
14
20
|
import {
|
|
15
21
|
createLarkCliWorkflowNotifyHook
|
|
16
|
-
} from "./roy-agent-core-
|
|
22
|
+
} from "./roy-agent-core-cxpgd415.js";
|
|
17
23
|
import {
|
|
18
24
|
LarkCardPatchNotifier,
|
|
19
25
|
buildTaskCard
|
|
20
|
-
} from "./roy-agent-core-
|
|
26
|
+
} from "./roy-agent-core-m3w3ytxt.js";
|
|
21
27
|
import {
|
|
22
28
|
normalizeLarkAtMentions
|
|
23
|
-
} from "./roy-agent-core-
|
|
24
|
-
import {
|
|
25
|
-
envKeyToConfigKey
|
|
26
|
-
} from "./roy-agent-core-7hh0brvs.js";
|
|
27
|
-
import {
|
|
28
|
-
BaseComponent
|
|
29
|
-
} from "./roy-agent-core-9s3payp1.js";
|
|
29
|
+
} from "./roy-agent-core-m880qz3y.js";
|
|
30
30
|
import {
|
|
31
31
|
globalHookManager,
|
|
32
32
|
init_global_hook_manager
|
|
33
|
-
} from "./roy-agent-core-
|
|
33
|
+
} from "./roy-agent-core-v7mwj8eq.js";
|
|
34
34
|
import {
|
|
35
35
|
init_context
|
|
36
|
-
} from "./roy-agent-core-
|
|
36
|
+
} from "./roy-agent-core-33hn1beg.js";
|
|
37
37
|
import {
|
|
38
38
|
createEnvContext,
|
|
39
39
|
normalizeParentTaskId,
|
|
40
40
|
runWithEnvContext
|
|
41
|
-
} from "./roy-agent-core-
|
|
41
|
+
} from "./roy-agent-core-hfyxv2kx.js";
|
|
42
42
|
import {
|
|
43
43
|
TracedAs,
|
|
44
44
|
init_decorator,
|
|
45
45
|
wrapFunction
|
|
46
|
-
} from "./roy-agent-core-
|
|
46
|
+
} from "./roy-agent-core-386wa4wy.js";
|
|
47
47
|
import {
|
|
48
48
|
createLogger,
|
|
49
49
|
getTracerProvider,
|
|
50
50
|
init_logger
|
|
51
|
-
} from "./roy-agent-core-
|
|
51
|
+
} from "./roy-agent-core-6awgfh3x.js";
|
|
52
52
|
import {
|
|
53
53
|
__legacyDecorateClassTS,
|
|
54
54
|
__require
|
|
55
55
|
} from "./roy-agent-core-fs0mn2jk.js";
|
|
56
56
|
|
|
57
|
+
// src/env/event-source/event-source-handlers.ts
|
|
58
|
+
import { spawn as spawn2, exec as execSync } from "child_process";
|
|
59
|
+
import { promisify as promisify2 } from "node:util";
|
|
60
|
+
init_global_hook_manager();
|
|
61
|
+
init_context();
|
|
62
|
+
|
|
63
|
+
// src/env/task/plugins/lark-card-action-handler.ts
|
|
64
|
+
init_logger();
|
|
65
|
+
import { exec } from "node:child_process";
|
|
66
|
+
import { promisify } from "node:util";
|
|
67
|
+
var logger = createLogger("lark-card-action-handler");
|
|
68
|
+
var execAsync = promisify(exec);
|
|
69
|
+
var CTX_FIELD_CHAT_ID = "larkPerTaskChatId";
|
|
70
|
+
var CTX_FIELD_CHAT_NAME = "larkPerTaskChatName";
|
|
71
|
+
var CTX_FIELD_CHAT_CREATED_AT = "larkPerTaskChatCreatedAt";
|
|
72
|
+
var CTX_FIELD_CHAT_TASK_ID = "larkPerTaskChatTaskId";
|
|
73
|
+
var CTX_FIELD_CHAT_INVITEES = "larkPerTaskChatInvitees";
|
|
74
|
+
var cardActionInflight = new Map;
|
|
75
|
+
async function handleCardAction(event, ports) {
|
|
76
|
+
const { action, taskId: taskIdRaw } = event.actionValue;
|
|
77
|
+
const taskId = typeof taskIdRaw === "string" ? parseInt(taskIdRaw, 10) : taskIdRaw;
|
|
78
|
+
if (!taskId || Number.isNaN(taskId)) {
|
|
79
|
+
return { ok: false, action, error: `Invalid taskId: ${taskIdRaw}` };
|
|
80
|
+
}
|
|
81
|
+
if (action !== "create_group" && action !== "disband_group") {
|
|
82
|
+
return { ok: false, action, error: `Unknown action: ${action}` };
|
|
83
|
+
}
|
|
84
|
+
const inflightKey = `${event.profile ?? "-"}:${event.messageId || String(taskId)}:${action}`;
|
|
85
|
+
const inflight = cardActionInflight.get(inflightKey);
|
|
86
|
+
if (inflight) {
|
|
87
|
+
logger.warn(`handleCardAction: ${inflightKey} 已有同操作在处理中(疑似用户重复点击),本次忽略。`);
|
|
88
|
+
return { ok: false, action, error: `重复点击:${inflightKey} 正在处理中,已忽略` };
|
|
89
|
+
}
|
|
90
|
+
const pending = handleCardActionInner(event, ports, taskId);
|
|
91
|
+
cardActionInflight.set(inflightKey, pending);
|
|
92
|
+
try {
|
|
93
|
+
return await pending;
|
|
94
|
+
} finally {
|
|
95
|
+
cardActionInflight.delete(inflightKey);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function handleCardActionInner(event, ports, taskId) {
|
|
99
|
+
const { action } = event.actionValue;
|
|
100
|
+
if (!ports.getTask) {
|
|
101
|
+
return { ok: false, action, error: "ports.getTask not injected" };
|
|
102
|
+
}
|
|
103
|
+
const task = await ports.getTask(taskId);
|
|
104
|
+
if (!task) {
|
|
105
|
+
return { ok: false, action, error: `Task #${taskId} not found` };
|
|
106
|
+
}
|
|
107
|
+
const existingChatId = extractPerTaskChatId(task.context);
|
|
108
|
+
if (action === "create_group") {
|
|
109
|
+
return await handleCreateGroup(event, ports, task, existingChatId);
|
|
110
|
+
} else {
|
|
111
|
+
return await handleDisbandGroup(event, ports, task, existingChatId);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function buildCardAfterGroupChange(event, ports, task, perTaskChatId, perTaskChatName) {
|
|
115
|
+
const meta = buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName);
|
|
116
|
+
const preserved = ports.notifier?.updateMetaAndBuildCard?.(event.messageId, meta, { clearPerTaskChat: !perTaskChatId });
|
|
117
|
+
if (preserved) {
|
|
118
|
+
logger.debug(`buildCardAfterGroupChange: 复用 notifier 累积状态(里程碑历史保留)messageId=${event.messageId}`);
|
|
119
|
+
return preserved;
|
|
120
|
+
}
|
|
121
|
+
logger.warn(`buildCardAfterGroupChange: notifier 无 messageId=${event.messageId} 的状态,` + `fallback 重建卡片(该卡片的历史里程碑可能丢失)`);
|
|
122
|
+
return buildTaskCard({
|
|
123
|
+
status: "running",
|
|
124
|
+
meta,
|
|
125
|
+
milestones: [
|
|
126
|
+
{ title: "Task 创建", timestamp: new Date().toISOString(), description: task.current_status }
|
|
127
|
+
]
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function handleCreateGroup(event, ports, task, existingChatId) {
|
|
131
|
+
if (existingChatId) {
|
|
132
|
+
const existingName = extractPerTaskChatName(task.context);
|
|
133
|
+
logger.info(`handleCreateGroup: Task #${task.id} 已有专属群 ${existingChatId}(幂等命中,不重复建群)`);
|
|
134
|
+
return {
|
|
135
|
+
ok: true,
|
|
136
|
+
action: "create_group",
|
|
137
|
+
newChatId: existingChatId,
|
|
138
|
+
cardUpdate: buildCardAfterGroupChange(event, ports, task, existingChatId, existingName)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (!ports.createGroupChat) {
|
|
142
|
+
return { ok: false, action: "create_group", error: "ports.createGroupChat not injected" };
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const renderedName = truncateText(`Task #${task.id} - ${task.title ?? "(无标题)"}`, 60);
|
|
146
|
+
const description = truncateText(`roy-agent task #${task.id} - ${task.project_path ?? ""}`, 100);
|
|
147
|
+
const operatorOpenId = extractOperatorOpenId(event.operatorId);
|
|
148
|
+
const initialUserIds = operatorOpenId ? [operatorOpenId] : undefined;
|
|
149
|
+
const chatId = await ports.createGroupChat({
|
|
150
|
+
name: renderedName,
|
|
151
|
+
description,
|
|
152
|
+
profile: event.profile,
|
|
153
|
+
initialUserIds
|
|
154
|
+
});
|
|
155
|
+
let invitedOpenIds = [];
|
|
156
|
+
if (operatorOpenId) {
|
|
157
|
+
if (initialUserIds) {
|
|
158
|
+
invitedOpenIds = [operatorOpenId];
|
|
159
|
+
logger.debug(`handleCreateGroup: ${operatorOpenId} 已在建群时邀请(initialUserIds 路径)`);
|
|
160
|
+
} else if (ports.inviteUser) {
|
|
161
|
+
try {
|
|
162
|
+
await ports.inviteUser(chatId, operatorOpenId, event.profile);
|
|
163
|
+
invitedOpenIds = [operatorOpenId];
|
|
164
|
+
logger.debug(`handleCreateGroup: 走 inviteUser 端口邀请 ${operatorOpenId} 进 ${chatId}`);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
logger.warn(`handleCreateGroup: inviteUser(${chatId}, ${operatorOpenId}) 失败(不阻断主流程): ${err.message}`);
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
logger.warn(`handleCreateGroup: createGroupChat 不支持 initialUserIds 且 inviteUser 未注入,` + `${operatorOpenId} 未被拉进群 ${chatId}。` + `需升级 caller(bridge/real impl)使用 ports.inviteUser 或实现 createGroupChat.initialUserIds 参数。`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const newContext = patchContext(task.context ?? "{}", {
|
|
173
|
+
[CTX_FIELD_CHAT_ID]: chatId,
|
|
174
|
+
[CTX_FIELD_CHAT_NAME]: renderedName,
|
|
175
|
+
[CTX_FIELD_CHAT_CREATED_AT]: new Date().toISOString(),
|
|
176
|
+
[CTX_FIELD_CHAT_TASK_ID]: task.id,
|
|
177
|
+
[CTX_FIELD_CHAT_INVITEES]: invitedOpenIds.length > 0 ? invitedOpenIds : undefined
|
|
178
|
+
});
|
|
179
|
+
if (ports.updateTaskContext) {
|
|
180
|
+
await ports.updateTaskContext(task.id, newContext);
|
|
181
|
+
}
|
|
182
|
+
const cardUpdate = buildCardAfterGroupChange(event, ports, task, chatId, renderedName);
|
|
183
|
+
return { ok: true, action: "create_group", newChatId: chatId, cardUpdate };
|
|
184
|
+
} catch (err) {
|
|
185
|
+
logger.error(`handleCardAction.create_group failed: ${err.message}`);
|
|
186
|
+
return { ok: false, action: "create_group", error: err.message };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function extractOperatorOpenId(raw) {
|
|
190
|
+
if (!raw)
|
|
191
|
+
return;
|
|
192
|
+
if (typeof raw === "string")
|
|
193
|
+
return raw;
|
|
194
|
+
if (typeof raw === "object" && typeof raw.open_id === "string")
|
|
195
|
+
return raw.open_id;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
async function handleDisbandGroup(event, ports, task, existingChatId) {
|
|
199
|
+
if (!existingChatId) {
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
action: "disband_group",
|
|
203
|
+
error: `Task #${task.id} 无专属群(context.larkPerTaskChatId 空),无需解散`
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (!ports.deleteGroupChat) {
|
|
207
|
+
return { ok: false, action: "disband_group", error: "ports.deleteGroupChat not injected" };
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
await ports.deleteGroupChat(existingChatId, event.profile);
|
|
211
|
+
const newContext = patchContext(task.context ?? "{}", {
|
|
212
|
+
[CTX_FIELD_CHAT_ID]: undefined,
|
|
213
|
+
[CTX_FIELD_CHAT_NAME]: undefined,
|
|
214
|
+
[CTX_FIELD_CHAT_CREATED_AT]: undefined,
|
|
215
|
+
[CTX_FIELD_CHAT_TASK_ID]: undefined
|
|
216
|
+
});
|
|
217
|
+
if (ports.updateTaskContext) {
|
|
218
|
+
await ports.updateTaskContext(task.id, newContext);
|
|
219
|
+
}
|
|
220
|
+
const cardUpdate = buildCardAfterGroupChange(event, ports, task, undefined, undefined);
|
|
221
|
+
return { ok: true, action: "disband_group", cardUpdate };
|
|
222
|
+
} catch (err) {
|
|
223
|
+
logger.error(`handleCardAction.disband_group failed: ${err.message}`);
|
|
224
|
+
return { ok: false, action: "disband_group", error: err.message };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function patchContext(contextStr, changes) {
|
|
228
|
+
let obj = {};
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(contextStr);
|
|
231
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
232
|
+
obj = parsed;
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
obj = {};
|
|
236
|
+
}
|
|
237
|
+
for (const [k, v] of Object.entries(changes)) {
|
|
238
|
+
if (v === undefined) {
|
|
239
|
+
delete obj[k];
|
|
240
|
+
} else {
|
|
241
|
+
obj[k] = v;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return JSON.stringify(obj);
|
|
245
|
+
}
|
|
246
|
+
function buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName) {
|
|
247
|
+
return {
|
|
248
|
+
id: task.id,
|
|
249
|
+
title: task.title,
|
|
250
|
+
description: task.description,
|
|
251
|
+
goals: task.goals,
|
|
252
|
+
parentTaskId: typeof task.parent_task_id === "number" && task.parent_task_id > 0 ? task.parent_task_id : undefined,
|
|
253
|
+
priority: task.priority,
|
|
254
|
+
type: task.type,
|
|
255
|
+
projectPath: task.project_path,
|
|
256
|
+
context: task.context,
|
|
257
|
+
currentStatus: task.current_status,
|
|
258
|
+
perTaskChatId,
|
|
259
|
+
perTaskChatName
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async function deleteGroupChatViaLarkCli(chatId, profile) {
|
|
263
|
+
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
264
|
+
try {
|
|
265
|
+
const { stdout: authStdout } = await execAsync(`lark-cli ${profileFlag}auth status --json`, { timeout: 15000 });
|
|
266
|
+
const appId = JSON.parse(authStdout).appId;
|
|
267
|
+
if (!appId) {
|
|
268
|
+
throw new Error(`lark-cli auth status 未返回 appId: ${authStdout.slice(0, 200)}`);
|
|
269
|
+
}
|
|
270
|
+
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`;
|
|
271
|
+
const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
|
|
272
|
+
logger.debug(`deleteGroupChatViaLarkCli(${chatId}) bot 退群 ok: ` + `stdout=${stdout?.slice(0, 300)} stderr=${stderr?.slice(0, 300)}`);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
logger.warn(`deleteGroupChatViaLarkCli(${chatId}) bot 退群失败(task.context 仍会清掉,` + `飞书侧群会保留):${err.message}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function shellQuote(s) {
|
|
278
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
279
|
+
}
|
|
280
|
+
function truncateText(text, maxLen) {
|
|
281
|
+
if (!text)
|
|
282
|
+
return "";
|
|
283
|
+
if (text.length <= maxLen)
|
|
284
|
+
return text;
|
|
285
|
+
return text.substring(0, Math.max(0, maxLen - 3)) + "...";
|
|
286
|
+
}
|
|
287
|
+
async function createGroupChatViaLarkCli(opts) {
|
|
288
|
+
const profileFlag = opts.profile ? `--profile ${opts.profile} ` : "";
|
|
289
|
+
const usersFlag = opts.initialUserIds && opts.initialUserIds.length > 0 ? `--users ${shellQuote(opts.initialUserIds.join(","))} ` : "";
|
|
290
|
+
const cmd = `lark-cli ${profileFlag}im +chat-create --name ${shellQuote(truncateText(opts.name, 60))} ` + usersFlag + `--description ${shellQuote(truncateText(opts.description, 100))} --as bot`;
|
|
291
|
+
const { stdout } = await execAsync(cmd, { timeout: 60000 });
|
|
292
|
+
const m = stdout.match(/"chat_id"\s*:\s*"(oc_[a-f0-9]+)"/);
|
|
293
|
+
if (!m) {
|
|
294
|
+
throw new Error(`lark-cli +chat-create 返回无法解析: ${stdout.slice(0, 300)}`);
|
|
295
|
+
}
|
|
296
|
+
return m[1];
|
|
297
|
+
}
|
|
298
|
+
|
|
57
299
|
// src/env/event-source/timer-handler.ts
|
|
58
300
|
init_decorator();
|
|
59
301
|
var DEFAULT_TIMER_INTERVAL_MS = 10 * 60 * 1000;
|
|
@@ -565,440 +807,49 @@ class BountyIMInstance {
|
|
|
565
807
|
}
|
|
566
808
|
if (evt.event === "message") {
|
|
567
809
|
const msg = evt.data || {};
|
|
568
|
-
const from = msg.from || "unknown";
|
|
569
|
-
let content = "未知内容";
|
|
570
|
-
if (msg.content?.type === "text") {
|
|
571
|
-
content = String(msg.content?.body || "");
|
|
572
|
-
}
|
|
573
|
-
return `[${from}] ${content}`;
|
|
574
|
-
}
|
|
575
|
-
return `事件: ${evt.event || "unknown"}`;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
var bountyIMHandler = {
|
|
579
|
-
type: "bounty-im",
|
|
580
|
-
validateConfig(config) {
|
|
581
|
-
const errors = [];
|
|
582
|
-
if (!config.id)
|
|
583
|
-
errors.push("EventSource ID is required");
|
|
584
|
-
if (!config.name)
|
|
585
|
-
errors.push("EventSource name is required");
|
|
586
|
-
try {
|
|
587
|
-
assertNoLegacyAddressField(config);
|
|
588
|
-
} catch (err) {
|
|
589
|
-
errors.push(err instanceof Error ? err.message : String(err));
|
|
590
|
-
}
|
|
591
|
-
const { imServerUrl, email } = readBountyIMConfig(config);
|
|
592
|
-
if (!email) {
|
|
593
|
-
errors.push("Bounty IM email is required (v0.14 strict email-only; top-level email or options.email)");
|
|
594
|
-
}
|
|
595
|
-
if (email !== undefined && email !== "") {
|
|
596
|
-
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
597
|
-
errors.push("Bounty IM email format invalid (expected: user@domain.tld)");
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
if (!imServerUrl) {
|
|
601
|
-
errors.push("Bounty IM imServerUrl is required (top-level imServerUrl or options.imServerUrl)");
|
|
602
|
-
} else if (!imServerUrl.startsWith("ws://") && !imServerUrl.startsWith("wss://")) {
|
|
603
|
-
errors.push("Bounty IM imServerUrl must start with ws:// or wss://");
|
|
604
|
-
}
|
|
605
|
-
return errors;
|
|
606
|
-
},
|
|
607
|
-
createInstance(config) {
|
|
608
|
-
return new BountyIMInstance(config);
|
|
609
|
-
}
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
// src/env/event-source/cli-command-handler.ts
|
|
613
|
-
init_logger();
|
|
614
|
-
var logger = createLogger("cli-command:event-source");
|
|
615
|
-
var CLI_COMMAND_ES_TYPE = "cli-command";
|
|
616
|
-
var CLI_SESSION_CHAT_ID_PREFIX = "cli:";
|
|
617
|
-
function buildCliReplyChannel(params) {
|
|
618
|
-
const sid = params.sessionId?.trim() ? params.sessionId.trim() : "default";
|
|
619
|
-
return {
|
|
620
|
-
type: CLI_COMMAND_ES_TYPE,
|
|
621
|
-
chatId: `${CLI_SESSION_CHAT_ID_PREFIX}${sid}`,
|
|
622
|
-
params: { sink: "stdout", cliSessionId: sid }
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
function parseCliSessionChatId(chatId) {
|
|
626
|
-
if (!chatId || !chatId.startsWith(CLI_SESSION_CHAT_ID_PREFIX))
|
|
627
|
-
return;
|
|
628
|
-
const sid = chatId.slice(CLI_SESSION_CHAT_ID_PREFIX.length);
|
|
629
|
-
return sid.length > 0 ? sid : undefined;
|
|
630
|
-
}
|
|
631
|
-
function describeNotifyEvent(type, taskLabel, desc) {
|
|
632
|
-
const actionable = new Set([
|
|
633
|
-
"task.background.completed",
|
|
634
|
-
"task.background.failed",
|
|
635
|
-
"task.background.timeout",
|
|
636
|
-
"task.background.stopped"
|
|
637
|
-
]).has(type);
|
|
638
|
-
switch (type) {
|
|
639
|
-
case "task.background.completed":
|
|
640
|
-
return { headline: `Sub-agent 完成了 ${taskLabel}「${desc}」。`, actionable };
|
|
641
|
-
case "task.background.failed":
|
|
642
|
-
return { headline: `Sub-agent 执行 ${taskLabel}「${desc}」失败。`, actionable };
|
|
643
|
-
case "task.background.timeout":
|
|
644
|
-
return { headline: `Sub-agent 执行 ${taskLabel}「${desc}」**超时**(未跑完)。`, actionable };
|
|
645
|
-
case "task.background.stopped":
|
|
646
|
-
return { headline: `Sub-agent 已停止 ${taskLabel}「${desc}」。`, actionable };
|
|
647
|
-
case "task.background.ask_user":
|
|
648
|
-
return { headline: `Sub-agent 在 ${taskLabel}「${desc}」中请求用户输入。`, actionable };
|
|
649
|
-
case "task.background.started":
|
|
650
|
-
return { headline: `Sub-agent 已启动 ${taskLabel}「${desc}」(后台运行中)。`, actionable };
|
|
651
|
-
case "task.background.progress":
|
|
652
|
-
return { headline: `Sub-agent 仍在执行 ${taskLabel}「${desc}」(进行中)。`, actionable };
|
|
653
|
-
default:
|
|
654
|
-
return { headline: `Sub-agent 更新了 ${taskLabel}「${desc}」。`, actionable };
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
function renderRuleNoticeSectionForCli(ruleNotice, headline) {
|
|
658
|
-
if (!ruleNotice || !ruleNotice.attempted) {
|
|
659
|
-
return `(本事件不直发规则回执)
|
|
660
|
-
` + `→ 你的回复要把结果讲**完整**(终端用户只能从你这条输出知道结论)。`;
|
|
661
|
-
}
|
|
662
|
-
if (ruleNotice.delivered) {
|
|
663
|
-
const when = ruleNotice.sentAt ? new Date(ruleNotice.sentAt).toLocaleTimeString("zh-CN", {
|
|
664
|
-
hour12: false,
|
|
665
|
-
hour: "2-digit",
|
|
666
|
-
minute: "2-digit"
|
|
667
|
-
}) : "";
|
|
668
|
-
const sent = ruleNotice.text ? `:「${ruleNotice.text.replace(/\s*\n+\s*/g, " ")}」` : "";
|
|
669
|
-
return `✅ 系统已于 ${when || "刚才"} 向终端输出过下面这条${sent}
|
|
670
|
-
` + `→ 你的回复**只发增量**:不要再重复「任务已结束」这类回执原文,` + `直接讲结论/交付 + 下一步选项。`;
|
|
671
|
-
}
|
|
672
|
-
return `❌ **回执未送达**(原因:${ruleNotice.error ?? "unknown"})
|
|
673
|
-
` + `→ 用户目前**还不知道**「${headline}」这件事,必须由你在回复里讲清楚,` + `并且不要假装系统已经通知过。`;
|
|
674
|
-
}
|
|
675
|
-
var cliCommandHandler = {
|
|
676
|
-
type: CLI_COMMAND_ES_TYPE,
|
|
677
|
-
validateConfig(_config) {
|
|
678
|
-
return [];
|
|
679
|
-
},
|
|
680
|
-
createInstance(config) {
|
|
681
|
-
return {
|
|
682
|
-
config,
|
|
683
|
-
start: async () => {},
|
|
684
|
-
stop: async () => {},
|
|
685
|
-
isRunning: () => false,
|
|
686
|
-
getStatus: () => ({
|
|
687
|
-
id: config.id,
|
|
688
|
-
type: CLI_COMMAND_ES_TYPE,
|
|
689
|
-
running: false
|
|
690
|
-
})
|
|
691
|
-
};
|
|
692
|
-
},
|
|
693
|
-
async emitTriggerEvent(payload, store) {
|
|
694
|
-
const eventId = await store.insertTriggerEvent(payload, CLI_COMMAND_ES_TYPE);
|
|
695
|
-
logger.info(`[cliCommandHandler] emitTriggerEvent: id=${eventId}`, {
|
|
696
|
-
sessionId: payload.metadata?.sessionId,
|
|
697
|
-
cwd: payload.metadata?.cwd
|
|
698
|
-
});
|
|
699
|
-
return eventId;
|
|
700
|
-
},
|
|
701
|
-
extractReplyContext(event, profile) {
|
|
702
|
-
const meta = event.metadata ?? {};
|
|
703
|
-
return {
|
|
704
|
-
esType: CLI_COMMAND_ES_TYPE,
|
|
705
|
-
chatId: typeof meta.chatId === "string" ? meta.chatId : "",
|
|
706
|
-
messageId: typeof meta.messageId === "string" ? meta.messageId : "",
|
|
707
|
-
senderOpenId: typeof meta.senderId === "string" ? meta.senderId : "",
|
|
708
|
-
profile: profile ?? (typeof meta.profile === "string" ? meta.profile : undefined)
|
|
709
|
-
};
|
|
710
|
-
},
|
|
711
|
-
buildNotifyHandlePrompt(input) {
|
|
712
|
-
const { notifyEvent, triggerEvent, ruleNotice, subAgentResult, channel } = input;
|
|
713
|
-
const triggerMeta = triggerEvent.metadata ?? {};
|
|
714
|
-
const legacyPayload = notifyEvent.payload ?? {};
|
|
715
|
-
const rawTaskId = notifyEvent.taskId ?? legacyPayload.associatedTaskId;
|
|
716
|
-
const bgId = notifyEvent.backgroundTaskId ?? legacyPayload.backgroundTaskId;
|
|
717
|
-
const shortBgId = bgId ? bgId.split("_").pop() : undefined;
|
|
718
|
-
const taskLabel = rawTaskId !== undefined ? `Task #${rawTaskId}` : shortBgId ? `后台任务 ${shortBgId}` : "后台任务";
|
|
719
|
-
const desc = notifyEvent.description ?? legacyPayload.description ?? "后台任务";
|
|
720
|
-
const result = (subAgentResult?.summary && subAgentResult.summary.trim() ? subAgentResult.summary : undefined) ?? notifyEvent.result ?? notifyEvent.error ?? legacyPayload.result ?? legacyPayload.error ?? "(无结果)";
|
|
721
|
-
const cwd = typeof triggerMeta.cwd === "string" ? triggerMeta.cwd : "(未知)";
|
|
722
|
-
const cliSession = typeof triggerMeta.sessionId === "string" ? triggerMeta.sessionId : "(未知)";
|
|
723
|
-
const query = typeof triggerEvent.text === "string" && triggerEvent.text ? triggerEvent.text : "(未记录)";
|
|
724
|
-
const { headline } = describeNotifyEvent(notifyEvent.type, taskLabel, desc);
|
|
725
|
-
const channelLabel = channel?.esType ?? CLI_COMMAND_ES_TYPE;
|
|
726
|
-
return {
|
|
727
|
-
promptText: [
|
|
728
|
-
`# 后台任务通知(渠道:CLI 命令行)`,
|
|
729
|
-
``,
|
|
730
|
-
`## 触发渠道`,
|
|
731
|
-
`- 渠道类型: \`${channelLabel}\`(用户在**终端**直接输入 query 触发,不是飞书消息)`,
|
|
732
|
-
`- 工作目录: \`${cwd}\``,
|
|
733
|
-
`- CLI session: \`${cliSession}\``,
|
|
734
|
-
`- 用户原始 query: ${query.length > 400 ? query.slice(0, 400) + "…" : query}`,
|
|
735
|
-
`- **回复方式: 直接输出到 stdout(终端)**。`,
|
|
736
|
-
``,
|
|
737
|
-
`⚠️ **本渠道没有 IM 通道,不要调用 lark-cli**。`,
|
|
738
|
-
` 不要尝试 \`lark-cli im +messages-send\` / \`+messages-reply\`,`,
|
|
739
|
-
` 不要编造 chatId(\`cli:*\` 不是真实飞书会话,发过去必然失败),`,
|
|
740
|
-
` 也不要调用任何飞书相关工具。用户就在终端前等你的文字输出。`,
|
|
741
|
-
``,
|
|
742
|
-
`## 系统回执`,
|
|
743
|
-
renderRuleNoticeSectionForCli(ruleNotice, headline),
|
|
744
|
-
``,
|
|
745
|
-
`## 事件`,
|
|
746
|
-
headline,
|
|
747
|
-
``,
|
|
748
|
-
`## Sub-agent 输出`,
|
|
749
|
-
result,
|
|
750
|
-
``,
|
|
751
|
-
`## 你要做什么`,
|
|
752
|
-
`1. 把上面 Sub-agent 的结论整理成**给终端用户直接读的一段话**。`,
|
|
753
|
-
`2. **直接输出这段文字**(它就是用户看到的内容),不要包 JSON、不要写文件。`,
|
|
754
|
-
`3. 若「系统回执」显示已送达,只讲**增量**;未送达则把结论讲完整。`
|
|
755
|
-
].join(`
|
|
756
|
-
`)
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
// src/env/event-source/event-source-handlers.ts
|
|
762
|
-
import { spawn as spawn3, exec as execSync } from "child_process";
|
|
763
|
-
import { promisify as promisify2 } from "node:util";
|
|
764
|
-
init_global_hook_manager();
|
|
765
|
-
init_context();
|
|
766
|
-
|
|
767
|
-
// src/env/task/plugins/lark-card-action-handler.ts
|
|
768
|
-
init_logger();
|
|
769
|
-
import { exec } from "node:child_process";
|
|
770
|
-
import { promisify } from "node:util";
|
|
771
|
-
var logger2 = createLogger("lark-card-action-handler");
|
|
772
|
-
var execAsync = promisify(exec);
|
|
773
|
-
var CTX_FIELD_CHAT_ID = "larkPerTaskChatId";
|
|
774
|
-
var CTX_FIELD_CHAT_NAME = "larkPerTaskChatName";
|
|
775
|
-
var CTX_FIELD_CHAT_CREATED_AT = "larkPerTaskChatCreatedAt";
|
|
776
|
-
var CTX_FIELD_CHAT_TASK_ID = "larkPerTaskChatTaskId";
|
|
777
|
-
var CTX_FIELD_CHAT_INVITEES = "larkPerTaskChatInvitees";
|
|
778
|
-
var cardActionInflight = new Map;
|
|
779
|
-
async function handleCardAction(event, ports) {
|
|
780
|
-
const { action, taskId: taskIdRaw } = event.actionValue;
|
|
781
|
-
const taskId = typeof taskIdRaw === "string" ? parseInt(taskIdRaw, 10) : taskIdRaw;
|
|
782
|
-
if (!taskId || Number.isNaN(taskId)) {
|
|
783
|
-
return { ok: false, action, error: `Invalid taskId: ${taskIdRaw}` };
|
|
784
|
-
}
|
|
785
|
-
if (action !== "create_group" && action !== "disband_group") {
|
|
786
|
-
return { ok: false, action, error: `Unknown action: ${action}` };
|
|
787
|
-
}
|
|
788
|
-
const inflightKey = `${event.profile ?? "-"}:${event.messageId || String(taskId)}:${action}`;
|
|
789
|
-
const inflight = cardActionInflight.get(inflightKey);
|
|
790
|
-
if (inflight) {
|
|
791
|
-
logger2.warn(`handleCardAction: ${inflightKey} 已有同操作在处理中(疑似用户重复点击),本次忽略。`);
|
|
792
|
-
return { ok: false, action, error: `重复点击:${inflightKey} 正在处理中,已忽略` };
|
|
793
|
-
}
|
|
794
|
-
const pending = handleCardActionInner(event, ports, taskId);
|
|
795
|
-
cardActionInflight.set(inflightKey, pending);
|
|
796
|
-
try {
|
|
797
|
-
return await pending;
|
|
798
|
-
} finally {
|
|
799
|
-
cardActionInflight.delete(inflightKey);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
async function handleCardActionInner(event, ports, taskId) {
|
|
803
|
-
const { action } = event.actionValue;
|
|
804
|
-
if (!ports.getTask) {
|
|
805
|
-
return { ok: false, action, error: "ports.getTask not injected" };
|
|
806
|
-
}
|
|
807
|
-
const task = await ports.getTask(taskId);
|
|
808
|
-
if (!task) {
|
|
809
|
-
return { ok: false, action, error: `Task #${taskId} not found` };
|
|
810
|
-
}
|
|
811
|
-
const existingChatId = extractPerTaskChatId(task.context);
|
|
812
|
-
if (action === "create_group") {
|
|
813
|
-
return await handleCreateGroup(event, ports, task, existingChatId);
|
|
814
|
-
} else {
|
|
815
|
-
return await handleDisbandGroup(event, ports, task, existingChatId);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
function buildCardAfterGroupChange(event, ports, task, perTaskChatId, perTaskChatName) {
|
|
819
|
-
const meta = buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName);
|
|
820
|
-
const preserved = ports.notifier?.updateMetaAndBuildCard?.(event.messageId, meta, { clearPerTaskChat: !perTaskChatId });
|
|
821
|
-
if (preserved) {
|
|
822
|
-
logger2.debug(`buildCardAfterGroupChange: 复用 notifier 累积状态(里程碑历史保留)messageId=${event.messageId}`);
|
|
823
|
-
return preserved;
|
|
824
|
-
}
|
|
825
|
-
logger2.warn(`buildCardAfterGroupChange: notifier 无 messageId=${event.messageId} 的状态,` + `fallback 重建卡片(该卡片的历史里程碑可能丢失)`);
|
|
826
|
-
return buildTaskCard({
|
|
827
|
-
status: "running",
|
|
828
|
-
meta,
|
|
829
|
-
milestones: [
|
|
830
|
-
{ title: "Task 创建", timestamp: new Date().toISOString(), description: task.current_status }
|
|
831
|
-
]
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
async function handleCreateGroup(event, ports, task, existingChatId) {
|
|
835
|
-
if (existingChatId) {
|
|
836
|
-
const existingName = extractPerTaskChatName(task.context);
|
|
837
|
-
logger2.info(`handleCreateGroup: Task #${task.id} 已有专属群 ${existingChatId}(幂等命中,不重复建群)`);
|
|
838
|
-
return {
|
|
839
|
-
ok: true,
|
|
840
|
-
action: "create_group",
|
|
841
|
-
newChatId: existingChatId,
|
|
842
|
-
cardUpdate: buildCardAfterGroupChange(event, ports, task, existingChatId, existingName)
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
if (!ports.createGroupChat) {
|
|
846
|
-
return { ok: false, action: "create_group", error: "ports.createGroupChat not injected" };
|
|
847
|
-
}
|
|
848
|
-
try {
|
|
849
|
-
const renderedName = truncateText(`Task #${task.id} - ${task.title ?? "(无标题)"}`, 60);
|
|
850
|
-
const description = truncateText(`roy-agent task #${task.id} - ${task.project_path ?? ""}`, 100);
|
|
851
|
-
const operatorOpenId = extractOperatorOpenId(event.operatorId);
|
|
852
|
-
const initialUserIds = operatorOpenId ? [operatorOpenId] : undefined;
|
|
853
|
-
const chatId = await ports.createGroupChat({
|
|
854
|
-
name: renderedName,
|
|
855
|
-
description,
|
|
856
|
-
profile: event.profile,
|
|
857
|
-
initialUserIds
|
|
858
|
-
});
|
|
859
|
-
let invitedOpenIds = [];
|
|
860
|
-
if (operatorOpenId) {
|
|
861
|
-
if (initialUserIds) {
|
|
862
|
-
invitedOpenIds = [operatorOpenId];
|
|
863
|
-
logger2.debug(`handleCreateGroup: ${operatorOpenId} 已在建群时邀请(initialUserIds 路径)`);
|
|
864
|
-
} else if (ports.inviteUser) {
|
|
865
|
-
try {
|
|
866
|
-
await ports.inviteUser(chatId, operatorOpenId, event.profile);
|
|
867
|
-
invitedOpenIds = [operatorOpenId];
|
|
868
|
-
logger2.debug(`handleCreateGroup: 走 inviteUser 端口邀请 ${operatorOpenId} 进 ${chatId}`);
|
|
869
|
-
} catch (err) {
|
|
870
|
-
logger2.warn(`handleCreateGroup: inviteUser(${chatId}, ${operatorOpenId}) 失败(不阻断主流程): ${err.message}`);
|
|
871
|
-
}
|
|
872
|
-
} else {
|
|
873
|
-
logger2.warn(`handleCreateGroup: createGroupChat 不支持 initialUserIds 且 inviteUser 未注入,` + `${operatorOpenId} 未被拉进群 ${chatId}。` + `需升级 caller(bridge/real impl)使用 ports.inviteUser 或实现 createGroupChat.initialUserIds 参数。`);
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
const newContext = patchContext(task.context ?? "{}", {
|
|
877
|
-
[CTX_FIELD_CHAT_ID]: chatId,
|
|
878
|
-
[CTX_FIELD_CHAT_NAME]: renderedName,
|
|
879
|
-
[CTX_FIELD_CHAT_CREATED_AT]: new Date().toISOString(),
|
|
880
|
-
[CTX_FIELD_CHAT_TASK_ID]: task.id,
|
|
881
|
-
[CTX_FIELD_CHAT_INVITEES]: invitedOpenIds.length > 0 ? invitedOpenIds : undefined
|
|
882
|
-
});
|
|
883
|
-
if (ports.updateTaskContext) {
|
|
884
|
-
await ports.updateTaskContext(task.id, newContext);
|
|
885
|
-
}
|
|
886
|
-
const cardUpdate = buildCardAfterGroupChange(event, ports, task, chatId, renderedName);
|
|
887
|
-
return { ok: true, action: "create_group", newChatId: chatId, cardUpdate };
|
|
888
|
-
} catch (err) {
|
|
889
|
-
logger2.error(`handleCardAction.create_group failed: ${err.message}`);
|
|
890
|
-
return { ok: false, action: "create_group", error: err.message };
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
function extractOperatorOpenId(raw) {
|
|
894
|
-
if (!raw)
|
|
895
|
-
return;
|
|
896
|
-
if (typeof raw === "string")
|
|
897
|
-
return raw;
|
|
898
|
-
if (typeof raw === "object" && typeof raw.open_id === "string")
|
|
899
|
-
return raw.open_id;
|
|
900
|
-
return;
|
|
901
|
-
}
|
|
902
|
-
async function handleDisbandGroup(event, ports, task, existingChatId) {
|
|
903
|
-
if (!existingChatId) {
|
|
904
|
-
return {
|
|
905
|
-
ok: false,
|
|
906
|
-
action: "disband_group",
|
|
907
|
-
error: `Task #${task.id} 无专属群(context.larkPerTaskChatId 空),无需解散`
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
if (!ports.deleteGroupChat) {
|
|
911
|
-
return { ok: false, action: "disband_group", error: "ports.deleteGroupChat not injected" };
|
|
912
|
-
}
|
|
913
|
-
try {
|
|
914
|
-
await ports.deleteGroupChat(existingChatId, event.profile);
|
|
915
|
-
const newContext = patchContext(task.context ?? "{}", {
|
|
916
|
-
[CTX_FIELD_CHAT_ID]: undefined,
|
|
917
|
-
[CTX_FIELD_CHAT_NAME]: undefined,
|
|
918
|
-
[CTX_FIELD_CHAT_CREATED_AT]: undefined,
|
|
919
|
-
[CTX_FIELD_CHAT_TASK_ID]: undefined
|
|
920
|
-
});
|
|
921
|
-
if (ports.updateTaskContext) {
|
|
922
|
-
await ports.updateTaskContext(task.id, newContext);
|
|
923
|
-
}
|
|
924
|
-
const cardUpdate = buildCardAfterGroupChange(event, ports, task, undefined, undefined);
|
|
925
|
-
return { ok: true, action: "disband_group", cardUpdate };
|
|
926
|
-
} catch (err) {
|
|
927
|
-
logger2.error(`handleCardAction.disband_group failed: ${err.message}`);
|
|
928
|
-
return { ok: false, action: "disband_group", error: err.message };
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
function patchContext(contextStr, changes) {
|
|
932
|
-
let obj = {};
|
|
933
|
-
try {
|
|
934
|
-
const parsed = JSON.parse(contextStr);
|
|
935
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
936
|
-
obj = parsed;
|
|
937
|
-
}
|
|
938
|
-
} catch {
|
|
939
|
-
obj = {};
|
|
940
|
-
}
|
|
941
|
-
for (const [k, v] of Object.entries(changes)) {
|
|
942
|
-
if (v === undefined) {
|
|
943
|
-
delete obj[k];
|
|
944
|
-
} else {
|
|
945
|
-
obj[k] = v;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
return JSON.stringify(obj);
|
|
949
|
-
}
|
|
950
|
-
function buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName) {
|
|
951
|
-
return {
|
|
952
|
-
id: task.id,
|
|
953
|
-
title: task.title,
|
|
954
|
-
description: task.description,
|
|
955
|
-
goals: task.goals,
|
|
956
|
-
parentTaskId: typeof task.parent_task_id === "number" && task.parent_task_id > 0 ? task.parent_task_id : undefined,
|
|
957
|
-
priority: task.priority,
|
|
958
|
-
type: task.type,
|
|
959
|
-
projectPath: task.project_path,
|
|
960
|
-
context: task.context,
|
|
961
|
-
currentStatus: task.current_status,
|
|
962
|
-
perTaskChatId,
|
|
963
|
-
perTaskChatName
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
async function deleteGroupChatViaLarkCli(chatId, profile) {
|
|
967
|
-
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
968
|
-
try {
|
|
969
|
-
const { stdout: authStdout } = await execAsync(`lark-cli ${profileFlag}auth status --json`, { timeout: 15000 });
|
|
970
|
-
const appId = JSON.parse(authStdout).appId;
|
|
971
|
-
if (!appId) {
|
|
972
|
-
throw new Error(`lark-cli auth status 未返回 appId: ${authStdout.slice(0, 200)}`);
|
|
973
|
-
}
|
|
974
|
-
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`;
|
|
975
|
-
const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
|
|
976
|
-
logger2.debug(`deleteGroupChatViaLarkCli(${chatId}) bot 退群 ok: ` + `stdout=${stdout?.slice(0, 300)} stderr=${stderr?.slice(0, 300)}`);
|
|
977
|
-
} catch (err) {
|
|
978
|
-
logger2.warn(`deleteGroupChatViaLarkCli(${chatId}) bot 退群失败(task.context 仍会清掉,` + `飞书侧群会保留):${err.message}`);
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
function shellQuote(s) {
|
|
982
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
983
|
-
}
|
|
984
|
-
function truncateText(text, maxLen) {
|
|
985
|
-
if (!text)
|
|
986
|
-
return "";
|
|
987
|
-
if (text.length <= maxLen)
|
|
988
|
-
return text;
|
|
989
|
-
return text.substring(0, Math.max(0, maxLen - 3)) + "...";
|
|
990
|
-
}
|
|
991
|
-
async function createGroupChatViaLarkCli(opts) {
|
|
992
|
-
const profileFlag = opts.profile ? `--profile ${opts.profile} ` : "";
|
|
993
|
-
const usersFlag = opts.initialUserIds && opts.initialUserIds.length > 0 ? `--users ${shellQuote(opts.initialUserIds.join(","))} ` : "";
|
|
994
|
-
const cmd = `lark-cli ${profileFlag}im +chat-create --name ${shellQuote(truncateText(opts.name, 60))} ` + usersFlag + `--description ${shellQuote(truncateText(opts.description, 100))} --as bot`;
|
|
995
|
-
const { stdout } = await execAsync(cmd, { timeout: 60000 });
|
|
996
|
-
const m = stdout.match(/"chat_id"\s*:\s*"(oc_[a-f0-9]+)"/);
|
|
997
|
-
if (!m) {
|
|
998
|
-
throw new Error(`lark-cli +chat-create 返回无法解析: ${stdout.slice(0, 300)}`);
|
|
810
|
+
const from = msg.from || "unknown";
|
|
811
|
+
let content = "未知内容";
|
|
812
|
+
if (msg.content?.type === "text") {
|
|
813
|
+
content = String(msg.content?.body || "");
|
|
814
|
+
}
|
|
815
|
+
return `[${from}] ${content}`;
|
|
816
|
+
}
|
|
817
|
+
return `事件: ${evt.event || "unknown"}`;
|
|
999
818
|
}
|
|
1000
|
-
return m[1];
|
|
1001
819
|
}
|
|
820
|
+
var bountyIMHandler = {
|
|
821
|
+
type: "bounty-im",
|
|
822
|
+
validateConfig(config) {
|
|
823
|
+
const errors = [];
|
|
824
|
+
if (!config.id)
|
|
825
|
+
errors.push("EventSource ID is required");
|
|
826
|
+
if (!config.name)
|
|
827
|
+
errors.push("EventSource name is required");
|
|
828
|
+
try {
|
|
829
|
+
assertNoLegacyAddressField(config);
|
|
830
|
+
} catch (err) {
|
|
831
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
832
|
+
}
|
|
833
|
+
const { imServerUrl, email } = readBountyIMConfig(config);
|
|
834
|
+
if (!email) {
|
|
835
|
+
errors.push("Bounty IM email is required (v0.14 strict email-only; top-level email or options.email)");
|
|
836
|
+
}
|
|
837
|
+
if (email !== undefined && email !== "") {
|
|
838
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
839
|
+
errors.push("Bounty IM email format invalid (expected: user@domain.tld)");
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (!imServerUrl) {
|
|
843
|
+
errors.push("Bounty IM imServerUrl is required (top-level imServerUrl or options.imServerUrl)");
|
|
844
|
+
} else if (!imServerUrl.startsWith("ws://") && !imServerUrl.startsWith("wss://")) {
|
|
845
|
+
errors.push("Bounty IM imServerUrl must start with ws:// or wss://");
|
|
846
|
+
}
|
|
847
|
+
return errors;
|
|
848
|
+
},
|
|
849
|
+
createInstance(config) {
|
|
850
|
+
return new BountyIMInstance(config);
|
|
851
|
+
}
|
|
852
|
+
};
|
|
1002
853
|
|
|
1003
854
|
// src/env/event-source/bounty-cli-handler.ts
|
|
1004
855
|
import { spawn } from "child_process";
|
|
@@ -1234,408 +1085,8 @@ var bountyCliHandler = {
|
|
|
1234
1085
|
|
|
1235
1086
|
// src/env/event-source/event-source-handlers.ts
|
|
1236
1087
|
init_logger();
|
|
1237
|
-
|
|
1238
|
-
// src/env/error-reply.ts
|
|
1239
|
-
init_logger();
|
|
1240
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
1241
|
-
var logger3 = createLogger("env:error-reply");
|
|
1242
|
-
var UPSTREAM_RETRY_PATTERNS = [
|
|
1243
|
-
/failed after \d+ attempts/i,
|
|
1244
|
-
/endpoint is unavailable/i,
|
|
1245
|
-
/upstream request failed/i,
|
|
1246
|
-
/rate.?limit/i,
|
|
1247
|
-
/\b429\b/,
|
|
1248
|
-
/\b50[0234]\b/,
|
|
1249
|
-
/service (is )?(temporarily )?unavailable/i,
|
|
1250
|
-
/\beconn(reset|refused|aborted)\b/i,
|
|
1251
|
-
/socket hang up/i,
|
|
1252
|
-
/fetch failed/i,
|
|
1253
|
-
/network error/i,
|
|
1254
|
-
/overloaded/i
|
|
1255
|
-
];
|
|
1256
|
-
var TIMEOUT_PATTERNS = [
|
|
1257
|
-
/timeout/i,
|
|
1258
|
-
/timed out/i,
|
|
1259
|
-
/deadline exceeded/i,
|
|
1260
|
-
/etimedout/i,
|
|
1261
|
-
/aborted due to timeout/i
|
|
1262
|
-
];
|
|
1263
|
-
var TRANSPORT_ABORT_PATTERNS = [
|
|
1264
|
-
/requestabortederror/i,
|
|
1265
|
-
/econnaborted/i,
|
|
1266
|
-
/connection (was )?(aborted|reset|closed)/i,
|
|
1267
|
-
/premature close/i
|
|
1268
|
-
];
|
|
1269
|
-
var ABORT_PATTERNS = [
|
|
1270
|
-
/\babort(?:ed|ing)?\b/i,
|
|
1271
|
-
/operation was cancelled/i,
|
|
1272
|
-
/operation was canceled/i,
|
|
1273
|
-
/sigint/i,
|
|
1274
|
-
/\binterrupted\b/i,
|
|
1275
|
-
/exiting due to (signal|shutdown)/i
|
|
1276
|
-
];
|
|
1277
|
-
var CONTEXT_PATTERNS = [
|
|
1278
|
-
/context threshold/i,
|
|
1279
|
-
/context (window|length|limit)/i,
|
|
1280
|
-
/token limit/i,
|
|
1281
|
-
/maximum context/i,
|
|
1282
|
-
/ctrieve\b/,
|
|
1283
|
-
/CTX_001/
|
|
1284
|
-
];
|
|
1285
|
-
var ASK_USER_PATTERNS = [/__ASK_USER_ERROR__/];
|
|
1286
|
-
function collectErrorText(error, depth = 0) {
|
|
1287
|
-
const parts = [];
|
|
1288
|
-
const push = (v) => {
|
|
1289
|
-
if (typeof v === "string" && v.length > 0)
|
|
1290
|
-
parts.push(v);
|
|
1291
|
-
};
|
|
1292
|
-
if (error === null || error === undefined)
|
|
1293
|
-
return "";
|
|
1294
|
-
if (typeof error === "string") {
|
|
1295
|
-
push(error);
|
|
1296
|
-
return parts.join(`
|
|
1297
|
-
`);
|
|
1298
|
-
}
|
|
1299
|
-
if (typeof error !== "object") {
|
|
1300
|
-
push(String(error));
|
|
1301
|
-
return parts.join(`
|
|
1302
|
-
`);
|
|
1303
|
-
}
|
|
1304
|
-
const e = error;
|
|
1305
|
-
push(e.name);
|
|
1306
|
-
push(e.message);
|
|
1307
|
-
for (const key of ["statusCode", "status", "code"]) {
|
|
1308
|
-
const v = e[key];
|
|
1309
|
-
if (typeof v === "number" || typeof v === "string")
|
|
1310
|
-
push(String(v));
|
|
1311
|
-
}
|
|
1312
|
-
if (depth < 3) {
|
|
1313
|
-
const nested = collectErrorText(e.lastError, depth + 1);
|
|
1314
|
-
if (nested)
|
|
1315
|
-
parts.push(nested);
|
|
1316
|
-
if (Array.isArray(e.errors)) {
|
|
1317
|
-
for (const inner of e.errors.slice(-3)) {
|
|
1318
|
-
const s = collectErrorText(inner, depth + 1);
|
|
1319
|
-
if (s)
|
|
1320
|
-
parts.push(s);
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
const cause = collectErrorText(e.cause, depth + 1);
|
|
1324
|
-
if (cause)
|
|
1325
|
-
parts.push(cause);
|
|
1326
|
-
}
|
|
1327
|
-
return parts.join(`
|
|
1328
|
-
`);
|
|
1329
|
-
}
|
|
1330
|
-
function truncate(text, max) {
|
|
1331
|
-
if (text.length <= max)
|
|
1332
|
-
return text;
|
|
1333
|
-
return text.slice(0, max) + "…";
|
|
1334
|
-
}
|
|
1335
|
-
var FAILURE_RULES = [
|
|
1336
|
-
{
|
|
1337
|
-
id: "ask_user",
|
|
1338
|
-
kind: "ask_user",
|
|
1339
|
-
names: ["AskUserError"],
|
|
1340
|
-
patterns: ASK_USER_PATTERNS,
|
|
1341
|
-
notify: false,
|
|
1342
|
-
title: "等待用户回答",
|
|
1343
|
-
reason: "agent 正在等待用户回答,不是失败。",
|
|
1344
|
-
retryable: false
|
|
1345
|
-
},
|
|
1346
|
-
{
|
|
1347
|
-
id: "context_overflow",
|
|
1348
|
-
kind: "context_overflow",
|
|
1349
|
-
names: ["ContextError"],
|
|
1350
|
-
patterns: CONTEXT_PATTERNS,
|
|
1351
|
-
notify: true,
|
|
1352
|
-
title: "上下文超出限制",
|
|
1353
|
-
reason: "这次对话的上下文超出模型窗口,自动压缩也未能恢复。建议新开一个会话继续,或先让我压缩当前会话。",
|
|
1354
|
-
retryable: false
|
|
1355
|
-
},
|
|
1356
|
-
{
|
|
1357
|
-
id: "timeout",
|
|
1358
|
-
kind: "timeout",
|
|
1359
|
-
names: ["TimeoutError"],
|
|
1360
|
-
patterns: TIMEOUT_PATTERNS,
|
|
1361
|
-
notify: true,
|
|
1362
|
-
title: "执行超时",
|
|
1363
|
-
reason: "本次执行等待响应超时(上游长时间未返回或网络卡住)。建议稍后重试,或把任务拆小一些。",
|
|
1364
|
-
retryable: true
|
|
1365
|
-
},
|
|
1366
|
-
{
|
|
1367
|
-
id: "upstream_retry",
|
|
1368
|
-
kind: "upstream_retry",
|
|
1369
|
-
names: ["AI_RetryError", "RetryError", "APICallError", "AI_APICallError"],
|
|
1370
|
-
patterns: UPSTREAM_RETRY_PATTERNS,
|
|
1371
|
-
notify: true,
|
|
1372
|
-
title: "模型服务暂时不可用(重试已用尽)",
|
|
1373
|
-
reason: "调用模型时上游持续返回错误(如 429 限流 / 503 不可用),自动重试多次后仍未成功。" + "这不是你的操作问题,也不是仓库代码的问题 —— 稍后重发一次即可。",
|
|
1374
|
-
retryable: true
|
|
1375
|
-
},
|
|
1376
|
-
{
|
|
1377
|
-
id: "upstream_aborted",
|
|
1378
|
-
kind: "upstream_aborted",
|
|
1379
|
-
names: ["RequestAbortedError"],
|
|
1380
|
-
patterns: TRANSPORT_ABORT_PATTERNS,
|
|
1381
|
-
unless: TIMEOUT_PATTERNS,
|
|
1382
|
-
notify: true,
|
|
1383
|
-
title: "与模型服务的连接中断",
|
|
1384
|
-
reason: "请求在传输过程中被中断(连接被重置 / 代理提前关闭),不是你的操作问题。稍后重发一次即可。",
|
|
1385
|
-
retryable: true
|
|
1386
|
-
},
|
|
1387
|
-
{
|
|
1388
|
-
id: "aborted",
|
|
1389
|
-
kind: "aborted",
|
|
1390
|
-
names: ["AbortError", "AbortSignal"],
|
|
1391
|
-
patterns: ABORT_PATTERNS,
|
|
1392
|
-
unless: [...TIMEOUT_PATTERNS, ...TRANSPORT_ABORT_PATTERNS],
|
|
1393
|
-
notify: true,
|
|
1394
|
-
title: "执行被中断",
|
|
1395
|
-
reason: "本次执行在完成前被中断(可能你手动取消了,或进程收到了停止信号)。你可以直接让我重新执行。",
|
|
1396
|
-
retryable: true
|
|
1397
|
-
}
|
|
1398
|
-
];
|
|
1399
|
-
var UNKNOWN_RULE = {
|
|
1400
|
-
id: "unknown",
|
|
1401
|
-
kind: "unknown",
|
|
1402
|
-
notify: true,
|
|
1403
|
-
title: "执行异常失败",
|
|
1404
|
-
reason: "本次执行遇到了未预期的异常,已中止。你可以重试;如果持续失败,请把这条消息里的技术细节发我。",
|
|
1405
|
-
retryable: true
|
|
1406
|
-
};
|
|
1407
|
-
function ruleMatches(rule, name, text) {
|
|
1408
|
-
const lowered = name.toLowerCase();
|
|
1409
|
-
const nameHit = rule.names?.some((n) => n.toLowerCase() === lowered) ?? false;
|
|
1410
|
-
const patternHit = rule.patterns?.some((p) => p.test(text)) ?? false;
|
|
1411
|
-
if (!nameHit && !patternHit)
|
|
1412
|
-
return false;
|
|
1413
|
-
if (rule.unless?.some((p) => p.test(text)))
|
|
1414
|
-
return false;
|
|
1415
|
-
return true;
|
|
1416
|
-
}
|
|
1417
|
-
function classifyReactFailure(error) {
|
|
1418
|
-
const text = collectErrorText(error);
|
|
1419
|
-
const name = error && typeof error === "object" ? String(error.name ?? "") : "";
|
|
1420
|
-
const technical = text ? truncate(text.replace(/\s*\n\s*/g, " | "), 400) : undefined;
|
|
1421
|
-
const rule = FAILURE_RULES.find((r) => ruleMatches(r, name, text)) ?? UNKNOWN_RULE;
|
|
1422
|
-
return {
|
|
1423
|
-
kind: rule.kind,
|
|
1424
|
-
ruleId: rule.id,
|
|
1425
|
-
title: rule.title,
|
|
1426
|
-
reason: rule.reason,
|
|
1427
|
-
retryable: rule.retryable,
|
|
1428
|
-
notify: rule.notify,
|
|
1429
|
-
technical
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
var MAX_QUERY_PREVIEW = 200;
|
|
1433
|
-
function buildFailureReplyMarkdown(info, opts) {
|
|
1434
|
-
const lines = [];
|
|
1435
|
-
lines.push(`❌ **${info.title}**`);
|
|
1436
|
-
lines.push("");
|
|
1437
|
-
lines.push(info.reason);
|
|
1438
|
-
const query = opts?.query?.trim();
|
|
1439
|
-
if (query) {
|
|
1440
|
-
lines.push("");
|
|
1441
|
-
lines.push(`> 原始请求:${truncate(query.replace(/\s+/g, " "), MAX_QUERY_PREVIEW)}`);
|
|
1442
|
-
}
|
|
1443
|
-
if (info.retryable) {
|
|
1444
|
-
lines.push("");
|
|
1445
|
-
lines.push("**你可以:** 直接再发一次(重发即可,无需额外操作)。");
|
|
1446
|
-
} else {
|
|
1447
|
-
lines.push("");
|
|
1448
|
-
lines.push("**你可以:** 新开一个会话,或先让我压缩当前会话。");
|
|
1449
|
-
}
|
|
1450
|
-
const technical = info.technical;
|
|
1451
|
-
if (technical) {
|
|
1452
|
-
lines.push("");
|
|
1453
|
-
lines.push(`> 技术细节:${truncate(technical, 300)}`);
|
|
1454
|
-
}
|
|
1455
|
-
return lines.join(`
|
|
1456
|
-
`);
|
|
1457
|
-
}
|
|
1458
|
-
function extractReplyChannelFromEvent(envEvent) {
|
|
1459
|
-
if (!envEvent || typeof envEvent !== "object")
|
|
1460
|
-
return;
|
|
1461
|
-
const e = envEvent;
|
|
1462
|
-
const candidates = [
|
|
1463
|
-
e.metadata?.replyChannel,
|
|
1464
|
-
e.payload?.replyChannel
|
|
1465
|
-
];
|
|
1466
|
-
for (const c of candidates) {
|
|
1467
|
-
if (c && typeof c === "object") {
|
|
1468
|
-
const ch = c;
|
|
1469
|
-
if (ch.chatId || ch.messageId)
|
|
1470
|
-
return ch;
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
return;
|
|
1474
|
-
}
|
|
1475
|
-
function isUsableChannel(ch) {
|
|
1476
|
-
if (!ch)
|
|
1477
|
-
return false;
|
|
1478
|
-
if (!ch.chatId && !ch.messageId)
|
|
1479
|
-
return false;
|
|
1480
|
-
if (ch.type && !ch.type.toLowerCase().includes("lark"))
|
|
1481
|
-
return false;
|
|
1482
|
-
return true;
|
|
1483
|
-
}
|
|
1484
|
-
var repliedTriggers = new Map;
|
|
1485
|
-
var CHAT_DEDUP_WINDOW_MS = 5 * 60000;
|
|
1486
|
-
var MAX_DEDUP_ENTRIES = 1000;
|
|
1487
|
-
function makeDedupKey(channel, info, explicit, now = Date.now()) {
|
|
1488
|
-
if (explicit)
|
|
1489
|
-
return explicit;
|
|
1490
|
-
if (channel.messageId)
|
|
1491
|
-
return `msg:${channel.messageId}`;
|
|
1492
|
-
if (channel.chatId) {
|
|
1493
|
-
const bucket = Math.floor(now / CHAT_DEDUP_WINDOW_MS);
|
|
1494
|
-
return `chat:${channel.chatId}:${info.kind}:${bucket}`;
|
|
1495
|
-
}
|
|
1496
|
-
return "unknown";
|
|
1497
|
-
}
|
|
1498
|
-
function isDuplicate(key, now = Date.now()) {
|
|
1499
|
-
const at = repliedTriggers.get(key);
|
|
1500
|
-
if (at === undefined)
|
|
1501
|
-
return false;
|
|
1502
|
-
if (key.startsWith("chat:") && now - at >= CHAT_DEDUP_WINDOW_MS) {
|
|
1503
|
-
repliedTriggers.delete(key);
|
|
1504
|
-
return false;
|
|
1505
|
-
}
|
|
1506
|
-
return true;
|
|
1507
|
-
}
|
|
1508
|
-
function rememberTrigger(key, now = Date.now()) {
|
|
1509
|
-
if (repliedTriggers.size >= MAX_DEDUP_ENTRIES) {
|
|
1510
|
-
const oldest = repliedTriggers.keys().next().value;
|
|
1511
|
-
if (oldest !== undefined)
|
|
1512
|
-
repliedTriggers.delete(oldest);
|
|
1513
|
-
}
|
|
1514
|
-
repliedTriggers.set(key, now);
|
|
1515
|
-
}
|
|
1516
|
-
var DEFAULT_REPLY_TIMEOUT_MS = 5000;
|
|
1517
|
-
function spawnLarkReply(options) {
|
|
1518
|
-
return new Promise((resolve, reject) => {
|
|
1519
|
-
const { chatId, messageId, content, profile } = options;
|
|
1520
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_REPLY_TIMEOUT_MS;
|
|
1521
|
-
let settled = false;
|
|
1522
|
-
let timer;
|
|
1523
|
-
const finish = (err) => {
|
|
1524
|
-
if (settled)
|
|
1525
|
-
return;
|
|
1526
|
-
settled = true;
|
|
1527
|
-
if (timer)
|
|
1528
|
-
clearTimeout(timer);
|
|
1529
|
-
if (err)
|
|
1530
|
-
reject(err);
|
|
1531
|
-
else
|
|
1532
|
-
resolve();
|
|
1533
|
-
};
|
|
1534
|
-
const normalizedContent = normalizeLarkAtMentions(content);
|
|
1535
|
-
let args;
|
|
1536
|
-
if (messageId) {
|
|
1537
|
-
args = [
|
|
1538
|
-
"im",
|
|
1539
|
-
"+messages-reply",
|
|
1540
|
-
"--message-id",
|
|
1541
|
-
messageId,
|
|
1542
|
-
"--markdown",
|
|
1543
|
-
normalizedContent,
|
|
1544
|
-
"--as",
|
|
1545
|
-
"bot"
|
|
1546
|
-
];
|
|
1547
|
-
} else if (chatId) {
|
|
1548
|
-
args = [
|
|
1549
|
-
"im",
|
|
1550
|
-
"+messages-send",
|
|
1551
|
-
"--chat-id",
|
|
1552
|
-
chatId,
|
|
1553
|
-
"--markdown",
|
|
1554
|
-
normalizedContent,
|
|
1555
|
-
"--as",
|
|
1556
|
-
"bot"
|
|
1557
|
-
];
|
|
1558
|
-
} else {
|
|
1559
|
-
finish(new Error("No target specified for failure reply"));
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1562
|
-
if (profile) {
|
|
1563
|
-
args.unshift("--profile", profile);
|
|
1564
|
-
}
|
|
1565
|
-
let proc;
|
|
1566
|
-
try {
|
|
1567
|
-
proc = spawn2("lark-cli", args, {
|
|
1568
|
-
shell: false,
|
|
1569
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1570
|
-
});
|
|
1571
|
-
} catch (err) {
|
|
1572
|
-
finish(err instanceof Error ? err : new Error(String(err)));
|
|
1573
|
-
return;
|
|
1574
|
-
}
|
|
1575
|
-
timer = setTimeout(() => {
|
|
1576
|
-
try {
|
|
1577
|
-
proc.kill("SIGKILL");
|
|
1578
|
-
} catch {}
|
|
1579
|
-
finish(new Error(`lark-cli timed out after ${timeoutMs}ms`));
|
|
1580
|
-
}, timeoutMs);
|
|
1581
|
-
timer.unref?.();
|
|
1582
|
-
let stderr = "";
|
|
1583
|
-
proc.stdout?.on("data", () => {});
|
|
1584
|
-
proc.stderr?.on("data", (data) => {
|
|
1585
|
-
stderr += data.toString();
|
|
1586
|
-
});
|
|
1587
|
-
proc.on("close", (code) => {
|
|
1588
|
-
if (code === 0)
|
|
1589
|
-
finish();
|
|
1590
|
-
else
|
|
1591
|
-
finish(new Error(`lark-cli exited with code ${code}: ${stderr}`));
|
|
1592
|
-
});
|
|
1593
|
-
proc.on("error", (err) => finish(err));
|
|
1594
|
-
});
|
|
1595
|
-
}
|
|
1596
|
-
async function notifyReactFailure(options) {
|
|
1597
|
-
const info = classifyReactFailure(options.error);
|
|
1598
|
-
if (!info.notify) {
|
|
1599
|
-
logger3.debug(`[#4418] 命中规则 ${info.ruleId}(非失败),不发失败说明` + (options.sessionId ? ` (session=${options.sessionId})` : ""));
|
|
1600
|
-
return { sent: false, kind: info.kind, info, skipped: "not_a_failure" };
|
|
1601
|
-
}
|
|
1602
|
-
const channel = (isUsableChannel(options.replyChannel) ? options.replyChannel : undefined) ?? extractReplyChannelFromEvent(options.envEvent);
|
|
1603
|
-
if (!isUsableChannel(channel)) {
|
|
1604
|
-
const hasTrigger = Boolean(options.envEvent);
|
|
1605
|
-
const noChannelMsg = `[#4418] React 中断(${info.kind}/${info.ruleId})无法定位 replyChannel,` + `飞书端收不到失败说明` + (hasTrigger ? " —— triggerEvent 存在但缺少 metadata.replyChannel" : "(本地执行,无 triggerEvent)") + (options.sessionId ? ` (session=${options.sessionId})` : "");
|
|
1606
|
-
if (hasTrigger)
|
|
1607
|
-
logger3.warn(noChannelMsg);
|
|
1608
|
-
else
|
|
1609
|
-
logger3.debug(noChannelMsg);
|
|
1610
|
-
return { sent: false, kind: info.kind, info, skipped: "no_channel" };
|
|
1611
|
-
}
|
|
1612
|
-
const dedupKey = makeDedupKey(channel, info, options.dedupKey);
|
|
1613
|
-
if (isDuplicate(dedupKey)) {
|
|
1614
|
-
logger3.info(`[#4418] React 中断(${info.kind})已回复过 ${dedupKey},跳过`);
|
|
1615
|
-
return { sent: false, kind: info.kind, info, skipped: "duplicate" };
|
|
1616
|
-
}
|
|
1617
|
-
rememberTrigger(dedupKey);
|
|
1618
|
-
const content = buildFailureReplyMarkdown(info, { query: options.query });
|
|
1619
|
-
try {
|
|
1620
|
-
await spawnLarkReply({
|
|
1621
|
-
chatId: channel.chatId,
|
|
1622
|
-
messageId: channel.messageId,
|
|
1623
|
-
content,
|
|
1624
|
-
profile: channel.profile,
|
|
1625
|
-
timeoutMs: options.replyTimeoutMs
|
|
1626
|
-
});
|
|
1627
|
-
logger3.info(`[#4418] React 中断(${info.kind}/${info.ruleId})已回飞书 ` + `(${channel.messageId ?? channel.chatId})`);
|
|
1628
|
-
return { sent: true, kind: info.kind, info };
|
|
1629
|
-
} catch (err) {
|
|
1630
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1631
|
-
repliedTriggers.delete(dedupKey);
|
|
1632
|
-
logger3.warn(`[#4418] React 中断(${info.kind}/${info.ruleId})回飞书失败(已忽略): ${errorMsg}`);
|
|
1633
|
-
return { sent: false, kind: info.kind, info, error: errorMsg };
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
// src/env/event-source/event-source-handlers.ts
|
|
1637
1088
|
var execAsync2 = promisify2(execSync);
|
|
1638
|
-
var
|
|
1089
|
+
var logger2 = createLogger("lark-cli:event-source");
|
|
1639
1090
|
var CHILD_READY_TIMEOUT_MS = 5000;
|
|
1640
1091
|
var GRACEFUL_STOP_TIMEOUT_MS = 5000;
|
|
1641
1092
|
var CARD_ACTION_EVENT_TYPE = "card.action.trigger";
|
|
@@ -1864,8 +1315,8 @@ class LarkCliInstance {
|
|
|
1864
1315
|
cardActionPorts;
|
|
1865
1316
|
setCardActionPorts(ports) {
|
|
1866
1317
|
this.cardActionPorts = ports;
|
|
1867
|
-
if (typeof
|
|
1868
|
-
|
|
1318
|
+
if (typeof logger2 !== "undefined" && logger2?.debug) {
|
|
1319
|
+
logger2.debug(`[LarkCliInstance] CardActionPorts injected: ` + `getTask=${typeof ports.getTask}, updateTaskContext=${typeof ports.updateTaskContext}, ` + `createGroupChat=${typeof ports.createGroupChat}, deleteGroupChat=${typeof ports.deleteGroupChat}, ` + `notifier=${typeof ports.notifier}`);
|
|
1869
1320
|
}
|
|
1870
1321
|
}
|
|
1871
1322
|
hasCardActionPorts() {
|
|
@@ -1892,7 +1343,7 @@ class LarkCliInstance {
|
|
|
1892
1343
|
if (this.status === "running")
|
|
1893
1344
|
return;
|
|
1894
1345
|
if (this.starting) {
|
|
1895
|
-
|
|
1346
|
+
logger2.warn(`[LarkCliInstance] start() 已在执行中,忽略重复调用`);
|
|
1896
1347
|
return;
|
|
1897
1348
|
}
|
|
1898
1349
|
this.starting = true;
|
|
@@ -1922,12 +1373,12 @@ class LarkCliInstance {
|
|
|
1922
1373
|
if (!consumesCardAction)
|
|
1923
1374
|
return;
|
|
1924
1375
|
if (this.config.eventTypes?.length && !matchEventType(CARD_ACTION_EVENT_TYPE, this.config.eventTypes)) {
|
|
1925
|
-
|
|
1376
|
+
logger2.warn(`[LarkCliInstance] eventKeys 含 ${CARD_ACTION_EVENT_TYPE},但 eventTypes=` + `${JSON.stringify(this.config.eventTypes)} 不含它 → 卡片点击会被**静默丢弃**。` + `请把 ${CARD_ACTION_EVENT_TYPE} 加进 eventTypes,或清空 eventTypes。`);
|
|
1926
1377
|
}
|
|
1927
|
-
|
|
1378
|
+
logger2.info(`[LarkCliInstance] 已监听 ${CARD_ACTION_EVENT_TYPE}。` + `⚠️ 若点击卡片无反应,先确认飞书开发者后台「事件与回调 → 回调配置」已开启该事件。`);
|
|
1928
1379
|
}
|
|
1929
1380
|
spawnConsumeChild(command) {
|
|
1930
|
-
return
|
|
1381
|
+
return spawn2("exec " + command, [], {
|
|
1931
1382
|
shell: true,
|
|
1932
1383
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1933
1384
|
windowsHide: true
|
|
@@ -1964,7 +1415,7 @@ class LarkCliInstance {
|
|
|
1964
1415
|
const conflicts = keys.filter((k) => busy.has(k));
|
|
1965
1416
|
if (conflicts.length > 0) {
|
|
1966
1417
|
const profile = this.config.profile;
|
|
1967
|
-
|
|
1418
|
+
logger2.warn(`[LarkCliInstance] eventKey 已被其它 consumer 占用(lark-cli 一个 key 只允许一个 consumer):` + `${conflicts.join(", ")}。本 event-source 启动后可能 exit 2。` + `用 \`lark-cli${profile ? ` --profile ${profile}` : ""} event status\` 查看,必要时 \`event stop\`。`);
|
|
1968
1419
|
}
|
|
1969
1420
|
}
|
|
1970
1421
|
async probeBusyEventKeys() {
|
|
@@ -2015,16 +1466,16 @@ class LarkCliInstance {
|
|
|
2015
1466
|
});
|
|
2016
1467
|
const handleExit = (code, source) => {
|
|
2017
1468
|
if (code !== 0 && code !== null) {
|
|
2018
|
-
|
|
1469
|
+
logger2.warn(`[LarkCliInstance] consume 子进程异常退出 code=${code} (${source}) cmd="${command}"`);
|
|
2019
1470
|
this.status = "error";
|
|
2020
1471
|
} else {
|
|
2021
|
-
|
|
1472
|
+
logger2.info(`[LarkCliInstance] consume 子进程已退出 code=${code} (${source}) cmd="${command}"`);
|
|
2022
1473
|
}
|
|
2023
1474
|
settle();
|
|
2024
1475
|
queueMicrotask(() => {
|
|
2025
1476
|
if (this.children.length > 0 && this.children.every((c) => !isChildAlive(c))) {
|
|
2026
1477
|
if (this.status !== "error") {
|
|
2027
|
-
|
|
1478
|
+
logger2.warn(`[LarkCliInstance] 所有 consume 子进程都已退出,状态从 running 改为 stopped`);
|
|
2028
1479
|
this.status = "stopped";
|
|
2029
1480
|
}
|
|
2030
1481
|
}
|
|
@@ -2033,12 +1484,12 @@ class LarkCliInstance {
|
|
|
2033
1484
|
child.on("exit", (code) => handleExit(code, "exit"));
|
|
2034
1485
|
child.on("close", (code) => handleExit(code, "close"));
|
|
2035
1486
|
child.on("error", (error) => {
|
|
2036
|
-
|
|
1487
|
+
logger2.error(`[LarkCliInstance] consume 子进程 error: ${error.message}`);
|
|
2037
1488
|
this.status = "error";
|
|
2038
1489
|
settle();
|
|
2039
1490
|
});
|
|
2040
1491
|
const timer = setTimeout(() => {
|
|
2041
|
-
|
|
1492
|
+
logger2.warn(`[LarkCliInstance] consume 子进程 ${CHILD_READY_TIMEOUT_MS}ms 内未见 ready marker: cmd="${command}"`);
|
|
2042
1493
|
settle();
|
|
2043
1494
|
}, CHILD_READY_TIMEOUT_MS);
|
|
2044
1495
|
ready.then(() => clearTimeout(timer));
|
|
@@ -2061,7 +1512,7 @@ class LarkCliInstance {
|
|
|
2061
1512
|
try {
|
|
2062
1513
|
child.kill("SIGTERM");
|
|
2063
1514
|
} catch (error) {
|
|
2064
|
-
|
|
1515
|
+
logger2.warn(`[LarkCliInstance] Error sending SIGTERM to ${child.pid}: ${error}`);
|
|
2065
1516
|
}
|
|
2066
1517
|
}
|
|
2067
1518
|
const allExited = Promise.all(children.map((child) => new Promise((resolve) => {
|
|
@@ -2116,12 +1567,12 @@ class LarkCliInstance {
|
|
|
2116
1567
|
}
|
|
2117
1568
|
wireCardActionPorts() {
|
|
2118
1569
|
if (this.cardActionPorts) {
|
|
2119
|
-
|
|
1570
|
+
logger2.debug("[LarkCliInstance] cardActionPorts 已由外部注入,跳过默认 cross-wire");
|
|
2120
1571
|
return;
|
|
2121
1572
|
}
|
|
2122
1573
|
const taskComponent = this.environment?.getComponent?.("task");
|
|
2123
1574
|
if (!taskComponent?.getTask || !taskComponent?.updateTask) {
|
|
2124
|
-
|
|
1575
|
+
logger2.debug(`[LarkCliInstance] card.action.trigger 端口未注入:taskComponent 不可用` + `(getTask=${typeof taskComponent?.getTask}, updateTask=${typeof taskComponent?.updateTask})。` + `卡片按钮点击会被跳过。`);
|
|
2125
1576
|
return;
|
|
2126
1577
|
}
|
|
2127
1578
|
const sharedNotifier = this.pluginInstances.get("LarkCliTaskNotifyPlugin")?.cardNotifier ?? this.pluginInstances.get("PerTaskGroupChatPlugin")?.cardNotifier;
|
|
@@ -2150,7 +1601,7 @@ class LarkCliInstance {
|
|
|
2150
1601
|
deleteGroupChat: (chatId, profile) => deleteGroupChatViaLarkCli(chatId, profile),
|
|
2151
1602
|
notifier: sharedNotifier ?? new LarkCardPatchNotifier
|
|
2152
1603
|
});
|
|
2153
|
-
|
|
1604
|
+
logger2.info(`[LarkCliInstance] card.action.trigger 端口已注入` + `(notifier ${sharedNotifier ? "复用插件共享实例" : "新建实例"})`);
|
|
2154
1605
|
}
|
|
2155
1606
|
getPluginHookPoints(plugin) {
|
|
2156
1607
|
if (Array.isArray(plugin.hooks)) {
|
|
@@ -2348,7 +1799,7 @@ class LarkCliInstance {
|
|
|
2348
1799
|
}
|
|
2349
1800
|
return null;
|
|
2350
1801
|
} catch (err) {
|
|
2351
|
-
|
|
1802
|
+
logger2.warn(`[LarkCliInstance] resolveChatTaskBinding failed for chatId=${chatId}: ${err}. ` + `群消息将不会注入 parentTaskId。`);
|
|
2352
1803
|
return null;
|
|
2353
1804
|
}
|
|
2354
1805
|
}
|
|
@@ -2370,7 +1821,7 @@ class LarkCliInstance {
|
|
|
2370
1821
|
processStreamWith(data, buffer, opts = {}) {
|
|
2371
1822
|
buffer.value += data;
|
|
2372
1823
|
if (buffer.value.length > 2 * 1024 * 1024) {
|
|
2373
|
-
|
|
1824
|
+
logger2.warn(`[LarkCliInstance] stream 行缓冲超过 2MB 仍无换行,丢弃(疑似非 NDJSON 输出)`);
|
|
2374
1825
|
buffer.value = "";
|
|
2375
1826
|
return;
|
|
2376
1827
|
}
|
|
@@ -2406,12 +1857,12 @@ class LarkCliInstance {
|
|
|
2406
1857
|
const now = Date.now();
|
|
2407
1858
|
const lastShared = LarkCliInstance.SHARED_SEEN.get(dedupKey);
|
|
2408
1859
|
if (lastShared !== undefined && now - lastShared < LarkCliInstance.SHARED_MSG_TTL_MS) {
|
|
2409
|
-
|
|
1860
|
+
logger2.warn(`[LarkCliInstance] duplicate lark-cli event suppressed across instances (event_id=${dedupKey}, ` + `lastSeen=${now - lastShared}ms ago). ` + `Task #4137: SHARED_SEEN 跨实例去重。`);
|
|
2410
1861
|
return;
|
|
2411
1862
|
}
|
|
2412
1863
|
const last = this.recentEventIds.get(dedupKey);
|
|
2413
1864
|
if (last !== undefined && now - last < this.DEDUP_WINDOW_MS) {
|
|
2414
|
-
|
|
1865
|
+
logger2.warn(`[LarkCliInstance] duplicate lark-cli event suppressed (event_id=${dedupKey}, ` + `lastSeen=${now - last}ms ago). ` + `Task #3977/3978: 防止同一飞书事件触发两次任务创建。`);
|
|
2415
1866
|
return;
|
|
2416
1867
|
}
|
|
2417
1868
|
LarkCliInstance.SHARED_SEEN.set(dedupKey, now);
|
|
@@ -2591,11 +2042,11 @@ class LarkCliInstance {
|
|
|
2591
2042
|
const chatId = event.chat_id || "";
|
|
2592
2043
|
const operatorId = event.operator_id?.open_id ?? event.operator_id ?? undefined;
|
|
2593
2044
|
if (!token || !messageId || !chatId) {
|
|
2594
|
-
|
|
2045
|
+
logger2.warn(`[LarkCliInstance] handleCardActionEvent: missing token/messageId/chatId, skip`);
|
|
2595
2046
|
return;
|
|
2596
2047
|
}
|
|
2597
2048
|
if (!this.cardActionPorts) {
|
|
2598
|
-
|
|
2049
|
+
logger2.warn(`[LarkCliInstance] handleCardActionEvent: cardActionPorts 未注入,` + `card.action.trigger 事件被跳过。` + `调 instance.setCardActionPorts({getTask, updateTaskContext, createGroupChat, deleteGroupChat, notifier}) 注入。`);
|
|
2599
2050
|
return;
|
|
2600
2051
|
}
|
|
2601
2052
|
const result = await handleCardAction({
|
|
@@ -2610,10 +2061,10 @@ class LarkCliInstance {
|
|
|
2610
2061
|
const patch = this.cardActionPorts.patchByToken ?? patchCardByLarkCli;
|
|
2611
2062
|
await patch(token, result.cardUpdate, profile);
|
|
2612
2063
|
} else {
|
|
2613
|
-
|
|
2064
|
+
logger2.warn(`[LarkCliInstance] handleCardActionEvent failed: ${result.error ?? "unknown"}`);
|
|
2614
2065
|
}
|
|
2615
2066
|
} catch (err) {
|
|
2616
|
-
|
|
2067
|
+
logger2.error(`[LarkCliInstance] handleCardActionEvent error: ${err.message}`);
|
|
2617
2068
|
}
|
|
2618
2069
|
}
|
|
2619
2070
|
async patchCardByToken(token, card, profile) {
|
|
@@ -2701,7 +2152,7 @@ class LarkCliInstance {
|
|
|
2701
2152
|
if (profile) {
|
|
2702
2153
|
args.unshift("--profile", profile);
|
|
2703
2154
|
}
|
|
2704
|
-
const proc =
|
|
2155
|
+
const proc = spawn2("lark-cli", args, {
|
|
2705
2156
|
shell: false,
|
|
2706
2157
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2707
2158
|
});
|
|
@@ -2748,7 +2199,7 @@ var NOTIFY_TERMINAL_TYPES = new Set([
|
|
|
2748
2199
|
"task.background.timeout",
|
|
2749
2200
|
"task.background.stopped"
|
|
2750
2201
|
]);
|
|
2751
|
-
function
|
|
2202
|
+
function describeNotifyEvent(type, taskId, desc) {
|
|
2752
2203
|
const actionable = NOTIFY_TERMINAL_TYPES.has(type);
|
|
2753
2204
|
switch (type) {
|
|
2754
2205
|
case "task.background.completed":
|
|
@@ -2798,7 +2249,7 @@ var larkCliHandler = {
|
|
|
2798
2249
|
},
|
|
2799
2250
|
async emitTriggerEvent(payload, store) {
|
|
2800
2251
|
const eventId = await store.insertTriggerEvent(payload, "lark-cli");
|
|
2801
|
-
|
|
2252
|
+
logger2.info(`[larkCliHandler] emitTriggerEvent: id=${eventId}`, {
|
|
2802
2253
|
chatId: payload.metadata?.chatId,
|
|
2803
2254
|
senderId: payload.senderId
|
|
2804
2255
|
});
|
|
@@ -2813,7 +2264,7 @@ var larkCliHandler = {
|
|
|
2813
2264
|
try {
|
|
2814
2265
|
const result = await runner.run(command);
|
|
2815
2266
|
if (result.code !== 0) {
|
|
2816
|
-
|
|
2267
|
+
logger2.error(`[larkCliHandler] replyMessage non-zero exit`, {
|
|
2817
2268
|
code: result.code,
|
|
2818
2269
|
stderr: result.stderr
|
|
2819
2270
|
});
|
|
@@ -2827,7 +2278,7 @@ var larkCliHandler = {
|
|
|
2827
2278
|
return { success: true, messageId: replyMessageId };
|
|
2828
2279
|
} catch (err) {
|
|
2829
2280
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2830
|
-
|
|
2281
|
+
logger2.error(`[larkCliHandler] replyMessage spawn error`, { error: errMsg });
|
|
2831
2282
|
return { success: false, error: errMsg };
|
|
2832
2283
|
}
|
|
2833
2284
|
},
|
|
@@ -2860,7 +2311,7 @@ var larkCliHandler = {
|
|
|
2860
2311
|
const result = input.subAgentResult.summary ?? input.notifyEvent.result ?? "";
|
|
2861
2312
|
const notifyType = input.notifyEvent.type;
|
|
2862
2313
|
const taskId = input.notifyEvent.taskId ?? "?";
|
|
2863
|
-
const { headline, actionable } =
|
|
2314
|
+
const { headline, actionable } = describeNotifyEvent(notifyType, taskId, desc);
|
|
2864
2315
|
const chatTypeLabel = chatType === "group" ? "群聊" : chatType === "p2p" ? "私聊" : "会话类型未知";
|
|
2865
2316
|
const canReply = Boolean(chatId);
|
|
2866
2317
|
const receiptSection = renderRuleNoticeSection(input.ruleNotice, headline);
|
|
@@ -2934,9 +2385,9 @@ async function patchCardByLarkCli(token, card, profile) {
|
|
|
2934
2385
|
const cmd = `lark-cli ${profileFlag}api POST /open-apis/interactive/v1/card/update ` + `--data '${JSON.stringify({ token, card }).replace(/'/g, "'\\''")}' --as bot`;
|
|
2935
2386
|
try {
|
|
2936
2387
|
const { stdout } = await execAsync2(cmd, { timeout: 30000 });
|
|
2937
|
-
|
|
2388
|
+
logger2.debug(`patchCardByLarkCli ok: ${stdout?.slice(0, 200)}`);
|
|
2938
2389
|
} catch (err) {
|
|
2939
|
-
|
|
2390
|
+
logger2.error(`patchCardByLarkCli failed: ${err.message}`);
|
|
2940
2391
|
}
|
|
2941
2392
|
}
|
|
2942
2393
|
var builtInHandlers = [
|
|
@@ -2950,454 +2401,4 @@ function getBuiltInHandler(type) {
|
|
|
2950
2401
|
return builtInHandlers.find((h) => h.type === type);
|
|
2951
2402
|
}
|
|
2952
2403
|
|
|
2953
|
-
|
|
2954
|
-
import { join } from "path";
|
|
2955
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2956
|
-
init_logger();
|
|
2957
|
-
init_decorator();
|
|
2958
|
-
|
|
2959
|
-
// src/env/event-source/event-source-config-registration.ts
|
|
2960
|
-
var EVENT_SOURCE_DEFAULTS = {
|
|
2961
|
-
"event-source.enabled": true,
|
|
2962
|
-
"event-source.persistenceEnabled": true
|
|
2963
|
-
};
|
|
2964
|
-
var EVENT_SOURCE_CONFIG_REGISTRATION = {
|
|
2965
|
-
name: "event-source",
|
|
2966
|
-
sources: [
|
|
2967
|
-
{ type: "env", envPrefix: "EVENT_SOURCE", priority: 20, watch: false }
|
|
2968
|
-
],
|
|
2969
|
-
keys: [
|
|
2970
|
-
{ key: "event-source.enabled", sources: ["env", "file"] },
|
|
2971
|
-
{ key: "event-source.persistenceEnabled", sources: ["env", "file"] },
|
|
2972
|
-
{ key: "event-source.configPath", sources: ["env", "file"] }
|
|
2973
|
-
]
|
|
2974
|
-
};
|
|
2975
|
-
|
|
2976
|
-
// src/env/event-source/event-source-component.ts
|
|
2977
|
-
var logger5 = createLogger("event-source");
|
|
2978
|
-
|
|
2979
|
-
class EventSourceComponent extends BaseComponent {
|
|
2980
|
-
name = "event-source";
|
|
2981
|
-
version = "1.0.0";
|
|
2982
|
-
sources = new Map;
|
|
2983
|
-
statuses = new Map;
|
|
2984
|
-
handlers = new Map;
|
|
2985
|
-
instances = new Map;
|
|
2986
|
-
eventHandlers = new Map;
|
|
2987
|
-
buffers = new Map;
|
|
2988
|
-
configPath = "";
|
|
2989
|
-
persistenceEnabled = true;
|
|
2990
|
-
configComponent;
|
|
2991
|
-
configWatcher;
|
|
2992
|
-
async onInit() {
|
|
2993
|
-
await this.executeInitHooks();
|
|
2994
|
-
await this.loadConfig();
|
|
2995
|
-
}
|
|
2996
|
-
async init(options) {
|
|
2997
|
-
if (options?.env) {
|
|
2998
|
-
this.env = options.env;
|
|
2999
|
-
}
|
|
3000
|
-
this.setStatus("initializing");
|
|
3001
|
-
if (options?.name)
|
|
3002
|
-
Object.defineProperty(this, "name", { value: options.name, writable: false });
|
|
3003
|
-
if (options?.version)
|
|
3004
|
-
Object.defineProperty(this, "version", { value: options.version, writable: false });
|
|
3005
|
-
if (options?.enabled !== undefined)
|
|
3006
|
-
this._enabled = options.enabled;
|
|
3007
|
-
const opts = options?.options;
|
|
3008
|
-
if (opts?.configComponent) {
|
|
3009
|
-
this.configComponent = opts.configComponent;
|
|
3010
|
-
await this.registerConfig(opts);
|
|
3011
|
-
if (opts.registerBuiltInHandlers !== false) {
|
|
3012
|
-
this.registerBuiltInHandlers();
|
|
3013
|
-
}
|
|
3014
|
-
} else {
|
|
3015
|
-
throw new Error("ConfigComponent is required for EventSourceComponent initialization");
|
|
3016
|
-
}
|
|
3017
|
-
await this.onInit();
|
|
3018
|
-
this.setStatus("running");
|
|
3019
|
-
}
|
|
3020
|
-
async onStart() {}
|
|
3021
|
-
async onStop() {
|
|
3022
|
-
this.configWatcher?.();
|
|
3023
|
-
this.configWatcher = undefined;
|
|
3024
|
-
const runningSources = Array.from(this.statuses.entries()).filter(([_, status]) => status === "running").map(([id]) => id);
|
|
3025
|
-
for (const id of runningSources) {
|
|
3026
|
-
await this.stopSource(id);
|
|
3027
|
-
}
|
|
3028
|
-
this.sources.clear();
|
|
3029
|
-
this.statuses.clear();
|
|
3030
|
-
this.instances.clear();
|
|
3031
|
-
this.eventHandlers.clear();
|
|
3032
|
-
this.handlers.clear();
|
|
3033
|
-
this.buffers.clear();
|
|
3034
|
-
}
|
|
3035
|
-
createDefaultEventHandler(sourceId, sourceType) {
|
|
3036
|
-
return (event) => {
|
|
3037
|
-
this.getEnv()?.pushEnvEvent(event);
|
|
3038
|
-
};
|
|
3039
|
-
}
|
|
3040
|
-
registerBuiltInHandlers() {
|
|
3041
|
-
for (const handler of builtInHandlers) {
|
|
3042
|
-
if (!this.handlers.has(handler.type)) {
|
|
3043
|
-
this.handlers.set(handler.type, handler);
|
|
3044
|
-
logger5.debug(`Registered built-in handler: ${handler.type}`);
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
async executeInitHooks() {
|
|
3049
|
-
const { EventSourceInitHooks } = await import("./roy-agent-core-99nwgj84.js");
|
|
3050
|
-
await EventSourceInitHooks.execute(this);
|
|
3051
|
-
}
|
|
3052
|
-
registerHandler(handler) {
|
|
3053
|
-
if (this.handlers.has(handler.type)) {
|
|
3054
|
-
logger5.warn(`Handler already registered for type: ${handler.type}, overwriting`);
|
|
3055
|
-
}
|
|
3056
|
-
this.handlers.set(handler.type, handler);
|
|
3057
|
-
logger5.debug(`Handler registered: ${handler.type}`);
|
|
3058
|
-
}
|
|
3059
|
-
unregisterHandler(type) {
|
|
3060
|
-
const deleted = this.handlers.delete(type);
|
|
3061
|
-
if (deleted) {
|
|
3062
|
-
logger5.debug(`Handler unregistered: ${type}`);
|
|
3063
|
-
}
|
|
3064
|
-
return deleted;
|
|
3065
|
-
}
|
|
3066
|
-
getHandler(type) {
|
|
3067
|
-
return this.handlers.get(type);
|
|
3068
|
-
}
|
|
3069
|
-
listHandlers() {
|
|
3070
|
-
return Array.from(this.handlers.values());
|
|
3071
|
-
}
|
|
3072
|
-
async registerConfig(options) {
|
|
3073
|
-
const configComponent = options.configComponent;
|
|
3074
|
-
if (!configComponent)
|
|
3075
|
-
return;
|
|
3076
|
-
const { configPath, config } = options;
|
|
3077
|
-
const envPrefix = "EVENT_SOURCE";
|
|
3078
|
-
configComponent.registerComponent(EVENT_SOURCE_CONFIG_REGISTRATION);
|
|
3079
|
-
if (configPath) {
|
|
3080
|
-
configComponent.registerSource({
|
|
3081
|
-
type: "file",
|
|
3082
|
-
relativePath: configPath,
|
|
3083
|
-
optional: true,
|
|
3084
|
-
watch: false
|
|
3085
|
-
});
|
|
3086
|
-
}
|
|
3087
|
-
configComponent.registerSource({
|
|
3088
|
-
type: "env",
|
|
3089
|
-
envPrefix,
|
|
3090
|
-
priority: 20,
|
|
3091
|
-
watch: false
|
|
3092
|
-
});
|
|
3093
|
-
await configComponent.load("event-source");
|
|
3094
|
-
for (const envKey of Object.keys(process.env)) {
|
|
3095
|
-
const configKey = envKeyToConfigKey(envKey, envPrefix, "event-source");
|
|
3096
|
-
if (!configKey)
|
|
3097
|
-
continue;
|
|
3098
|
-
const value = process.env[envKey];
|
|
3099
|
-
if (value !== undefined) {
|
|
3100
|
-
await configComponent.set(configKey, value);
|
|
3101
|
-
}
|
|
3102
|
-
}
|
|
3103
|
-
for (const [key, value] of Object.entries(EVENT_SOURCE_DEFAULTS)) {
|
|
3104
|
-
if (configComponent.get(key) === undefined) {
|
|
3105
|
-
await configComponent.set(key, value);
|
|
3106
|
-
}
|
|
3107
|
-
}
|
|
3108
|
-
if (configComponent.get("event-source.configPath") === undefined) {
|
|
3109
|
-
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
3110
|
-
const dataDir = join(home, ".roy-agent");
|
|
3111
|
-
await configComponent.set("event-source.configPath", join(dataDir, "event-sources.json"));
|
|
3112
|
-
}
|
|
3113
|
-
if (config) {
|
|
3114
|
-
const flatConfig = this.flattenConfig(config);
|
|
3115
|
-
for (const [key, value] of Object.entries(flatConfig)) {
|
|
3116
|
-
await configComponent.set(key, value);
|
|
3117
|
-
}
|
|
3118
|
-
}
|
|
3119
|
-
this.registerConfigWatcher(configComponent);
|
|
3120
|
-
}
|
|
3121
|
-
buildConfig() {
|
|
3122
|
-
const configComponent = this.configComponent;
|
|
3123
|
-
if (!configComponent) {
|
|
3124
|
-
return { enabled: true, persistenceEnabled: true, configPath: "" };
|
|
3125
|
-
}
|
|
3126
|
-
return {
|
|
3127
|
-
enabled: configComponent.get("event-source.enabled") ?? true,
|
|
3128
|
-
persistenceEnabled: configComponent.get("event-source.persistenceEnabled") ?? true,
|
|
3129
|
-
configPath: configComponent.get("event-source.configPath") ?? ""
|
|
3130
|
-
};
|
|
3131
|
-
}
|
|
3132
|
-
flattenConfig(obj, prefix = "event-source") {
|
|
3133
|
-
const result = {};
|
|
3134
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
3135
|
-
const fullKey = `${prefix}.${key}`;
|
|
3136
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
3137
|
-
Object.assign(result, this.flattenConfig(value, fullKey));
|
|
3138
|
-
} else {
|
|
3139
|
-
result[fullKey] = value;
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
return result;
|
|
3143
|
-
}
|
|
3144
|
-
registerConfigWatcher(configComponent) {
|
|
3145
|
-
if (typeof configComponent.watch !== "function")
|
|
3146
|
-
return;
|
|
3147
|
-
this.configWatcher = configComponent.watch("event-source.*", (event) => {
|
|
3148
|
-
this.onConfigChange(event);
|
|
3149
|
-
});
|
|
3150
|
-
}
|
|
3151
|
-
onConfigChange(event) {
|
|
3152
|
-
logger5.info(`EventSource config changed: ${event.key}`, {
|
|
3153
|
-
oldValue: event.oldValue,
|
|
3154
|
-
newValue: event.newValue
|
|
3155
|
-
});
|
|
3156
|
-
}
|
|
3157
|
-
register(config) {
|
|
3158
|
-
if (this.sources.has(config.id)) {
|
|
3159
|
-
throw new Error(`EventSource already exists: ${config.id}`);
|
|
3160
|
-
}
|
|
3161
|
-
const handler = this.handlers.get(config.type);
|
|
3162
|
-
if (!handler) {
|
|
3163
|
-
throw new Error(`No handler registered for event source type: ${config.type}`);
|
|
3164
|
-
}
|
|
3165
|
-
const errors = handler.validateConfig(config);
|
|
3166
|
-
if (errors.length > 0) {
|
|
3167
|
-
throw new Error(`Invalid config: ${errors.join(", ")}`);
|
|
3168
|
-
}
|
|
3169
|
-
this.sources.set(config.id, config);
|
|
3170
|
-
this.statuses.set(config.id, "created");
|
|
3171
|
-
logger5.debug(`EventSource registered: ${config.id} (${config.type})`);
|
|
3172
|
-
this.saveConfig().catch((err) => {
|
|
3173
|
-
logger5.error(`Failed to save config after registering ${config.id}:`, err);
|
|
3174
|
-
});
|
|
3175
|
-
}
|
|
3176
|
-
unregister(id) {
|
|
3177
|
-
const status = this.statuses.get(id);
|
|
3178
|
-
if (status === "running") {
|
|
3179
|
-
this.stopSource(id);
|
|
3180
|
-
}
|
|
3181
|
-
const existed = this.sources.has(id);
|
|
3182
|
-
this.sources.delete(id);
|
|
3183
|
-
this.statuses.delete(id);
|
|
3184
|
-
this.instances.delete(id);
|
|
3185
|
-
this.eventHandlers.delete(id);
|
|
3186
|
-
this.buffers.delete(id);
|
|
3187
|
-
if (existed) {
|
|
3188
|
-
logger5.debug(`EventSource unregistered: ${id}`);
|
|
3189
|
-
this.saveConfig().catch((err) => {
|
|
3190
|
-
logger5.error(`Failed to save config after unregistering ${id}:`, err);
|
|
3191
|
-
});
|
|
3192
|
-
}
|
|
3193
|
-
return existed;
|
|
3194
|
-
}
|
|
3195
|
-
async update(id, partial) {
|
|
3196
|
-
if (!partial || Object.keys(partial).length === 0) {
|
|
3197
|
-
throw new Error("update() partial must not be empty");
|
|
3198
|
-
}
|
|
3199
|
-
const existing = this.sources.get(id);
|
|
3200
|
-
if (!existing) {
|
|
3201
|
-
throw new Error(`EventSource not found: ${id}`);
|
|
3202
|
-
}
|
|
3203
|
-
if (Object.prototype.hasOwnProperty.call(partial, "type")) {
|
|
3204
|
-
if (partial.type !== existing.type) {
|
|
3205
|
-
throw new Error(`Cannot update event source type: ${existing.type} -> ${partial.type} (use unregister + register instead)`);
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
const wasRunning = this.statuses.get(id) === "running";
|
|
3209
|
-
let stopped = false;
|
|
3210
|
-
if (wasRunning) {
|
|
3211
|
-
await this.stopSource(id);
|
|
3212
|
-
stopped = true;
|
|
3213
|
-
}
|
|
3214
|
-
const updated = {
|
|
3215
|
-
...existing,
|
|
3216
|
-
...partial,
|
|
3217
|
-
id: existing.id,
|
|
3218
|
-
type: existing.type
|
|
3219
|
-
};
|
|
3220
|
-
const handler = this.handlers.get(existing.type);
|
|
3221
|
-
if (handler) {
|
|
3222
|
-
const errors = handler.validateConfig(updated);
|
|
3223
|
-
if (errors.length > 0) {
|
|
3224
|
-
throw new Error(`Invalid config after update: ${errors.join(", ")}`);
|
|
3225
|
-
}
|
|
3226
|
-
}
|
|
3227
|
-
this.sources.set(id, updated);
|
|
3228
|
-
logger5.info(`EventSource updated: ${id}`, {
|
|
3229
|
-
fields: Object.keys(partial)
|
|
3230
|
-
});
|
|
3231
|
-
try {
|
|
3232
|
-
await this.saveConfig();
|
|
3233
|
-
} catch (err) {
|
|
3234
|
-
logger5.error(`Failed to save config after updating ${id}:`, err);
|
|
3235
|
-
throw err;
|
|
3236
|
-
}
|
|
3237
|
-
return {
|
|
3238
|
-
id,
|
|
3239
|
-
updated,
|
|
3240
|
-
wasRunning,
|
|
3241
|
-
stopped,
|
|
3242
|
-
restarted: false,
|
|
3243
|
-
fields: Object.keys(partial)
|
|
3244
|
-
};
|
|
3245
|
-
}
|
|
3246
|
-
get(id) {
|
|
3247
|
-
return this.sources.get(id);
|
|
3248
|
-
}
|
|
3249
|
-
list() {
|
|
3250
|
-
return Array.from(this.sources.values());
|
|
3251
|
-
}
|
|
3252
|
-
getStatus(id) {
|
|
3253
|
-
if (id === undefined) {
|
|
3254
|
-
return this._status;
|
|
3255
|
-
}
|
|
3256
|
-
return this.statuses.get(id);
|
|
3257
|
-
}
|
|
3258
|
-
getEventSourceStatus(id) {
|
|
3259
|
-
return this.getStatus(id);
|
|
3260
|
-
}
|
|
3261
|
-
onEvent(id, handler) {
|
|
3262
|
-
this.eventHandlers.set(id, handler);
|
|
3263
|
-
const instance = this.instances.get(id);
|
|
3264
|
-
if (instance) {
|
|
3265
|
-
instance.onEvent(handler);
|
|
3266
|
-
}
|
|
3267
|
-
}
|
|
3268
|
-
offEvent(id) {
|
|
3269
|
-
this.eventHandlers.delete(id);
|
|
3270
|
-
const instance = this.instances.get(id);
|
|
3271
|
-
if (instance) {
|
|
3272
|
-
instance.offEvent();
|
|
3273
|
-
}
|
|
3274
|
-
}
|
|
3275
|
-
async startSource(id) {
|
|
3276
|
-
const config = this.sources.get(id);
|
|
3277
|
-
if (!config) {
|
|
3278
|
-
throw new Error(`EventSource not found: ${id}`);
|
|
3279
|
-
}
|
|
3280
|
-
const currentStatus = this.statuses.get(id);
|
|
3281
|
-
if (currentStatus === "running") {
|
|
3282
|
-
return;
|
|
3283
|
-
}
|
|
3284
|
-
this.statuses.set(id, "starting");
|
|
3285
|
-
try {
|
|
3286
|
-
const handler = this.handlers.get(config.type);
|
|
3287
|
-
if (!handler) {
|
|
3288
|
-
throw new Error(`No handler registered for event source type: ${config.type}`);
|
|
3289
|
-
}
|
|
3290
|
-
const instance = handler.createInstance(config);
|
|
3291
|
-
this.instances.set(id, instance);
|
|
3292
|
-
if (typeof instance.setEnvironment === "function") {
|
|
3293
|
-
instance.setEnvironment(this.getEnv());
|
|
3294
|
-
}
|
|
3295
|
-
const eventHandler = this.eventHandlers.get(id) ?? this.createDefaultEventHandler(id, config.type);
|
|
3296
|
-
instance.onEvent(eventHandler);
|
|
3297
|
-
await instance.start();
|
|
3298
|
-
this.statuses.set(id, "running");
|
|
3299
|
-
this.getEnv()?.pushEnvEvent({
|
|
3300
|
-
type: "event-source.started",
|
|
3301
|
-
payload: { sourceId: id, sourceType: config.type }
|
|
3302
|
-
});
|
|
3303
|
-
logger5.info(`EventSource started: ${id} (${config.type})`);
|
|
3304
|
-
} catch (error) {
|
|
3305
|
-
this.statuses.set(id, "error");
|
|
3306
|
-
logger5.error(`EventSource failed to start: ${id}`, error);
|
|
3307
|
-
throw error;
|
|
3308
|
-
}
|
|
3309
|
-
}
|
|
3310
|
-
async stopSource(id) {
|
|
3311
|
-
const status = this.statuses.get(id);
|
|
3312
|
-
logger5.info(`[EventSource ${id}] stopSource called, current status: ${status}`);
|
|
3313
|
-
if (status === "stopped" || status === "created") {
|
|
3314
|
-
logger5.info(`[EventSource ${id}] already stopped/created, skipping`);
|
|
3315
|
-
return;
|
|
3316
|
-
}
|
|
3317
|
-
this.statuses.set(id, "stopping");
|
|
3318
|
-
try {
|
|
3319
|
-
const instance = this.instances.get(id);
|
|
3320
|
-
if (instance) {
|
|
3321
|
-
await instance.stop();
|
|
3322
|
-
this.instances.delete(id);
|
|
3323
|
-
}
|
|
3324
|
-
const config = this.sources.get(id);
|
|
3325
|
-
this.statuses.set(id, "stopped");
|
|
3326
|
-
this.getEnv()?.pushEnvEvent({
|
|
3327
|
-
type: "event-source.stopped",
|
|
3328
|
-
payload: { sourceId: id, sourceType: config?.type }
|
|
3329
|
-
});
|
|
3330
|
-
logger5.info(`EventSource stopped: ${id}`);
|
|
3331
|
-
} catch (error) {
|
|
3332
|
-
this.statuses.set(id, "error");
|
|
3333
|
-
logger5.error(`EventSource failed to stop: ${id}`, error);
|
|
3334
|
-
throw error;
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
getConfigFilePath() {
|
|
3338
|
-
if (this.configPath) {
|
|
3339
|
-
return this.configPath;
|
|
3340
|
-
}
|
|
3341
|
-
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
3342
|
-
const dataDir = join(home, ".roy-agent");
|
|
3343
|
-
if (!existsSync(dataDir)) {
|
|
3344
|
-
mkdirSync(dataDir, { recursive: true });
|
|
3345
|
-
}
|
|
3346
|
-
return join(dataDir, "event-sources.json");
|
|
3347
|
-
}
|
|
3348
|
-
async saveConfig() {
|
|
3349
|
-
if (!this.persistenceEnabled)
|
|
3350
|
-
return;
|
|
3351
|
-
try {
|
|
3352
|
-
const configPath = this.getConfigFilePath();
|
|
3353
|
-
const config = {
|
|
3354
|
-
version: "1.0.0",
|
|
3355
|
-
sources: Array.from(this.sources.values())
|
|
3356
|
-
};
|
|
3357
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
3358
|
-
logger5.debug(`Saved ${this.sources.size} event source configurations`);
|
|
3359
|
-
} catch (error) {
|
|
3360
|
-
logger5.error("Failed to save event source configurations:", error);
|
|
3361
|
-
throw error;
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
async loadConfig() {
|
|
3365
|
-
if (!this.persistenceEnabled)
|
|
3366
|
-
return;
|
|
3367
|
-
try {
|
|
3368
|
-
const configPath = this.getConfigFilePath();
|
|
3369
|
-
if (!existsSync(configPath)) {
|
|
3370
|
-
logger5.debug(`No event source config file found at ${configPath}`);
|
|
3371
|
-
return;
|
|
3372
|
-
}
|
|
3373
|
-
const content = readFileSync(configPath, "utf-8");
|
|
3374
|
-
const config = JSON.parse(content);
|
|
3375
|
-
if (!config.sources || !Array.isArray(config.sources)) {
|
|
3376
|
-
logger5.warn("Invalid event source config format, skipping load");
|
|
3377
|
-
return;
|
|
3378
|
-
}
|
|
3379
|
-
let loadedCount = 0;
|
|
3380
|
-
for (const source of config.sources) {
|
|
3381
|
-
if (!this.sources.has(source.id)) {
|
|
3382
|
-
this.sources.set(source.id, source);
|
|
3383
|
-
this.statuses.set(source.id, "created");
|
|
3384
|
-
loadedCount++;
|
|
3385
|
-
}
|
|
3386
|
-
}
|
|
3387
|
-
logger5.info(`Loaded ${loadedCount} event source configurations`);
|
|
3388
|
-
} catch (error) {
|
|
3389
|
-
logger5.error("Failed to load event source configurations:", error);
|
|
3390
|
-
}
|
|
3391
|
-
}
|
|
3392
|
-
}
|
|
3393
|
-
__legacyDecorateClassTS([
|
|
3394
|
-
TracedAs("event-source.update", { recordParams: false, log: false })
|
|
3395
|
-
], EventSourceComponent.prototype, "update", null);
|
|
3396
|
-
__legacyDecorateClassTS([
|
|
3397
|
-
TracedAs("event_source.start", { recordParams: true, recordResult: false })
|
|
3398
|
-
], EventSourceComponent.prototype, "startSource", null);
|
|
3399
|
-
__legacyDecorateClassTS([
|
|
3400
|
-
TracedAs("event_source.stop", { recordParams: true, recordResult: false })
|
|
3401
|
-
], EventSourceComponent.prototype, "stopSource", null);
|
|
3402
|
-
|
|
3403
|
-
export { TimerInstance, timerHandler, buildDefaultBountyIMSystemPrompt, resolveBountyIMSystemPrompt, BountyIMInstance, bountyIMHandler, CLI_COMMAND_ES_TYPE, CLI_SESSION_CHAT_ID_PREFIX, buildCliReplyChannel, parseCliSessionChatId, cliCommandHandler, extractMetadata, larkCliHandler, builtInHandlers, getBuiltInHandler, EventSourceComponent };
|
|
2404
|
+
export { TimerInstance, timerHandler, buildDefaultBountyIMSystemPrompt, resolveBountyIMSystemPrompt, BountyIMInstance, bountyIMHandler, bountyCliHandler, buildLarkRecommendedAction, extractMetadata, LarkCliInstance, renderRuleNoticeSection, larkCliHandler, patchCardByLarkCli, builtInHandlers, getBuiltInHandler };
|