@ai-setting/roy-agent-core 1.7.51 → 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.
@@ -11,7 +11,7 @@ import {
11
11
  larkCliHandler,
12
12
  resolveBountyIMSystemPrompt,
13
13
  timerHandler
14
- } from "../../shared/@ai-setting/roy-agent-core-wwytg8rv.js";
14
+ } from "../../shared/@ai-setting/roy-agent-core-ezgsem94.js";
15
15
  import {
16
16
  BUILT_IN_EVENT_SOURCE_TYPES,
17
17
  BUILT_IN_EVENT_SOURCE_TYPE_LIST,
@@ -22,7 +22,7 @@ import {
22
22
  validateEventSourceConfig
23
23
  } from "../../shared/@ai-setting/roy-agent-core-ck60j1q9.js";
24
24
  import"../../shared/@ai-setting/roy-agent-core-rnwr39b0.js";
25
- import"../../shared/@ai-setting/roy-agent-core-kyasfzeh.js";
25
+ import"../../shared/@ai-setting/roy-agent-core-pkpkctvf.js";
26
26
  import {
27
27
  EventStoreImpl,
28
28
  getSharedEventStore,
@@ -31,8 +31,8 @@ import {
31
31
  } from "../../shared/@ai-setting/roy-agent-core-yhsssn86.js";
32
32
  import"../../shared/@ai-setting/roy-agent-core-1pg0fepg.js";
33
33
  import"../../shared/@ai-setting/roy-agent-core-gdsqp6j8.js";
34
- import"../../shared/@ai-setting/roy-agent-core-wxsgay02.js";
35
- import"../../shared/@ai-setting/roy-agent-core-zd077w51.js";
34
+ import"../../shared/@ai-setting/roy-agent-core-brwzs8wf.js";
35
+ import"../../shared/@ai-setting/roy-agent-core-j8e92gj8.js";
36
36
  import"../../shared/@ai-setting/roy-agent-core-9csa89jx.js";
37
37
  import"../../shared/@ai-setting/roy-agent-core-na73mym1.js";
38
38
  import"../../shared/@ai-setting/roy-agent-core-8s9jd2qy.js";
package/dist/env/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BaseEnvironment
3
- } from "../shared/@ai-setting/roy-agent-core-9faekt5n.js";
3
+ } from "../shared/@ai-setting/roy-agent-core-rcfewbzr.js";
4
4
  import {
5
5
  TaskComponent
6
6
  } from "../shared/@ai-setting/roy-agent-core-39xejr39.js";
@@ -15,7 +15,7 @@ import {
15
15
  EventSourceComponent,
16
16
  builtInHandlers,
17
17
  larkCliHandler
18
- } from "../shared/@ai-setting/roy-agent-core-wwytg8rv.js";
18
+ } from "../shared/@ai-setting/roy-agent-core-ezgsem94.js";
19
19
  import {
20
20
  EventSourceInitHooks,
21
21
  getDefaultConfigForType,
@@ -24,12 +24,12 @@ import {
24
24
  validateEventSourceConfig
25
25
  } from "../shared/@ai-setting/roy-agent-core-ck60j1q9.js";
26
26
  import"../shared/@ai-setting/roy-agent-core-rnwr39b0.js";
27
- import"../shared/@ai-setting/roy-agent-core-kyasfzeh.js";
27
+ import"../shared/@ai-setting/roy-agent-core-pkpkctvf.js";
28
28
  import"../shared/@ai-setting/roy-agent-core-yhsssn86.js";
29
29
  import"../shared/@ai-setting/roy-agent-core-1pg0fepg.js";
30
30
  import"../shared/@ai-setting/roy-agent-core-gdsqp6j8.js";
31
- import"../shared/@ai-setting/roy-agent-core-wxsgay02.js";
32
- import"../shared/@ai-setting/roy-agent-core-zd077w51.js";
31
+ import"../shared/@ai-setting/roy-agent-core-brwzs8wf.js";
32
+ import"../shared/@ai-setting/roy-agent-core-j8e92gj8.js";
33
33
  import {
34
34
  WorkflowComponent
35
35
  } from "../shared/@ai-setting/roy-agent-core-s214vmmd.js";
@@ -10,11 +10,11 @@ import {
10
10
  formatExtractorInput,
11
11
  formatExtractorInputFromDescription,
12
12
  parseExtractorOutput
13
- } from "../../../shared/@ai-setting/roy-agent-core-kyasfzeh.js";
13
+ } from "../../../shared/@ai-setting/roy-agent-core-pkpkctvf.js";
14
14
  import"../../../shared/@ai-setting/roy-agent-core-yhsssn86.js";
15
15
  import"../../../shared/@ai-setting/roy-agent-core-1pg0fepg.js";
16
16
  import"../../../shared/@ai-setting/roy-agent-core-gdsqp6j8.js";
17
- import"../../../shared/@ai-setting/roy-agent-core-zd077w51.js";
17
+ import"../../../shared/@ai-setting/roy-agent-core-j8e92gj8.js";
18
18
  import"../../../shared/@ai-setting/roy-agent-core-na73mym1.js";
19
19
  import"../../../shared/@ai-setting/roy-agent-core-8s9jd2qy.js";
20
20
  import"../../../shared/@ai-setting/roy-agent-core-jrj30vq3.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  LarkCliWorkflowNotifyHook,
3
3
  createLarkCliWorkflowNotifyHook
4
- } from "../../../shared/@ai-setting/roy-agent-core-wxsgay02.js";
5
- import"../../../shared/@ai-setting/roy-agent-core-zd077w51.js";
4
+ } from "../../../shared/@ai-setting/roy-agent-core-brwzs8wf.js";
5
+ import"../../../shared/@ai-setting/roy-agent-core-j8e92gj8.js";
6
6
  import"../../../shared/@ai-setting/roy-agent-core-9csa89jx.js";
7
7
  import"../../../shared/@ai-setting/roy-agent-core-qcspfszy.js";
8
8
  import"../../../shared/@ai-setting/roy-agent-core-4z3rq5gw.js";
package/dist/index.js CHANGED
@@ -66,7 +66,7 @@ import {
66
66
  BaseEnvironment,
67
67
  generateDescendingId,
68
68
  generateId
69
- } from "./shared/@ai-setting/roy-agent-core-9faekt5n.js";
69
+ } from "./shared/@ai-setting/roy-agent-core-rcfewbzr.js";
70
70
  import {
71
71
  TaskComponent
72
72
  } from "./shared/@ai-setting/roy-agent-core-39xejr39.js";
@@ -92,7 +92,7 @@ import {
92
92
  larkCliHandler,
93
93
  resolveBountyIMSystemPrompt,
94
94
  timerHandler
95
- } from "./shared/@ai-setting/roy-agent-core-wwytg8rv.js";
95
+ } from "./shared/@ai-setting/roy-agent-core-ezgsem94.js";
96
96
  import {
97
97
  BUILT_IN_EVENT_SOURCE_TYPES,
98
98
  BUILT_IN_EVENT_SOURCE_TYPE_LIST,
@@ -109,7 +109,7 @@ import {
109
109
  import {
110
110
  TaskTagPlugin,
111
111
  createAutoTaskPlugin
112
- } from "./shared/@ai-setting/roy-agent-core-kyasfzeh.js";
112
+ } from "./shared/@ai-setting/roy-agent-core-pkpkctvf.js";
113
113
  import {
114
114
  EventStoreImpl,
115
115
  getSharedEventStore,
@@ -123,8 +123,8 @@ import {
123
123
  getBuiltInPrompt,
124
124
  getBuiltInPromptNames
125
125
  } from "./shared/@ai-setting/roy-agent-core-gdsqp6j8.js";
126
- import"./shared/@ai-setting/roy-agent-core-wxsgay02.js";
127
- import"./shared/@ai-setting/roy-agent-core-zd077w51.js";
126
+ import"./shared/@ai-setting/roy-agent-core-brwzs8wf.js";
127
+ import"./shared/@ai-setting/roy-agent-core-j8e92gj8.js";
128
128
  import {
129
129
  WorkflowComponent
130
130
  } from "./shared/@ai-setting/roy-agent-core-s214vmmd.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LarkCardPatchNotifier,
3
3
  formatDurationMs
4
- } from "./roy-agent-core-zd077w51.js";
4
+ } from "./roy-agent-core-j8e92gj8.js";
5
5
  import {
6
6
  WorkflowHookPoints,
7
7
  init_workflow_hook_points
@@ -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-kyasfzeh.js";
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-wxsgay02.js";
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 logger = createLogger("lark-cli:event-source");
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
- child;
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
- await this.loadPlugins();
1095
- const profile = this.config.profile;
1096
- const profileFlag = profile ? `--profile ${profile} ` : "";
1097
- let command;
1098
- if (this.config.command) {
1099
- command = this.config.command.replace(/^(lark-cli)(\s+|$)/, (_, head, tail) => `${head} ${profileFlag.trim()}${tail}`);
1100
- } else {
1101
- command = `lark-cli ${profileFlag}event consume im.message.receive_v1 --as bot`;
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
- this.child = spawn2("exec " + command, [], {
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
- let stdoutBuffer = "";
1109
- let stderrBuffer = "";
1110
- this.child.stdout?.on("data", (data) => {
1111
- const str = data.toString();
1112
- stdoutBuffer += str;
1113
- this.processStream(stdoutBuffer);
1114
- stdoutBuffer = "";
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
- this.child.stderr?.on("data", (data) => {
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 (!output.startsWith("[event]") && output.startsWith("{")) {
1119
- stderrBuffer += output;
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
- this.child.on("exit", (code) => {
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
- this.status = "stopped";
1483
+ logger2.info(`[LarkCliInstance] consume 子进程已退出 code=${code} (${source}) cmd="${command}"`);
1132
1484
  }
1133
- });
1134
- this.child.on("error", (error) => {
1135
- console.error(`[LarkCliInstance] Process error: ${error.message}`);
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
- await new Promise((resolve) => setTimeout(resolve, 1000));
1139
- this.status = "running";
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
- if (this.child && this.child.pid) {
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
- this.child.kill("SIGTERM");
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
- console.error(`[LarkCliInstance] Error stopping process: ${error}`);
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
- this.child?.kill("SIGKILL");
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
- this.wireCardRoutingGroupProvider();
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
- logger.warn(`[LarkCliInstance] resolveChatTaskBinding failed for chatId=${chatId}: ${err}. ` + `群消息将不会注入 parentTaskId。`);
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
- logger.warn(`[LarkCliInstance] duplicate lark-cli event suppressed across instances (event_id=${dedupKey}, ` + `lastSeen=${now - lastShared}ms ago). ` + `Task #4137: SHARED_SEEN 跨实例去重。`);
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
- logger.warn(`[LarkCliInstance] duplicate lark-cli event suppressed (event_id=${dedupKey}, ` + `lastSeen=${now - last}ms ago). ` + `Task #3977/3978: 防止同一飞书事件触发两次任务创建。`);
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
- logger.info(`[larkCliHandler] emitTriggerEvent: id=${eventId}`, {
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
- logger.error(`[larkCliHandler] replyMessage non-zero exit`, {
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
- logger.error(`[larkCliHandler] replyMessage spawn error`, { error: errMsg });
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 logger2 = createLogger("event-source");
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
- logger2.debug(`Registered built-in handler: ${handler.type}`);
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
- logger2.warn(`Handler already registered for type: ${handler.type}, overwriting`);
2537
+ logger3.warn(`Handler already registered for type: ${handler.type}, overwriting`);
2050
2538
  }
2051
2539
  this.handlers.set(handler.type, handler);
2052
- logger2.debug(`Handler registered: ${handler.type}`);
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
- logger2.debug(`Handler unregistered: ${type}`);
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
- logger2.info(`EventSource config changed: ${event.key}`, {
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
- logger2.debug(`EventSource registered: ${config.id} (${config.type})`);
2654
+ logger3.debug(`EventSource registered: ${config.id} (${config.type})`);
2167
2655
  this.saveConfig().catch((err) => {
2168
- logger2.error(`Failed to save config after registering ${config.id}:`, err);
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
- logger2.debug(`EventSource unregistered: ${id}`);
2671
+ logger3.debug(`EventSource unregistered: ${id}`);
2184
2672
  this.saveConfig().catch((err) => {
2185
- logger2.error(`Failed to save config after unregistering ${id}:`, err);
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
- logger2.info(`EventSource updated: ${id}`, {
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
- logger2.error(`Failed to save config after updating ${id}:`, err);
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
- logger2.info(`EventSource started: ${id} (${config.type})`);
2786
+ logger3.info(`EventSource started: ${id} (${config.type})`);
2299
2787
  } catch (error) {
2300
2788
  this.statuses.set(id, "error");
2301
- logger2.error(`EventSource failed to start: ${id}`, error);
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
- logger2.info(`[EventSource ${id}] stopSource called, current status: ${status}`);
2795
+ logger3.info(`[EventSource ${id}] stopSource called, current status: ${status}`);
2308
2796
  if (status === "stopped" || status === "created") {
2309
- logger2.info(`[EventSource ${id}] already stopped/created, skipping`);
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
- logger2.info(`EventSource stopped: ${id}`);
2813
+ logger3.info(`EventSource stopped: ${id}`);
2326
2814
  } catch (error) {
2327
2815
  this.statuses.set(id, "error");
2328
- logger2.error(`EventSource failed to stop: ${id}`, error);
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
- logger2.debug(`Saved ${this.sources.size} event source configurations`);
2841
+ logger3.debug(`Saved ${this.sources.size} event source configurations`);
2354
2842
  } catch (error) {
2355
- logger2.error("Failed to save event source configurations:", error);
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
- logger2.debug(`No event source config file found at ${configPath}`);
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
- logger2.warn("Invalid event source config format, skipping load");
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
- logger2.info(`Loaded ${loadedCount} event source configurations`);
2870
+ logger3.info(`Loaded ${loadedCount} event source configurations`);
2383
2871
  } catch (error) {
2384
- logger2.error("Failed to load event source configurations:", error);
2872
+ logger3.error("Failed to load event source configurations:", error);
2385
2873
  }
2386
2874
  }
2387
2875
  }
@@ -229,6 +229,29 @@ function buildTaskCard(opts) {
229
229
  const updateCount = opts.updateCount ?? 0;
230
230
  const spinner = opts.spinner ?? "";
231
231
  const elements = [];
232
+ const hasGroup = !!(opts.meta.perTaskChatId && opts.meta.perTaskChatId.length > 0);
233
+ const taskIdForButton = opts.meta.id !== undefined ? String(opts.meta.id) : "";
234
+ elements.push({
235
+ tag: "button",
236
+ text: {
237
+ tag: "plain_text",
238
+ content: hasGroup ? "解散该群" : "为这个任务建群"
239
+ },
240
+ type: hasGroup ? "danger" : "primary",
241
+ value: {
242
+ action: hasGroup ? "disband_group" : "create_group",
243
+ taskId: taskIdForButton
244
+ }
245
+ });
246
+ if (hasGroup) {
247
+ const link = buildChatAppLink(opts.meta.perTaskChatId);
248
+ const label = opts.meta.perTaskChatName ? `\uD83D\uDCE8 进入专属群:${sanitizeInlineText(opts.meta.perTaskChatName, 60)}` : "\uD83D\uDCE8 进入专属群";
249
+ elements.push({
250
+ tag: "markdown",
251
+ content: `[${label}](${link})`
252
+ });
253
+ elements.push({ tag: "hr" });
254
+ }
232
255
  const infoLines = renderTaskInfoLines(opts.meta);
233
256
  if (infoLines.length > 0) {
234
257
  elements.push({ tag: "markdown", content: infoLines.join(`
@@ -595,6 +618,25 @@ class LarkCardPatchNotifier {
595
618
  this.states.delete(opts.messageId);
596
619
  this.mutexes.delete(opts.messageId);
597
620
  }
621
+ updateMetaAndBuildCard(messageId, meta, opts = {}) {
622
+ const state = this.states.get(messageId);
623
+ if (!state)
624
+ return;
625
+ state.meta = mergeTaskMeta(state.meta, meta);
626
+ if (opts.clearPerTaskChat) {
627
+ delete state.meta.perTaskChatId;
628
+ delete state.meta.perTaskChatName;
629
+ }
630
+ return buildTaskCard({
631
+ status: "running",
632
+ meta: state.meta,
633
+ milestones: state.milestones,
634
+ processSteps: state.processSteps,
635
+ progress: state.progress,
636
+ updateCount: state.updateCount,
637
+ spinner: SPINNER_EMOJIS[state.spinnerIndex] ?? SPINNER_EMOJIS[0]
638
+ });
639
+ }
598
640
  trackedCardCount() {
599
641
  return this.states.size;
600
642
  }
@@ -1034,4 +1076,4 @@ function sanitize(text) {
1034
1076
  return `${trimmed.slice(0, 500)}…`;
1035
1077
  }
1036
1078
 
1037
- export { humanizeValue, formatDurationMs, LarkCardPatchNotifier };
1079
+ export { humanizeValue, buildTaskCard, formatDurationMs, LarkCardPatchNotifier };
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  LarkCardPatchNotifier,
12
12
  humanizeValue
13
- } from "./roy-agent-core-zd077w51.js";
13
+ } from "./roy-agent-core-j8e92gj8.js";
14
14
  import {
15
15
  validateWorkflowDefinition
16
16
  } from "./roy-agent-core-na73mym1.js";
@@ -393,6 +393,17 @@ function extractPerTaskChatId(contextStr) {
393
393
  return;
394
394
  }
395
395
  }
396
+ function extractPerTaskChatName(contextStr) {
397
+ if (!contextStr)
398
+ return;
399
+ try {
400
+ const obj = JSON.parse(contextStr);
401
+ const v = obj?.["larkPerTaskChatName"];
402
+ return typeof v === "string" && v.length > 0 ? v : undefined;
403
+ } catch {
404
+ return;
405
+ }
406
+ }
396
407
  function extractPerTaskChatProfile(contextStr) {
397
408
  if (!contextStr)
398
409
  return;
@@ -507,7 +518,8 @@ function decideCardChatTarget(input) {
507
518
  lineageChat,
508
519
  triggerChatId,
509
520
  triggerProfile,
510
- triggerOwnership
521
+ triggerOwnership,
522
+ allowNewGroup
511
523
  } = input;
512
524
  const foreign = !!triggerChatId && !!triggerOwnership?.dedicated && triggerOwnership.ownerTaskIds.length > 0 && !triggerOwnership.ownerTaskIds.some((id) => lineageIds.includes(id));
513
525
  if (triggerChatId && !foreign) {
@@ -538,16 +550,25 @@ function decideCardChatTarget(input) {
538
550
  reason: "既没有触发群也没有 lineage 群(无法决定投递目标)"
539
551
  };
540
552
  }
553
+ if (allowNewGroup === true) {
554
+ return {
555
+ chatId: undefined,
556
+ source: "new-group",
557
+ profile: triggerProfile,
558
+ reroutedFrom: foreign ? {
559
+ chatId: triggerChatId,
560
+ ownerTaskIds: triggerOwnership.ownerTaskIds,
561
+ sources: triggerOwnership.sources
562
+ } : undefined,
563
+ reason: foreign ? `触发群 ${triggerChatId} 是 task ${triggerOwnership.ownerTaskIds.join(",")} 的专属群,且本 task 无群 → 需要新建(allowNewGroup=true)` : "无触发群且本 task 无群 → 需要新建(allowNewGroup=true)"
564
+ };
565
+ }
541
566
  return {
542
- chatId: undefined,
543
- source: "new-group",
567
+ chatId: triggerChatId,
544
568
  profile: triggerProfile,
545
- reroutedFrom: foreign ? {
546
- chatId: triggerChatId,
547
- ownerTaskIds: triggerOwnership.ownerTaskIds,
548
- sources: triggerOwnership.sources
549
- } : undefined,
550
- reason: foreign ? `触发群 ${triggerChatId} 是 task ${triggerOwnership.ownerTaskIds.join(",")} 的专属群,且本 task 无群 → 需要新建` : "无触发群且本 task 无群 → 需要新建"
569
+ source: "fallback-to-trigger",
570
+ reroutedFrom: undefined,
571
+ reason: foreign ? `触发群 ${triggerChatId} 是 task ${triggerOwnership.ownerTaskIds.join(",")} 的专属群,但默认不建群 → 退回触发群 ${triggerChatId}` : `默认不建群 → 退回触发群 ${triggerChatId}`
551
572
  };
552
573
  }
553
574
 
@@ -777,7 +798,8 @@ class LarkCliTaskNotifyHook {
777
798
  lineageChat,
778
799
  triggerChatId,
779
800
  triggerProfile,
780
- triggerOwnership
801
+ triggerOwnership,
802
+ allowNewGroup: this.config.allowNewGroup === true
781
803
  });
782
804
  if (decision.source === "new-group") {
783
805
  if (this.cardRoutingPorts?.ensureTaskGroupChat) {
@@ -987,6 +1009,8 @@ class LarkCliTaskNotifyHook {
987
1009
  return;
988
1010
  }
989
1011
  buildTaskMeta(task) {
1012
+ const recordedNames = extractRecordedChatNames(task.context);
1013
+ const matchedName = recordedNames.find((n) => new RegExp(`(?:Task\\s*#?\\s*|#)(?<![\\d])(?<!\\d)${task.id}(?!\\d)(?![\\d])`, "i").test(n));
990
1014
  return {
991
1015
  id: task.id,
992
1016
  title: task.title,
@@ -997,7 +1021,9 @@ class LarkCliTaskNotifyHook {
997
1021
  type: task.type,
998
1022
  projectPath: task.project_path,
999
1023
  context: task.context,
1000
- currentStatus: task.current_status
1024
+ currentStatus: task.current_status,
1025
+ perTaskChatId: extractPerTaskChatId(task.context),
1026
+ perTaskChatName: matchedName
1001
1027
  };
1002
1028
  }
1003
1029
  deriveTimelineFromOperation(operation) {
@@ -1119,6 +1145,7 @@ class PerTaskGroupChatPlugin {
1119
1145
  sendWelcomeMessage: config.sendWelcomeMessage ?? true,
1120
1146
  resolveToTriggerChatFallback: config.resolveToTriggerChatFallback ?? false,
1121
1147
  cardInitialMessage: config.cardInitialMessage !== false,
1148
+ autoCreateGroup: config.autoCreateGroup === true,
1122
1149
  cardNotifier: config.cardNotifier ?? new LarkCardPatchNotifier
1123
1150
  };
1124
1151
  this.cardNotifier = this.config.cardNotifier;
@@ -1263,7 +1290,11 @@ class PerTaskGroupChatPlugin {
1263
1290
  console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.messageId=${env.messageId}`);
1264
1291
  console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.sourceId=${env.sourceId}`);
1265
1292
  }
1266
- logger4.debug(`onAfterCreate[${this.pluginName}]: enter task=${task.id} "${task.title}" ` + `parent_task_id=${task.parent_task_id ?? "none"} ` + `requireReplyChannel=${this.config.requireReplyChannel} ` + `disableSubTaskSkip=${this.config.disableSubTaskSkip} ` + `sendWelcomeMessage=${this.config.sendWelcomeMessage}`);
1293
+ logger4.debug(`onAfterCreate[${this.pluginName}]: enter task=${task.id} "${task.title}" ` + `parent_task_id=${task.parent_task_id ?? "none"} ` + `requireReplyChannel=${this.config.requireReplyChannel} ` + `disableSubTaskSkip=${this.config.disableSubTaskSkip} ` + `sendWelcomeMessage=${this.config.sendWelcomeMessage} ` + `autoCreateGroup=${this.config.autoCreateGroup}`);
1294
+ if (!this.config.autoCreateGroup) {
1295
+ logger4.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} autoCreateGroup=false ` + `→ skip group chat creation(按需建群:用户在卡片顶部按钮触发)`);
1296
+ return;
1297
+ }
1267
1298
  const replyChannel = this.getReplyChannelFromCtx(ctx);
1268
1299
  if (!replyChannel?.chatId && this.config.requireReplyChannel) {
1269
1300
  logger4.debug(`onAfterCreate[${this.pluginName}]: no replyChannel.chatId for task #${task.id}, skip. ` + `修复:eventsource 收到 im.message.receive_v1 事件时 replyChannel 应有值;` + `或在 plugin config 设 requireReplyChannel=false 强制创建群聊(profile 降级到 fallbackProfile/无)`);
@@ -1714,4 +1745,4 @@ __legacyDecorateClassTS([
1714
1745
  function createPerTaskGroupChatPlugin(config = {}) {
1715
1746
  return new PerTaskGroupChatPlugin(config);
1716
1747
  }
1717
- export { createAutoTaskPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, mergeChatOwnership, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
1748
+ export { createAutoTaskPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, extractPerTaskChatId, extractPerTaskChatName, mergeChatOwnership, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createAutoTaskPlugin
3
- } from "./roy-agent-core-kyasfzeh.js";
3
+ } from "./roy-agent-core-pkpkctvf.js";
4
4
  import {
5
5
  ContextError,
6
6
  ErrorCodes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-setting/roy-agent-core",
3
- "version": "1.7.51",
3
+ "version": "1.7.52",
4
4
  "type": "module",
5
5
  "description": "Core SDK for roy-agent - Environment, Components, Tools, Sessions, Tasks",
6
6
  "main": "./dist/index.js",