@ai-setting/roy-agent-core 1.7.50 → 1.7.52
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/env/event-source/index.js +6 -6
- package/dist/env/index.js +11 -11
- package/dist/env/prompt/index.js +2 -2
- package/dist/env/task/delegate/index.js +2 -2
- package/dist/env/task/index.js +3 -3
- package/dist/env/task/plugins/index.js +4 -4
- package/dist/env/workflow/index.js +3 -3
- package/dist/env/workflow/plugins/index.js +2 -2
- package/dist/env/workflow/tools/index.js +2 -2
- package/dist/env/workflow/utils/index.js +21 -1
- package/dist/index.js +12 -12
- package/dist/shared/@ai-setting/{roy-agent-core-rpwn590p.js → roy-agent-core-39xejr39.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-wxsgay02.js → roy-agent-core-brwzs8wf.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-rpnjzztt.js → roy-agent-core-ezgsem94.js} +566 -78
- package/dist/shared/@ai-setting/{roy-agent-core-cg9f4zch.js → roy-agent-core-gdsqp6j8.js} +73 -14
- package/dist/shared/@ai-setting/{roy-agent-core-zd077w51.js → roy-agent-core-j8e92gj8.js} +43 -1
- package/dist/shared/@ai-setting/roy-agent-core-na73mym1.js +367 -0
- package/dist/shared/@ai-setting/{roy-agent-core-gpf2059p.js → roy-agent-core-p2r27era.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-s7jpkfm1.js → roy-agent-core-pkpkctvf.js} +47 -16
- package/dist/shared/@ai-setting/{roy-agent-core-avyd65je.js → roy-agent-core-rcfewbzr.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-vy69rpxy.js → roy-agent-core-s214vmmd.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-f28va833.js → roy-agent-core-stnddvx9.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-q9gnnv6v.js → roy-agent-core-vvxb9522.js} +17 -4
- package/package.json +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-q6twb0df.js +0 -35
|
@@ -5,14 +5,20 @@ import {
|
|
|
5
5
|
TaskTagPlugin,
|
|
6
6
|
createLarkCliTaskNotifyHook,
|
|
7
7
|
createPerTaskGroupChatPlugin,
|
|
8
|
+
extractPerTaskChatId,
|
|
9
|
+
extractPerTaskChatName,
|
|
8
10
|
mergeChatOwnership
|
|
9
|
-
} from "./roy-agent-core-
|
|
11
|
+
} from "./roy-agent-core-pkpkctvf.js";
|
|
10
12
|
import {
|
|
11
13
|
getSharedEventStore
|
|
12
14
|
} from "./roy-agent-core-yhsssn86.js";
|
|
13
15
|
import {
|
|
14
16
|
createLarkCliWorkflowNotifyHook
|
|
15
|
-
} from "./roy-agent-core-
|
|
17
|
+
} from "./roy-agent-core-brwzs8wf.js";
|
|
18
|
+
import {
|
|
19
|
+
LarkCardPatchNotifier,
|
|
20
|
+
buildTaskCard
|
|
21
|
+
} from "./roy-agent-core-j8e92gj8.js";
|
|
16
22
|
import {
|
|
17
23
|
normalizeLarkAtMentions
|
|
18
24
|
} from "./roy-agent-core-64f22h1g.js";
|
|
@@ -605,10 +611,247 @@ var bountyIMHandler = {
|
|
|
605
611
|
};
|
|
606
612
|
|
|
607
613
|
// src/env/event-source/event-source-handlers.ts
|
|
608
|
-
import { spawn as spawn2 } from "child_process";
|
|
614
|
+
import { spawn as spawn2, exec as execSync } from "child_process";
|
|
615
|
+
import { promisify as promisify2 } from "node:util";
|
|
609
616
|
init_global_hook_manager();
|
|
610
617
|
init_context();
|
|
611
618
|
|
|
619
|
+
// src/env/task/plugins/lark-card-action-handler.ts
|
|
620
|
+
init_logger();
|
|
621
|
+
import { exec } from "node:child_process";
|
|
622
|
+
import { promisify } from "node:util";
|
|
623
|
+
var logger = createLogger("lark-card-action-handler");
|
|
624
|
+
var execAsync = promisify(exec);
|
|
625
|
+
var CTX_FIELD_CHAT_ID = "larkPerTaskChatId";
|
|
626
|
+
var CTX_FIELD_CHAT_NAME = "larkPerTaskChatName";
|
|
627
|
+
var CTX_FIELD_CHAT_CREATED_AT = "larkPerTaskChatCreatedAt";
|
|
628
|
+
var CTX_FIELD_CHAT_TASK_ID = "larkPerTaskChatTaskId";
|
|
629
|
+
var CTX_FIELD_CHAT_INVITEES = "larkPerTaskChatInvitees";
|
|
630
|
+
var cardActionInflight = new Map;
|
|
631
|
+
async function handleCardAction(event, ports) {
|
|
632
|
+
const { action, taskId: taskIdRaw } = event.actionValue;
|
|
633
|
+
const taskId = typeof taskIdRaw === "string" ? parseInt(taskIdRaw, 10) : taskIdRaw;
|
|
634
|
+
if (!taskId || Number.isNaN(taskId)) {
|
|
635
|
+
return { ok: false, action, error: `Invalid taskId: ${taskIdRaw}` };
|
|
636
|
+
}
|
|
637
|
+
if (action !== "create_group" && action !== "disband_group") {
|
|
638
|
+
return { ok: false, action, error: `Unknown action: ${action}` };
|
|
639
|
+
}
|
|
640
|
+
const inflightKey = `${event.profile ?? "-"}:${event.messageId || String(taskId)}:${action}`;
|
|
641
|
+
const inflight = cardActionInflight.get(inflightKey);
|
|
642
|
+
if (inflight) {
|
|
643
|
+
logger.warn(`handleCardAction: ${inflightKey} 已有同操作在处理中(疑似用户重复点击),本次忽略。`);
|
|
644
|
+
return { ok: false, action, error: `重复点击:${inflightKey} 正在处理中,已忽略` };
|
|
645
|
+
}
|
|
646
|
+
const pending = handleCardActionInner(event, ports, taskId);
|
|
647
|
+
cardActionInflight.set(inflightKey, pending);
|
|
648
|
+
try {
|
|
649
|
+
return await pending;
|
|
650
|
+
} finally {
|
|
651
|
+
cardActionInflight.delete(inflightKey);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async function handleCardActionInner(event, ports, taskId) {
|
|
655
|
+
const { action } = event.actionValue;
|
|
656
|
+
if (!ports.getTask) {
|
|
657
|
+
return { ok: false, action, error: "ports.getTask not injected" };
|
|
658
|
+
}
|
|
659
|
+
const task = await ports.getTask(taskId);
|
|
660
|
+
if (!task) {
|
|
661
|
+
return { ok: false, action, error: `Task #${taskId} not found` };
|
|
662
|
+
}
|
|
663
|
+
const existingChatId = extractPerTaskChatId(task.context);
|
|
664
|
+
if (action === "create_group") {
|
|
665
|
+
return await handleCreateGroup(event, ports, task, existingChatId);
|
|
666
|
+
} else {
|
|
667
|
+
return await handleDisbandGroup(event, ports, task, existingChatId);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function buildCardAfterGroupChange(event, ports, task, perTaskChatId, perTaskChatName) {
|
|
671
|
+
const meta = buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName);
|
|
672
|
+
const preserved = ports.notifier?.updateMetaAndBuildCard?.(event.messageId, meta, { clearPerTaskChat: !perTaskChatId });
|
|
673
|
+
if (preserved) {
|
|
674
|
+
logger.debug(`buildCardAfterGroupChange: 复用 notifier 累积状态(里程碑历史保留)messageId=${event.messageId}`);
|
|
675
|
+
return preserved;
|
|
676
|
+
}
|
|
677
|
+
logger.warn(`buildCardAfterGroupChange: notifier 无 messageId=${event.messageId} 的状态,` + `fallback 重建卡片(该卡片的历史里程碑可能丢失)`);
|
|
678
|
+
return buildTaskCard({
|
|
679
|
+
status: "running",
|
|
680
|
+
meta,
|
|
681
|
+
milestones: [
|
|
682
|
+
{ title: "Task 创建", timestamp: new Date().toISOString(), description: task.current_status }
|
|
683
|
+
]
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
async function handleCreateGroup(event, ports, task, existingChatId) {
|
|
687
|
+
if (existingChatId) {
|
|
688
|
+
const existingName = extractPerTaskChatName(task.context);
|
|
689
|
+
logger.info(`handleCreateGroup: Task #${task.id} 已有专属群 ${existingChatId}(幂等命中,不重复建群)`);
|
|
690
|
+
return {
|
|
691
|
+
ok: true,
|
|
692
|
+
action: "create_group",
|
|
693
|
+
newChatId: existingChatId,
|
|
694
|
+
cardUpdate: buildCardAfterGroupChange(event, ports, task, existingChatId, existingName)
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
if (!ports.createGroupChat) {
|
|
698
|
+
return { ok: false, action: "create_group", error: "ports.createGroupChat not injected" };
|
|
699
|
+
}
|
|
700
|
+
try {
|
|
701
|
+
const renderedName = truncateText(`Task #${task.id} - ${task.title ?? "(无标题)"}`, 60);
|
|
702
|
+
const description = truncateText(`roy-agent task #${task.id} - ${task.project_path ?? ""}`, 100);
|
|
703
|
+
const operatorOpenId = extractOperatorOpenId(event.operatorId);
|
|
704
|
+
const initialUserIds = operatorOpenId ? [operatorOpenId] : undefined;
|
|
705
|
+
const chatId = await ports.createGroupChat({
|
|
706
|
+
name: renderedName,
|
|
707
|
+
description,
|
|
708
|
+
profile: event.profile,
|
|
709
|
+
initialUserIds
|
|
710
|
+
});
|
|
711
|
+
let invitedOpenIds = [];
|
|
712
|
+
if (operatorOpenId) {
|
|
713
|
+
if (initialUserIds) {
|
|
714
|
+
invitedOpenIds = [operatorOpenId];
|
|
715
|
+
logger.debug(`handleCreateGroup: ${operatorOpenId} 已在建群时邀请(initialUserIds 路径)`);
|
|
716
|
+
} else if (ports.inviteUser) {
|
|
717
|
+
try {
|
|
718
|
+
await ports.inviteUser(chatId, operatorOpenId, event.profile);
|
|
719
|
+
invitedOpenIds = [operatorOpenId];
|
|
720
|
+
logger.debug(`handleCreateGroup: 走 inviteUser 端口邀请 ${operatorOpenId} 进 ${chatId}`);
|
|
721
|
+
} catch (err) {
|
|
722
|
+
logger.warn(`handleCreateGroup: inviteUser(${chatId}, ${operatorOpenId}) 失败(不阻断主流程): ${err.message}`);
|
|
723
|
+
}
|
|
724
|
+
} else {
|
|
725
|
+
logger.warn(`handleCreateGroup: createGroupChat 不支持 initialUserIds 且 inviteUser 未注入,` + `${operatorOpenId} 未被拉进群 ${chatId}。` + `需升级 caller(bridge/real impl)使用 ports.inviteUser 或实现 createGroupChat.initialUserIds 参数。`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const newContext = patchContext(task.context ?? "{}", {
|
|
729
|
+
[CTX_FIELD_CHAT_ID]: chatId,
|
|
730
|
+
[CTX_FIELD_CHAT_NAME]: renderedName,
|
|
731
|
+
[CTX_FIELD_CHAT_CREATED_AT]: new Date().toISOString(),
|
|
732
|
+
[CTX_FIELD_CHAT_TASK_ID]: task.id,
|
|
733
|
+
[CTX_FIELD_CHAT_INVITEES]: invitedOpenIds.length > 0 ? invitedOpenIds : undefined
|
|
734
|
+
});
|
|
735
|
+
if (ports.updateTaskContext) {
|
|
736
|
+
await ports.updateTaskContext(task.id, newContext);
|
|
737
|
+
}
|
|
738
|
+
const cardUpdate = buildCardAfterGroupChange(event, ports, task, chatId, renderedName);
|
|
739
|
+
return { ok: true, action: "create_group", newChatId: chatId, cardUpdate };
|
|
740
|
+
} catch (err) {
|
|
741
|
+
logger.error(`handleCardAction.create_group failed: ${err.message}`);
|
|
742
|
+
return { ok: false, action: "create_group", error: err.message };
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function extractOperatorOpenId(raw) {
|
|
746
|
+
if (!raw)
|
|
747
|
+
return;
|
|
748
|
+
if (typeof raw === "string")
|
|
749
|
+
return raw;
|
|
750
|
+
if (typeof raw === "object" && typeof raw.open_id === "string")
|
|
751
|
+
return raw.open_id;
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
async function handleDisbandGroup(event, ports, task, existingChatId) {
|
|
755
|
+
if (!existingChatId) {
|
|
756
|
+
return {
|
|
757
|
+
ok: false,
|
|
758
|
+
action: "disband_group",
|
|
759
|
+
error: `Task #${task.id} 无专属群(context.larkPerTaskChatId 空),无需解散`
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
if (!ports.deleteGroupChat) {
|
|
763
|
+
return { ok: false, action: "disband_group", error: "ports.deleteGroupChat not injected" };
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
await ports.deleteGroupChat(existingChatId, event.profile);
|
|
767
|
+
const newContext = patchContext(task.context ?? "{}", {
|
|
768
|
+
[CTX_FIELD_CHAT_ID]: undefined,
|
|
769
|
+
[CTX_FIELD_CHAT_NAME]: undefined,
|
|
770
|
+
[CTX_FIELD_CHAT_CREATED_AT]: undefined,
|
|
771
|
+
[CTX_FIELD_CHAT_TASK_ID]: undefined
|
|
772
|
+
});
|
|
773
|
+
if (ports.updateTaskContext) {
|
|
774
|
+
await ports.updateTaskContext(task.id, newContext);
|
|
775
|
+
}
|
|
776
|
+
const cardUpdate = buildCardAfterGroupChange(event, ports, task, undefined, undefined);
|
|
777
|
+
return { ok: true, action: "disband_group", cardUpdate };
|
|
778
|
+
} catch (err) {
|
|
779
|
+
logger.error(`handleCardAction.disband_group failed: ${err.message}`);
|
|
780
|
+
return { ok: false, action: "disband_group", error: err.message };
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function patchContext(contextStr, changes) {
|
|
784
|
+
let obj = {};
|
|
785
|
+
try {
|
|
786
|
+
const parsed = JSON.parse(contextStr);
|
|
787
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
788
|
+
obj = parsed;
|
|
789
|
+
}
|
|
790
|
+
} catch {
|
|
791
|
+
obj = {};
|
|
792
|
+
}
|
|
793
|
+
for (const [k, v] of Object.entries(changes)) {
|
|
794
|
+
if (v === undefined) {
|
|
795
|
+
delete obj[k];
|
|
796
|
+
} else {
|
|
797
|
+
obj[k] = v;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return JSON.stringify(obj);
|
|
801
|
+
}
|
|
802
|
+
function buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName) {
|
|
803
|
+
return {
|
|
804
|
+
id: task.id,
|
|
805
|
+
title: task.title,
|
|
806
|
+
description: task.description,
|
|
807
|
+
goals: task.goals,
|
|
808
|
+
parentTaskId: typeof task.parent_task_id === "number" && task.parent_task_id > 0 ? task.parent_task_id : undefined,
|
|
809
|
+
priority: task.priority,
|
|
810
|
+
type: task.type,
|
|
811
|
+
projectPath: task.project_path,
|
|
812
|
+
context: task.context,
|
|
813
|
+
currentStatus: task.current_status,
|
|
814
|
+
perTaskChatId,
|
|
815
|
+
perTaskChatName
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
async function deleteGroupChatViaLarkCli(chatId, profile) {
|
|
819
|
+
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
820
|
+
try {
|
|
821
|
+
const { stdout: authStdout } = await execAsync(`lark-cli ${profileFlag}auth status --json`, { timeout: 15000 });
|
|
822
|
+
const appId = JSON.parse(authStdout).appId;
|
|
823
|
+
if (!appId) {
|
|
824
|
+
throw new Error(`lark-cli auth status 未返回 appId: ${authStdout.slice(0, 200)}`);
|
|
825
|
+
}
|
|
826
|
+
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`;
|
|
827
|
+
const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
|
|
828
|
+
logger.debug(`deleteGroupChatViaLarkCli(${chatId}) bot 退群 ok: ` + `stdout=${stdout?.slice(0, 300)} stderr=${stderr?.slice(0, 300)}`);
|
|
829
|
+
} catch (err) {
|
|
830
|
+
logger.warn(`deleteGroupChatViaLarkCli(${chatId}) bot 退群失败(task.context 仍会清掉,` + `飞书侧群会保留):${err.message}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function shellQuote(s) {
|
|
834
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
835
|
+
}
|
|
836
|
+
function truncateText(text, maxLen) {
|
|
837
|
+
if (!text)
|
|
838
|
+
return "";
|
|
839
|
+
if (text.length <= maxLen)
|
|
840
|
+
return text;
|
|
841
|
+
return text.substring(0, Math.max(0, maxLen - 3)) + "...";
|
|
842
|
+
}
|
|
843
|
+
async function createGroupChatViaLarkCli(opts) {
|
|
844
|
+
const profileFlag = opts.profile ? `--profile ${opts.profile} ` : "";
|
|
845
|
+
const usersFlag = opts.initialUserIds && opts.initialUserIds.length > 0 ? `--users ${shellQuote(opts.initialUserIds.join(","))} ` : "";
|
|
846
|
+
const cmd = `lark-cli ${profileFlag}im +chat-create --name ${shellQuote(truncateText(opts.name, 60))} ` + usersFlag + `--description ${shellQuote(truncateText(opts.description, 100))} --as bot`;
|
|
847
|
+
const { stdout } = await execAsync(cmd, { timeout: 60000 });
|
|
848
|
+
const m = stdout.match(/"chat_id"\s*:\s*"(oc_[a-f0-9]+)"/);
|
|
849
|
+
if (!m) {
|
|
850
|
+
throw new Error(`lark-cli +chat-create 返回无法解析: ${stdout.slice(0, 300)}`);
|
|
851
|
+
}
|
|
852
|
+
return m[1];
|
|
853
|
+
}
|
|
854
|
+
|
|
612
855
|
// src/env/event-source/bounty-cli-handler.ts
|
|
613
856
|
import { spawn } from "child_process";
|
|
614
857
|
function readBountyCliConfig(config) {
|
|
@@ -843,7 +1086,14 @@ var bountyCliHandler = {
|
|
|
843
1086
|
|
|
844
1087
|
// src/env/event-source/event-source-handlers.ts
|
|
845
1088
|
init_logger();
|
|
846
|
-
var
|
|
1089
|
+
var execAsync2 = promisify2(execSync);
|
|
1090
|
+
var logger2 = createLogger("lark-cli:event-source");
|
|
1091
|
+
var CHILD_READY_TIMEOUT_MS = 5000;
|
|
1092
|
+
var GRACEFUL_STOP_TIMEOUT_MS = 5000;
|
|
1093
|
+
var CARD_ACTION_EVENT_TYPE = "card.action.trigger";
|
|
1094
|
+
function isChildAlive(child) {
|
|
1095
|
+
return child.exitCode === null && child.signalCode === null;
|
|
1096
|
+
}
|
|
847
1097
|
function isPluginDebugEnabled() {
|
|
848
1098
|
return process.env.ROY_TASK_NOTIFY_DEBUG === "1" || process.env.ROY_TRACE_DEBUG === "1";
|
|
849
1099
|
}
|
|
@@ -1065,12 +1315,23 @@ function extractMetadata(rawEvent, eventType) {
|
|
|
1065
1315
|
class LarkCliInstance {
|
|
1066
1316
|
config;
|
|
1067
1317
|
status = "created";
|
|
1068
|
-
|
|
1318
|
+
children = [];
|
|
1069
1319
|
buffer = "";
|
|
1320
|
+
starting = false;
|
|
1070
1321
|
eventHandler;
|
|
1071
1322
|
cardRoutingPorts;
|
|
1072
1323
|
chatNameCache = new Map;
|
|
1073
1324
|
pluginInstances = new Map;
|
|
1325
|
+
cardActionPorts;
|
|
1326
|
+
setCardActionPorts(ports) {
|
|
1327
|
+
this.cardActionPorts = ports;
|
|
1328
|
+
if (typeof logger2 !== "undefined" && logger2?.debug) {
|
|
1329
|
+
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}`);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
hasCardActionPorts() {
|
|
1333
|
+
return this.cardActionPorts !== undefined;
|
|
1334
|
+
}
|
|
1074
1335
|
static SHARED_SEEN = new Map;
|
|
1075
1336
|
static SHARED_MSG_TTL_MS = 10 * 60 * 1000;
|
|
1076
1337
|
static SHARED_MSG_MAX = 1000;
|
|
@@ -1091,74 +1352,195 @@ class LarkCliInstance {
|
|
|
1091
1352
|
async start() {
|
|
1092
1353
|
if (this.status === "running")
|
|
1093
1354
|
return;
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1355
|
+
if (this.starting) {
|
|
1356
|
+
logger2.warn(`[LarkCliInstance] start() 已在执行中,忽略重复调用`);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
this.starting = true;
|
|
1360
|
+
try {
|
|
1361
|
+
await this.loadPlugins();
|
|
1362
|
+
this.wireCardRoutingGroupProvider();
|
|
1363
|
+
this.wireCardActionPorts();
|
|
1364
|
+
const commands = this.buildConsumeCommands();
|
|
1365
|
+
this.warnOnCardActionConfigTraps(commands);
|
|
1366
|
+
this.warnOnConsumeConflict(commands);
|
|
1367
|
+
this.status = "starting";
|
|
1368
|
+
const readyTrackers = [];
|
|
1369
|
+
for (const command of commands) {
|
|
1370
|
+
const child = this.spawnConsumeChild(command);
|
|
1371
|
+
this.children.push(child);
|
|
1372
|
+
readyTrackers.push(this.wireChildStreams(child, command));
|
|
1373
|
+
}
|
|
1374
|
+
await Promise.all(readyTrackers);
|
|
1375
|
+
if (this.status !== "error") {
|
|
1376
|
+
this.status = "running";
|
|
1377
|
+
}
|
|
1378
|
+
} finally {
|
|
1379
|
+
this.starting = false;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
warnOnCardActionConfigTraps(commands) {
|
|
1383
|
+
const consumesCardAction = commands.some((c) => c.includes(CARD_ACTION_EVENT_TYPE));
|
|
1384
|
+
if (!consumesCardAction)
|
|
1385
|
+
return;
|
|
1386
|
+
if (this.config.eventTypes?.length && !matchEventType(CARD_ACTION_EVENT_TYPE, this.config.eventTypes)) {
|
|
1387
|
+
logger2.warn(`[LarkCliInstance] eventKeys 含 ${CARD_ACTION_EVENT_TYPE},但 eventTypes=` + `${JSON.stringify(this.config.eventTypes)} 不含它 → 卡片点击会被**静默丢弃**。` + `请把 ${CARD_ACTION_EVENT_TYPE} 加进 eventTypes,或清空 eventTypes。`);
|
|
1102
1388
|
}
|
|
1103
|
-
|
|
1389
|
+
logger2.info(`[LarkCliInstance] 已监听 ${CARD_ACTION_EVENT_TYPE}。` + `⚠️ 若点击卡片无反应,先确认飞书开发者后台「事件与回调 → 回调配置」已开启该事件。`);
|
|
1390
|
+
}
|
|
1391
|
+
spawnConsumeChild(command) {
|
|
1392
|
+
return spawn2("exec " + command, [], {
|
|
1104
1393
|
shell: true,
|
|
1105
1394
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1106
1395
|
windowsHide: true
|
|
1107
1396
|
});
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
this.
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1397
|
+
}
|
|
1398
|
+
resolveConsumeEventKeys() {
|
|
1399
|
+
const explicit = this.config.eventKeys;
|
|
1400
|
+
if (Array.isArray(explicit) && explicit.length > 0) {
|
|
1401
|
+
return explicit.filter((k) => typeof k === "string").map((k) => k.trim()).filter((k) => k.length > 0);
|
|
1402
|
+
}
|
|
1403
|
+
return [];
|
|
1404
|
+
}
|
|
1405
|
+
buildConsumeCommands() {
|
|
1406
|
+
const profile = this.config.profile;
|
|
1407
|
+
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
1408
|
+
const keys = this.resolveConsumeEventKeys();
|
|
1409
|
+
if (keys.length > 0) {
|
|
1410
|
+
return keys.map((k) => `lark-cli ${profileFlag}event consume ${k} --as bot`);
|
|
1411
|
+
}
|
|
1412
|
+
if (this.config.command) {
|
|
1413
|
+
return [
|
|
1414
|
+
this.config.command.replace(/^(lark-cli)(\s+|$)/, (_, head, tail) => `${head} ${profileFlag.trim()}${tail}`)
|
|
1415
|
+
];
|
|
1416
|
+
}
|
|
1417
|
+
return [`lark-cli ${profileFlag}event consume im.message.receive_v1 --as bot`];
|
|
1418
|
+
}
|
|
1419
|
+
async warnOnConsumeConflict(commands) {
|
|
1420
|
+
const keys = commands.map((c) => c.match(/event consume\s+(\S+)/)?.[1]).filter((k) => !!k);
|
|
1421
|
+
if (keys.length === 0)
|
|
1422
|
+
return;
|
|
1423
|
+
const busy = await this.probeBusyEventKeys();
|
|
1424
|
+
if (busy.size === 0)
|
|
1425
|
+
return;
|
|
1426
|
+
const conflicts = keys.filter((k) => busy.has(k));
|
|
1427
|
+
if (conflicts.length > 0) {
|
|
1428
|
+
const profile = this.config.profile;
|
|
1429
|
+
logger2.warn(`[LarkCliInstance] eventKey 已被其它 consumer 占用(lark-cli 一个 key 只允许一个 consumer):` + `${conflicts.join(", ")}。本 event-source 启动后可能 exit 2。` + `用 \`lark-cli${profile ? ` --profile ${profile}` : ""} event status\` 查看,必要时 \`event stop\`。`);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
async probeBusyEventKeys() {
|
|
1433
|
+
const profile = this.config.profile;
|
|
1434
|
+
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
1435
|
+
try {
|
|
1436
|
+
const { stdout } = await execAsync2(`lark-cli ${profileFlag}event status --json`, {
|
|
1437
|
+
timeout: 5000
|
|
1438
|
+
});
|
|
1439
|
+
const parsed = JSON.parse(stdout);
|
|
1440
|
+
const busy = new Set;
|
|
1441
|
+
for (const app of parsed.apps ?? []) {
|
|
1442
|
+
for (const c of app.consumers ?? []) {
|
|
1443
|
+
if (c.event_key)
|
|
1444
|
+
busy.add(c.event_key);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return busy;
|
|
1448
|
+
} catch {
|
|
1449
|
+
return new Set;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
wireChildStreams(child, command) {
|
|
1453
|
+
const stdoutBuffer = { value: "" };
|
|
1454
|
+
const stderrBuffer = { value: "" };
|
|
1455
|
+
let settled = false;
|
|
1456
|
+
let resolveReady;
|
|
1457
|
+
const ready = new Promise((res) => {
|
|
1458
|
+
resolveReady = res;
|
|
1115
1459
|
});
|
|
1116
|
-
|
|
1460
|
+
const settle = () => {
|
|
1461
|
+
if (settled)
|
|
1462
|
+
return;
|
|
1463
|
+
settled = true;
|
|
1464
|
+
resolveReady();
|
|
1465
|
+
};
|
|
1466
|
+
child.stdout?.on("data", (data) => {
|
|
1467
|
+
this.processStreamWith(data.toString(), stdoutBuffer);
|
|
1468
|
+
});
|
|
1469
|
+
child.stderr?.on("data", (data) => {
|
|
1117
1470
|
const output = data.toString();
|
|
1118
|
-
if (
|
|
1119
|
-
|
|
1120
|
-
this.processStream(stderrBuffer);
|
|
1121
|
-
stderrBuffer = "";
|
|
1122
|
-
}
|
|
1471
|
+
if (output.includes("[event] ready"))
|
|
1472
|
+
settle();
|
|
1123
1473
|
if (output.includes('"ok": false')) {
|
|
1124
1474
|
this.status = "error";
|
|
1125
1475
|
}
|
|
1476
|
+
this.processStreamWith(output, stderrBuffer, { jsonLinesOnly: true });
|
|
1126
1477
|
});
|
|
1127
|
-
|
|
1478
|
+
const handleExit = (code, source) => {
|
|
1128
1479
|
if (code !== 0 && code !== null) {
|
|
1480
|
+
logger2.warn(`[LarkCliInstance] consume 子进程异常退出 code=${code} (${source}) cmd="${command}"`);
|
|
1129
1481
|
this.status = "error";
|
|
1130
1482
|
} else {
|
|
1131
|
-
|
|
1483
|
+
logger2.info(`[LarkCliInstance] consume 子进程已退出 code=${code} (${source}) cmd="${command}"`);
|
|
1132
1484
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1485
|
+
settle();
|
|
1486
|
+
queueMicrotask(() => {
|
|
1487
|
+
if (this.children.length > 0 && this.children.every((c) => !isChildAlive(c))) {
|
|
1488
|
+
if (this.status !== "error") {
|
|
1489
|
+
logger2.warn(`[LarkCliInstance] 所有 consume 子进程都已退出,状态从 running 改为 stopped`);
|
|
1490
|
+
this.status = "stopped";
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
};
|
|
1495
|
+
child.on("exit", (code) => handleExit(code, "exit"));
|
|
1496
|
+
child.on("close", (code) => handleExit(code, "close"));
|
|
1497
|
+
child.on("error", (error) => {
|
|
1498
|
+
logger2.error(`[LarkCliInstance] consume 子进程 error: ${error.message}`);
|
|
1136
1499
|
this.status = "error";
|
|
1500
|
+
settle();
|
|
1137
1501
|
});
|
|
1138
|
-
|
|
1139
|
-
|
|
1502
|
+
const timer = setTimeout(() => {
|
|
1503
|
+
logger2.warn(`[LarkCliInstance] consume 子进程 ${CHILD_READY_TIMEOUT_MS}ms 内未见 ready marker: cmd="${command}"`);
|
|
1504
|
+
settle();
|
|
1505
|
+
}, CHILD_READY_TIMEOUT_MS);
|
|
1506
|
+
ready.then(() => clearTimeout(timer));
|
|
1507
|
+
return ready;
|
|
1140
1508
|
}
|
|
1141
1509
|
async stop() {
|
|
1142
1510
|
if (this.status === "stopped" || this.status === "created")
|
|
1143
1511
|
return;
|
|
1144
1512
|
this.unloadPlugins();
|
|
1145
|
-
|
|
1513
|
+
const children = this.children;
|
|
1514
|
+
this.children = [];
|
|
1515
|
+
if (children.length === 0) {
|
|
1516
|
+
this.buffer = "";
|
|
1517
|
+
this.status = "stopped";
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
for (const child of children) {
|
|
1521
|
+
if (!child || !child.pid)
|
|
1522
|
+
continue;
|
|
1146
1523
|
try {
|
|
1147
|
-
|
|
1148
|
-
await new Promise((resolve) => {
|
|
1149
|
-
setTimeout(resolve, 5000);
|
|
1150
|
-
});
|
|
1151
|
-
if (!this.child?.killed) {
|
|
1152
|
-
this.child.kill("SIGKILL");
|
|
1153
|
-
}
|
|
1524
|
+
child.kill("SIGTERM");
|
|
1154
1525
|
} catch (error) {
|
|
1155
|
-
|
|
1526
|
+
logger2.warn(`[LarkCliInstance] Error sending SIGTERM to ${child.pid}: ${error}`);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
const allExited = Promise.all(children.map((child) => new Promise((resolve) => {
|
|
1530
|
+
if (isChildAlive(child) === false)
|
|
1531
|
+
return resolve();
|
|
1532
|
+
child.once("exit", () => resolve());
|
|
1533
|
+
child.once("error", () => resolve());
|
|
1534
|
+
})));
|
|
1535
|
+
const timeout = new Promise((resolve) => setTimeout(resolve, GRACEFUL_STOP_TIMEOUT_MS));
|
|
1536
|
+
await Promise.race([allExited, timeout]);
|
|
1537
|
+
for (const child of children) {
|
|
1538
|
+
if (isChildAlive(child)) {
|
|
1156
1539
|
try {
|
|
1157
|
-
|
|
1540
|
+
child.kill("SIGKILL");
|
|
1158
1541
|
} catch {}
|
|
1159
1542
|
}
|
|
1160
1543
|
}
|
|
1161
|
-
this.child = undefined;
|
|
1162
1544
|
this.buffer = "";
|
|
1163
1545
|
this.status = "stopped";
|
|
1164
1546
|
}
|
|
@@ -1193,7 +1575,44 @@ class LarkCliInstance {
|
|
|
1193
1575
|
console.error(`[LarkCliInstance] Failed to import plugin "${pluginName}". ` + `plugin name=${pluginName} 不在 BUILT_IN_PLUGIN_FACTORIES 注册表里`);
|
|
1194
1576
|
}
|
|
1195
1577
|
}
|
|
1196
|
-
|
|
1578
|
+
}
|
|
1579
|
+
wireCardActionPorts() {
|
|
1580
|
+
if (this.cardActionPorts) {
|
|
1581
|
+
logger2.debug("[LarkCliInstance] cardActionPorts 已由外部注入,跳过默认 cross-wire");
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
const taskComponent = this.environment?.getComponent?.("task");
|
|
1585
|
+
if (!taskComponent?.getTask || !taskComponent?.updateTask) {
|
|
1586
|
+
logger2.debug(`[LarkCliInstance] card.action.trigger 端口未注入:taskComponent 不可用` + `(getTask=${typeof taskComponent?.getTask}, updateTask=${typeof taskComponent?.updateTask})。` + `卡片按钮点击会被跳过。`);
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
const sharedNotifier = this.pluginInstances.get("LarkCliTaskNotifyPlugin")?.cardNotifier ?? this.pluginInstances.get("PerTaskGroupChatPlugin")?.cardNotifier;
|
|
1590
|
+
this.setCardActionPorts({
|
|
1591
|
+
getTask: async (id) => {
|
|
1592
|
+
const t = await taskComponent.getTask(id);
|
|
1593
|
+
if (!t)
|
|
1594
|
+
return;
|
|
1595
|
+
return {
|
|
1596
|
+
id: t.id,
|
|
1597
|
+
title: t.title,
|
|
1598
|
+
description: t.description,
|
|
1599
|
+
goals: t.goals_and_expected_deliverables ?? t.goals,
|
|
1600
|
+
priority: t.priority,
|
|
1601
|
+
type: t.type,
|
|
1602
|
+
project_path: t.project_path,
|
|
1603
|
+
parent_task_id: t.parent_task_id ?? null,
|
|
1604
|
+
current_status: t.current_status,
|
|
1605
|
+
context: t.context
|
|
1606
|
+
};
|
|
1607
|
+
},
|
|
1608
|
+
updateTaskContext: async (taskId, context) => {
|
|
1609
|
+
await taskComponent.updateTask(taskId, { context });
|
|
1610
|
+
},
|
|
1611
|
+
createGroupChat: (opts) => createGroupChatViaLarkCli(opts),
|
|
1612
|
+
deleteGroupChat: (chatId, profile) => deleteGroupChatViaLarkCli(chatId, profile),
|
|
1613
|
+
notifier: sharedNotifier ?? new LarkCardPatchNotifier
|
|
1614
|
+
});
|
|
1615
|
+
logger2.info(`[LarkCliInstance] card.action.trigger 端口已注入` + `(notifier ${sharedNotifier ? "复用插件共享实例" : "新建实例"})`);
|
|
1197
1616
|
}
|
|
1198
1617
|
wireCardRoutingGroupProvider() {
|
|
1199
1618
|
const notifyHook = this.pluginInstances.get("LarkCliTaskNotifyPlugin");
|
|
@@ -1455,7 +1874,7 @@ class LarkCliInstance {
|
|
|
1455
1874
|
}
|
|
1456
1875
|
return null;
|
|
1457
1876
|
} catch (err) {
|
|
1458
|
-
|
|
1877
|
+
logger2.warn(`[LarkCliInstance] resolveChatTaskBinding failed for chatId=${chatId}: ${err}. ` + `群消息将不会注入 parentTaskId。`);
|
|
1459
1878
|
return null;
|
|
1460
1879
|
}
|
|
1461
1880
|
}
|
|
@@ -1472,6 +1891,22 @@ class LarkCliInstance {
|
|
|
1472
1891
|
const lines = this.buffer.split(`
|
|
1473
1892
|
`);
|
|
1474
1893
|
this.buffer = lines.pop() || "";
|
|
1894
|
+
this.dispatchLines(lines);
|
|
1895
|
+
}
|
|
1896
|
+
processStreamWith(data, buffer, opts = {}) {
|
|
1897
|
+
buffer.value += data;
|
|
1898
|
+
if (buffer.value.length > 2 * 1024 * 1024) {
|
|
1899
|
+
logger2.warn(`[LarkCliInstance] stream 行缓冲超过 2MB 仍无换行,丢弃(疑似非 NDJSON 输出)`);
|
|
1900
|
+
buffer.value = "";
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
const lines = buffer.value.split(`
|
|
1904
|
+
`);
|
|
1905
|
+
buffer.value = lines.pop() || "";
|
|
1906
|
+
const selected = opts.jsonLinesOnly ? lines.filter((l) => l.trimStart().startsWith("{")) : lines;
|
|
1907
|
+
this.dispatchLines(selected);
|
|
1908
|
+
}
|
|
1909
|
+
dispatchLines(lines) {
|
|
1475
1910
|
for (const line of lines) {
|
|
1476
1911
|
if (line.trim()) {
|
|
1477
1912
|
this.handleEvent(line);
|
|
@@ -1491,12 +1926,12 @@ class LarkCliInstance {
|
|
|
1491
1926
|
const now = Date.now();
|
|
1492
1927
|
const lastShared = LarkCliInstance.SHARED_SEEN.get(dedupKey);
|
|
1493
1928
|
if (lastShared !== undefined && now - lastShared < LarkCliInstance.SHARED_MSG_TTL_MS) {
|
|
1494
|
-
|
|
1929
|
+
logger2.warn(`[LarkCliInstance] duplicate lark-cli event suppressed across instances (event_id=${dedupKey}, ` + `lastSeen=${now - lastShared}ms ago). ` + `Task #4137: SHARED_SEEN 跨实例去重。`);
|
|
1495
1930
|
return;
|
|
1496
1931
|
}
|
|
1497
1932
|
const last = this.recentEventIds.get(dedupKey);
|
|
1498
1933
|
if (last !== undefined && now - last < this.DEDUP_WINDOW_MS) {
|
|
1499
|
-
|
|
1934
|
+
logger2.warn(`[LarkCliInstance] duplicate lark-cli event suppressed (event_id=${dedupKey}, ` + `lastSeen=${now - last}ms ago). ` + `Task #3977/3978: 防止同一飞书事件触发两次任务创建。`);
|
|
1500
1935
|
return;
|
|
1501
1936
|
}
|
|
1502
1937
|
LarkCliInstance.SHARED_SEEN.set(dedupKey, now);
|
|
@@ -1549,6 +1984,7 @@ class LarkCliInstance {
|
|
|
1549
1984
|
} else {
|
|
1550
1985
|
eventType = "unknown";
|
|
1551
1986
|
}
|
|
1987
|
+
const isCardAction = eventType === CARD_ACTION_EVENT_TYPE;
|
|
1552
1988
|
if (this.config.eventTypes?.length && !matchEventType(eventType, this.config.eventTypes)) {
|
|
1553
1989
|
return;
|
|
1554
1990
|
}
|
|
@@ -1558,10 +1994,10 @@ class LarkCliInstance {
|
|
|
1558
1994
|
eventSourceId: this.config.id
|
|
1559
1995
|
};
|
|
1560
1996
|
const { metadata, replyChannel, recommendedAction } = extractMetadata(enrichedEvent, eventType);
|
|
1561
|
-
const parentTaskId = await this.resolveChatTaskBinding(metadata?.chatId);
|
|
1997
|
+
const parentTaskId = isCardAction ? null : await this.resolveChatTaskBinding(metadata?.chatId);
|
|
1562
1998
|
const userId = this.extractUserId(rawEvent);
|
|
1563
1999
|
let triggerEventId;
|
|
1564
|
-
const sharedStore = getSharedEventStore();
|
|
2000
|
+
const sharedStore = isCardAction ? undefined : getSharedEventStore();
|
|
1565
2001
|
if (sharedStore && larkCliHandler.emitTriggerEvent) {
|
|
1566
2002
|
try {
|
|
1567
2003
|
triggerEventId = await larkCliHandler.emitTriggerEvent({
|
|
@@ -1650,6 +2086,10 @@ class LarkCliInstance {
|
|
|
1650
2086
|
parentTaskId: normalizeParentTaskId(parentTaskId)
|
|
1651
2087
|
});
|
|
1652
2088
|
runWithEnvContext(envContext, () => {
|
|
2089
|
+
if (eventType === CARD_ACTION_EVENT_TYPE) {
|
|
2090
|
+
this.handleCardActionEvent(rawEvent, envReplyChannel?.profile);
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
1653
2093
|
this.eventHandler?.(evt);
|
|
1654
2094
|
});
|
|
1655
2095
|
} catch (error) {
|
|
@@ -1659,6 +2099,44 @@ class LarkCliInstance {
|
|
|
1659
2099
|
rootSpan.end();
|
|
1660
2100
|
}
|
|
1661
2101
|
}
|
|
2102
|
+
async handleCardActionEvent(rawEvent, profile) {
|
|
2103
|
+
try {
|
|
2104
|
+
const event = rawEvent;
|
|
2105
|
+
const actionValueStr = event.action_value || "{}";
|
|
2106
|
+
const actionValue = JSON.parse(actionValueStr);
|
|
2107
|
+
const token = event.token || "";
|
|
2108
|
+
const messageId = event.message_id || "";
|
|
2109
|
+
const chatId = event.chat_id || "";
|
|
2110
|
+
const operatorId = event.operator_id?.open_id ?? event.operator_id ?? undefined;
|
|
2111
|
+
if (!token || !messageId || !chatId) {
|
|
2112
|
+
logger2.warn(`[LarkCliInstance] handleCardActionEvent: missing token/messageId/chatId, skip`);
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
if (!this.cardActionPorts) {
|
|
2116
|
+
logger2.warn(`[LarkCliInstance] handleCardActionEvent: cardActionPorts 未注入,` + `card.action.trigger 事件被跳过。` + `调 instance.setCardActionPorts({getTask, updateTaskContext, createGroupChat, deleteGroupChat, notifier}) 注入。`);
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
const result = await handleCardAction({
|
|
2120
|
+
actionValue,
|
|
2121
|
+
chatId,
|
|
2122
|
+
messageId,
|
|
2123
|
+
token,
|
|
2124
|
+
operatorId,
|
|
2125
|
+
profile
|
|
2126
|
+
}, this.cardActionPorts);
|
|
2127
|
+
if (result.ok && result.cardUpdate) {
|
|
2128
|
+
const patch = this.cardActionPorts.patchByToken ?? patchCardByLarkCli;
|
|
2129
|
+
await patch(token, result.cardUpdate, profile);
|
|
2130
|
+
} else {
|
|
2131
|
+
logger2.warn(`[LarkCliInstance] handleCardActionEvent failed: ${result.error ?? "unknown"}`);
|
|
2132
|
+
}
|
|
2133
|
+
} catch (err) {
|
|
2134
|
+
logger2.error(`[LarkCliInstance] handleCardActionEvent error: ${err.message}`);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
async patchCardByToken(token, card, profile) {
|
|
2138
|
+
return patchCardByLarkCli(token, card, profile);
|
|
2139
|
+
}
|
|
1662
2140
|
extractUserId(rawEvent) {
|
|
1663
2141
|
const event = rawEvent;
|
|
1664
2142
|
const innerEvent = event.event;
|
|
@@ -1824,7 +2302,7 @@ var larkCliHandler = {
|
|
|
1824
2302
|
},
|
|
1825
2303
|
async emitTriggerEvent(payload, store) {
|
|
1826
2304
|
const eventId = await store.insertTriggerEvent(payload, "lark-cli");
|
|
1827
|
-
|
|
2305
|
+
logger2.info(`[larkCliHandler] emitTriggerEvent: id=${eventId}`, {
|
|
1828
2306
|
chatId: payload.metadata?.chatId,
|
|
1829
2307
|
senderId: payload.senderId
|
|
1830
2308
|
});
|
|
@@ -1839,7 +2317,7 @@ var larkCliHandler = {
|
|
|
1839
2317
|
try {
|
|
1840
2318
|
const result = await runner.run(command);
|
|
1841
2319
|
if (result.code !== 0) {
|
|
1842
|
-
|
|
2320
|
+
logger2.error(`[larkCliHandler] replyMessage non-zero exit`, {
|
|
1843
2321
|
code: result.code,
|
|
1844
2322
|
stderr: result.stderr
|
|
1845
2323
|
});
|
|
@@ -1853,7 +2331,7 @@ var larkCliHandler = {
|
|
|
1853
2331
|
return { success: true, messageId: replyMessageId };
|
|
1854
2332
|
} catch (err) {
|
|
1855
2333
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1856
|
-
|
|
2334
|
+
logger2.error(`[larkCliHandler] replyMessage spawn error`, { error: errMsg });
|
|
1857
2335
|
return { success: false, error: errMsg };
|
|
1858
2336
|
}
|
|
1859
2337
|
},
|
|
@@ -1935,6 +2413,16 @@ function getLarkCliRunner() {
|
|
|
1935
2413
|
function setLarkCliRunner(runner) {
|
|
1936
2414
|
_larkCliRunner = runner;
|
|
1937
2415
|
}
|
|
2416
|
+
async function patchCardByLarkCli(token, card, profile) {
|
|
2417
|
+
const profileFlag = profile ? `--profile ${profile} ` : "";
|
|
2418
|
+
const cmd = `lark-cli ${profileFlag}api POST /open-apis/interactive/v1/card/update ` + `--data '${JSON.stringify({ token, card }).replace(/'/g, "'\\''")}' --as bot`;
|
|
2419
|
+
try {
|
|
2420
|
+
const { stdout } = await execAsync2(cmd, { timeout: 30000 });
|
|
2421
|
+
logger2.debug(`patchCardByLarkCli ok: ${stdout?.slice(0, 200)}`);
|
|
2422
|
+
} catch (err) {
|
|
2423
|
+
logger2.error(`patchCardByLarkCli failed: ${err.message}`);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
1938
2426
|
var builtInHandlers = [
|
|
1939
2427
|
larkCliHandler,
|
|
1940
2428
|
timerHandler,
|
|
@@ -1969,7 +2457,7 @@ var EVENT_SOURCE_CONFIG_REGISTRATION = {
|
|
|
1969
2457
|
};
|
|
1970
2458
|
|
|
1971
2459
|
// src/env/event-source/event-source-component.ts
|
|
1972
|
-
var
|
|
2460
|
+
var logger3 = createLogger("event-source");
|
|
1973
2461
|
|
|
1974
2462
|
class EventSourceComponent extends BaseComponent {
|
|
1975
2463
|
name = "event-source";
|
|
@@ -2036,7 +2524,7 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2036
2524
|
for (const handler of builtInHandlers) {
|
|
2037
2525
|
if (!this.handlers.has(handler.type)) {
|
|
2038
2526
|
this.handlers.set(handler.type, handler);
|
|
2039
|
-
|
|
2527
|
+
logger3.debug(`Registered built-in handler: ${handler.type}`);
|
|
2040
2528
|
}
|
|
2041
2529
|
}
|
|
2042
2530
|
}
|
|
@@ -2046,15 +2534,15 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2046
2534
|
}
|
|
2047
2535
|
registerHandler(handler) {
|
|
2048
2536
|
if (this.handlers.has(handler.type)) {
|
|
2049
|
-
|
|
2537
|
+
logger3.warn(`Handler already registered for type: ${handler.type}, overwriting`);
|
|
2050
2538
|
}
|
|
2051
2539
|
this.handlers.set(handler.type, handler);
|
|
2052
|
-
|
|
2540
|
+
logger3.debug(`Handler registered: ${handler.type}`);
|
|
2053
2541
|
}
|
|
2054
2542
|
unregisterHandler(type) {
|
|
2055
2543
|
const deleted = this.handlers.delete(type);
|
|
2056
2544
|
if (deleted) {
|
|
2057
|
-
|
|
2545
|
+
logger3.debug(`Handler unregistered: ${type}`);
|
|
2058
2546
|
}
|
|
2059
2547
|
return deleted;
|
|
2060
2548
|
}
|
|
@@ -2144,7 +2632,7 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2144
2632
|
});
|
|
2145
2633
|
}
|
|
2146
2634
|
onConfigChange(event) {
|
|
2147
|
-
|
|
2635
|
+
logger3.info(`EventSource config changed: ${event.key}`, {
|
|
2148
2636
|
oldValue: event.oldValue,
|
|
2149
2637
|
newValue: event.newValue
|
|
2150
2638
|
});
|
|
@@ -2163,9 +2651,9 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2163
2651
|
}
|
|
2164
2652
|
this.sources.set(config.id, config);
|
|
2165
2653
|
this.statuses.set(config.id, "created");
|
|
2166
|
-
|
|
2654
|
+
logger3.debug(`EventSource registered: ${config.id} (${config.type})`);
|
|
2167
2655
|
this.saveConfig().catch((err) => {
|
|
2168
|
-
|
|
2656
|
+
logger3.error(`Failed to save config after registering ${config.id}:`, err);
|
|
2169
2657
|
});
|
|
2170
2658
|
}
|
|
2171
2659
|
unregister(id) {
|
|
@@ -2180,9 +2668,9 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2180
2668
|
this.eventHandlers.delete(id);
|
|
2181
2669
|
this.buffers.delete(id);
|
|
2182
2670
|
if (existed) {
|
|
2183
|
-
|
|
2671
|
+
logger3.debug(`EventSource unregistered: ${id}`);
|
|
2184
2672
|
this.saveConfig().catch((err) => {
|
|
2185
|
-
|
|
2673
|
+
logger3.error(`Failed to save config after unregistering ${id}:`, err);
|
|
2186
2674
|
});
|
|
2187
2675
|
}
|
|
2188
2676
|
return existed;
|
|
@@ -2220,13 +2708,13 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2220
2708
|
}
|
|
2221
2709
|
}
|
|
2222
2710
|
this.sources.set(id, updated);
|
|
2223
|
-
|
|
2711
|
+
logger3.info(`EventSource updated: ${id}`, {
|
|
2224
2712
|
fields: Object.keys(partial)
|
|
2225
2713
|
});
|
|
2226
2714
|
try {
|
|
2227
2715
|
await this.saveConfig();
|
|
2228
2716
|
} catch (err) {
|
|
2229
|
-
|
|
2717
|
+
logger3.error(`Failed to save config after updating ${id}:`, err);
|
|
2230
2718
|
throw err;
|
|
2231
2719
|
}
|
|
2232
2720
|
return {
|
|
@@ -2295,18 +2783,18 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2295
2783
|
type: "event-source.started",
|
|
2296
2784
|
payload: { sourceId: id, sourceType: config.type }
|
|
2297
2785
|
});
|
|
2298
|
-
|
|
2786
|
+
logger3.info(`EventSource started: ${id} (${config.type})`);
|
|
2299
2787
|
} catch (error) {
|
|
2300
2788
|
this.statuses.set(id, "error");
|
|
2301
|
-
|
|
2789
|
+
logger3.error(`EventSource failed to start: ${id}`, error);
|
|
2302
2790
|
throw error;
|
|
2303
2791
|
}
|
|
2304
2792
|
}
|
|
2305
2793
|
async stopSource(id) {
|
|
2306
2794
|
const status = this.statuses.get(id);
|
|
2307
|
-
|
|
2795
|
+
logger3.info(`[EventSource ${id}] stopSource called, current status: ${status}`);
|
|
2308
2796
|
if (status === "stopped" || status === "created") {
|
|
2309
|
-
|
|
2797
|
+
logger3.info(`[EventSource ${id}] already stopped/created, skipping`);
|
|
2310
2798
|
return;
|
|
2311
2799
|
}
|
|
2312
2800
|
this.statuses.set(id, "stopping");
|
|
@@ -2322,10 +2810,10 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2322
2810
|
type: "event-source.stopped",
|
|
2323
2811
|
payload: { sourceId: id, sourceType: config?.type }
|
|
2324
2812
|
});
|
|
2325
|
-
|
|
2813
|
+
logger3.info(`EventSource stopped: ${id}`);
|
|
2326
2814
|
} catch (error) {
|
|
2327
2815
|
this.statuses.set(id, "error");
|
|
2328
|
-
|
|
2816
|
+
logger3.error(`EventSource failed to stop: ${id}`, error);
|
|
2329
2817
|
throw error;
|
|
2330
2818
|
}
|
|
2331
2819
|
}
|
|
@@ -2350,9 +2838,9 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2350
2838
|
sources: Array.from(this.sources.values())
|
|
2351
2839
|
};
|
|
2352
2840
|
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
2353
|
-
|
|
2841
|
+
logger3.debug(`Saved ${this.sources.size} event source configurations`);
|
|
2354
2842
|
} catch (error) {
|
|
2355
|
-
|
|
2843
|
+
logger3.error("Failed to save event source configurations:", error);
|
|
2356
2844
|
throw error;
|
|
2357
2845
|
}
|
|
2358
2846
|
}
|
|
@@ -2362,13 +2850,13 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2362
2850
|
try {
|
|
2363
2851
|
const configPath = this.getConfigFilePath();
|
|
2364
2852
|
if (!existsSync(configPath)) {
|
|
2365
|
-
|
|
2853
|
+
logger3.debug(`No event source config file found at ${configPath}`);
|
|
2366
2854
|
return;
|
|
2367
2855
|
}
|
|
2368
2856
|
const content = readFileSync(configPath, "utf-8");
|
|
2369
2857
|
const config = JSON.parse(content);
|
|
2370
2858
|
if (!config.sources || !Array.isArray(config.sources)) {
|
|
2371
|
-
|
|
2859
|
+
logger3.warn("Invalid event source config format, skipping load");
|
|
2372
2860
|
return;
|
|
2373
2861
|
}
|
|
2374
2862
|
let loadedCount = 0;
|
|
@@ -2379,9 +2867,9 @@ class EventSourceComponent extends BaseComponent {
|
|
|
2379
2867
|
loadedCount++;
|
|
2380
2868
|
}
|
|
2381
2869
|
}
|
|
2382
|
-
|
|
2870
|
+
logger3.info(`Loaded ${loadedCount} event source configurations`);
|
|
2383
2871
|
} catch (error) {
|
|
2384
|
-
|
|
2872
|
+
logger3.error("Failed to load event source configurations:", error);
|
|
2385
2873
|
}
|
|
2386
2874
|
}
|
|
2387
2875
|
}
|