@ai-setting/roy-agent-core 1.7.85 → 1.7.89

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.
Files changed (148) hide show
  1. package/dist/config/index.js +7 -7
  2. package/dist/env/agent/agent-component.d.ts +1 -1
  3. package/dist/env/agent/agent-component.js +13 -12
  4. package/dist/env/agent/index.js +13 -12
  5. package/dist/env/agent/types.d.ts +14 -0
  6. package/dist/env/commands/index.js +7 -7
  7. package/dist/env/context/index.js +3 -3
  8. package/dist/env/debug/index.js +7 -7
  9. package/dist/env/error-reply.js +1 -1
  10. package/dist/env/event-source/event-source-handlers.d.ts +18 -2
  11. package/dist/env/event-source/event-source-handlers.js +26 -21
  12. package/dist/env/event-source/event-store.js +2 -2
  13. package/dist/env/event-source/index.js +25 -22
  14. package/dist/env/event-source/timer-handler.d.ts +98 -0
  15. package/dist/env/event-source/types.d.ts +53 -1
  16. package/dist/env/event-source/wecom-card-action-handler.d.ts +235 -3
  17. package/dist/env/event-source/wecom-card-builder.d.ts +207 -1
  18. package/dist/env/event-source/wecom-cli-handler.d.ts +25 -0
  19. package/dist/{shared/@ai-setting/roy-agent-core-vyr6svt6.js → env/event-source/wecom-respond-registry.js} +8 -6
  20. package/dist/env/event-source/wecom-session-card-sender.d.ts +135 -0
  21. package/dist/env/event-source/wecom-session-card-sender.js +153 -0
  22. package/dist/env/hook/index.js +5 -5
  23. package/dist/env/index.js +50 -45
  24. package/dist/env/llm/index.js +9 -8
  25. package/dist/env/log-trace/index.js +7 -7
  26. package/dist/env/log-trace/trace-filters.d.ts +6 -1
  27. package/dist/env/mcp/index.js +7 -7
  28. package/dist/env/memory/index.js +7 -7
  29. package/dist/env/plugin/index.js +7 -7
  30. package/dist/env/prompt/index.js +8 -8
  31. package/dist/env/prompt/prompts-index.d.ts +1 -1
  32. package/dist/env/session/group-session-link.d.ts +99 -0
  33. package/dist/env/session/index.js +11 -10
  34. package/dist/env/session/session-component.d.ts +89 -0
  35. package/dist/env/session/storage/index.js +5 -4
  36. package/dist/env/session/types.d.ts +10 -0
  37. package/dist/env/skill/index.js +7 -7
  38. package/dist/env/task/backend/index.d.ts +3 -0
  39. package/dist/env/task/backend/index.js +151 -3
  40. package/dist/env/task/backend/remote-task-port.d.ts +85 -0
  41. package/dist/env/task/backend/types.d.ts +5 -0
  42. package/dist/env/task/delegate/delegate-channel-hint.d.ts +39 -0
  43. package/dist/env/task/delegate/delegate-tool.d.ts +71 -6
  44. package/dist/env/task/delegate/delegate-tool.js +11 -9
  45. package/dist/env/task/delegate/index.js +8 -8
  46. package/dist/env/task/index.d.ts +1 -1
  47. package/dist/env/task/index.js +34 -18
  48. package/dist/env/task/mcp/index.js +4 -4
  49. package/dist/env/task/plugins/card-subject.d.ts +117 -0
  50. package/dist/env/task/plugins/index.js +19 -17
  51. package/dist/env/task/plugins/lark-card-action-handler.d.ts +261 -6
  52. package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +501 -0
  53. package/dist/env/task/plugins/lark-card-patch-notifier.js +12 -2
  54. package/dist/env/task/plugins/lark-card-state-store.d.ts +60 -0
  55. package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +45 -0
  56. package/dist/env/task/plugins/sub-session-activity.d.ts +415 -0
  57. package/dist/env/task/plugins/wecom-session-card-store.d.ts +134 -0
  58. package/dist/env/task/plugins/wecom-session-card-store.js +23 -0
  59. package/dist/env/task/plugins/wecom-task-notify-plugin.d.ts +40 -0
  60. package/dist/env/task/storage/index.d.ts +1 -0
  61. package/dist/env/task/storage/index.js +24 -4
  62. package/dist/env/task/storage/migrations/006_add_task_owner.d.ts +24 -0
  63. package/dist/env/task/storage/remote-task-store.d.ts +186 -0
  64. package/dist/env/task/task-component.d.ts +7 -0
  65. package/dist/env/task/tools/index.js +4 -4
  66. package/dist/env/task/tools/task-types.d.ts +7 -0
  67. package/dist/env/task/types.d.ts +17 -0
  68. package/dist/env/tool/built-in/index.js +6 -4
  69. package/dist/env/tool/index.js +14 -10
  70. package/dist/env/tool/types.d.ts +25 -0
  71. package/dist/env/tool/validator.d.ts +8 -0
  72. package/dist/env/workflow/engine/index.js +12 -12
  73. package/dist/env/workflow/hooks/index.js +7 -7
  74. package/dist/env/workflow/index.js +24 -23
  75. package/dist/env/workflow/nodes/index.js +2 -2
  76. package/dist/env/workflow/plugins/index.js +10 -8
  77. package/dist/env/workflow/service/index.js +2 -2
  78. package/dist/env/workflow/storage/index.js +3 -3
  79. package/dist/env/workflow/tools/index.js +16 -15
  80. package/dist/env/workflow/utils/index.js +4 -4
  81. package/dist/index.d.ts +1 -1
  82. package/dist/index.js +71 -60
  83. package/dist/shared/@ai-setting/{roy-agent-core-q813vafd.js → roy-agent-core-05z35shp.js} +1 -1
  84. package/dist/shared/@ai-setting/{roy-agent-core-dc9zq75z.js → roy-agent-core-0crmsc12.js} +3 -3
  85. package/dist/shared/@ai-setting/{roy-agent-core-7dd0s3xb.js → roy-agent-core-0n1wwndq.js} +1027 -14
  86. package/dist/shared/@ai-setting/{roy-agent-core-cha0jc7n.js → roy-agent-core-0sbmdz8d.js} +137 -16
  87. package/dist/shared/@ai-setting/{roy-agent-core-87942bw4.js → roy-agent-core-1mhyh4c5.js} +1 -1
  88. package/dist/shared/@ai-setting/{roy-agent-core-v6vr6za7.js → roy-agent-core-1vv4fx15.js} +4 -4
  89. package/dist/shared/@ai-setting/{roy-agent-core-nwbfepba.js → roy-agent-core-3e040tc5.js} +1 -1
  90. package/dist/shared/@ai-setting/roy-agent-core-418zc9ha.js +288 -0
  91. package/dist/shared/@ai-setting/{roy-agent-core-qk9hd89p.js → roy-agent-core-49qcpqwg.js} +54 -19
  92. package/dist/shared/@ai-setting/{roy-agent-core-xf6z96qy.js → roy-agent-core-4b38m749.js} +818 -346
  93. package/dist/shared/@ai-setting/{roy-agent-core-r5wt1h3c.js → roy-agent-core-4fkxy2gv.js} +1 -1
  94. package/dist/shared/@ai-setting/{roy-agent-core-na18zwj0.js → roy-agent-core-4t3yskp8.js} +1 -1
  95. package/dist/shared/@ai-setting/{roy-agent-core-d1n4jbza.js → roy-agent-core-5dhvfgaw.js} +5 -5
  96. package/dist/shared/@ai-setting/{roy-agent-core-5pr4zp88.js → roy-agent-core-5kzhaap1.js} +1 -1
  97. package/dist/shared/@ai-setting/{roy-agent-core-qq96p0tj.js → roy-agent-core-5s2t8z00.js} +22 -1
  98. package/dist/shared/@ai-setting/{roy-agent-core-6rh8k6ba.js → roy-agent-core-6gj1hc5z.js} +7 -7
  99. package/dist/shared/@ai-setting/{roy-agent-core-g8htfwxj.js → roy-agent-core-7f10p7m2.js} +1 -1
  100. package/dist/shared/@ai-setting/{roy-agent-core-h21gngxf.js → roy-agent-core-7yh5wps0.js} +1 -1
  101. package/dist/shared/@ai-setting/{roy-agent-core-efkjyh69.js → roy-agent-core-9710zwwz.js} +1 -1
  102. package/dist/shared/@ai-setting/{roy-agent-core-rykf7c04.js → roy-agent-core-9c565frx.js} +1 -1
  103. package/dist/shared/@ai-setting/{roy-agent-core-vffccgf6.js → roy-agent-core-9cen1jvg.js} +18 -7
  104. package/dist/shared/@ai-setting/{roy-agent-core-fghc2m8z.js → roy-agent-core-aa8kv18f.js} +34 -2
  105. package/dist/shared/@ai-setting/roy-agent-core-age3qseb.js +177 -0
  106. package/dist/shared/@ai-setting/{roy-agent-core-7bpj859q.js → roy-agent-core-azyz78ky.js} +7 -7
  107. package/dist/shared/@ai-setting/{roy-agent-core-264qexac.js → roy-agent-core-b7h39vrc.js} +4 -4
  108. package/dist/shared/@ai-setting/{roy-agent-core-dqxec05w.js → roy-agent-core-bjtc9d35.js} +2 -2
  109. package/dist/shared/@ai-setting/roy-agent-core-c4ec7k0p.js +426 -0
  110. package/dist/shared/@ai-setting/{roy-agent-core-ddyk1rc0.js → roy-agent-core-cre2y2ct.js} +160 -4
  111. package/dist/shared/@ai-setting/{roy-agent-core-tr3ep81v.js → roy-agent-core-d6zdbgtc.js} +1 -1
  112. package/dist/shared/@ai-setting/{roy-agent-core-0qyrk5k0.js → roy-agent-core-ds6qzmf8.js} +14 -5
  113. package/dist/shared/@ai-setting/{roy-agent-core-p6ks90tb.js → roy-agent-core-dvec4nn2.js} +6 -6
  114. package/dist/shared/@ai-setting/{roy-agent-core-cgtq6427.js → roy-agent-core-e08bvpaq.js} +1 -1
  115. package/dist/shared/@ai-setting/{roy-agent-core-12n08pg5.js → roy-agent-core-e69qxdyy.js} +1 -1
  116. package/dist/shared/@ai-setting/{roy-agent-core-xcp8vwax.js → roy-agent-core-fepm835e.js} +2 -2
  117. package/dist/shared/@ai-setting/{roy-agent-core-6j02sbee.js → roy-agent-core-fhg03v43.js} +1 -1
  118. package/dist/shared/@ai-setting/{roy-agent-core-dvtg4sn0.js → roy-agent-core-g8yr733d.js} +1 -1
  119. package/dist/shared/@ai-setting/{roy-agent-core-m3vjepzz.js → roy-agent-core-h1e1fm9b.js} +38 -202
  120. package/dist/shared/@ai-setting/{roy-agent-core-c2gbt68e.js → roy-agent-core-h83zdsrq.js} +2 -2
  121. package/dist/shared/@ai-setting/{roy-agent-core-tk98f92b.js → roy-agent-core-k3gcvybs.js} +2 -2
  122. package/dist/shared/@ai-setting/{roy-agent-core-b31v3qpv.js → roy-agent-core-k3h8t4q1.js} +992 -212
  123. package/dist/shared/@ai-setting/{roy-agent-core-pgvcf91v.js → roy-agent-core-m39v7qpx.js} +2 -2
  124. package/dist/shared/@ai-setting/{roy-agent-core-ezvpreqz.js → roy-agent-core-m66ysky0.js} +54 -6
  125. package/dist/shared/@ai-setting/{roy-agent-core-begm7ytz.js → roy-agent-core-m7d1t3db.js} +3 -3
  126. package/dist/shared/@ai-setting/{roy-agent-core-tm1mxttk.js → roy-agent-core-mf5p38yf.js} +2 -2
  127. package/dist/shared/@ai-setting/{roy-agent-core-83bewqx8.js → roy-agent-core-mvh0td7p.js} +2 -2
  128. package/dist/shared/@ai-setting/{roy-agent-core-ze5cqpf2.js → roy-agent-core-mx855td4.js} +17 -38
  129. package/dist/shared/@ai-setting/{roy-agent-core-0dh8npnz.js → roy-agent-core-n197medr.js} +13 -13
  130. package/dist/shared/@ai-setting/{roy-agent-core-cmprygrp.js → roy-agent-core-nqkfkpf4.js} +4 -4
  131. package/dist/shared/@ai-setting/{roy-agent-core-7cbpmk4q.js → roy-agent-core-p31ftgq9.js} +1 -1
  132. package/dist/shared/@ai-setting/{roy-agent-core-1v4g0d4x.js → roy-agent-core-rm7seqwb.js} +1 -1
  133. package/dist/shared/@ai-setting/{roy-agent-core-ax9e9szb.js → roy-agent-core-smf94h73.js} +1 -1
  134. package/dist/shared/@ai-setting/{roy-agent-core-acj8ag08.js → roy-agent-core-tjnabyvq.js} +1 -1
  135. package/dist/shared/@ai-setting/{roy-agent-core-qqaak24r.js → roy-agent-core-tkpp1ba0.js} +1 -1
  136. package/dist/shared/@ai-setting/{roy-agent-core-dkrvmjsh.js → roy-agent-core-tpm1dd1g.js} +23 -15
  137. package/dist/shared/@ai-setting/{roy-agent-core-ed233k5z.js → roy-agent-core-v1n0106v.js} +1 -1
  138. package/dist/shared/@ai-setting/{roy-agent-core-zxz5fkwj.js → roy-agent-core-v6yshv0z.js} +1 -1
  139. package/dist/shared/@ai-setting/{roy-agent-core-mcy5apay.js → roy-agent-core-vf9sp6s3.js} +1 -1
  140. package/dist/shared/@ai-setting/{roy-agent-core-xvt7kf7p.js → roy-agent-core-w9ryeg2p.js} +3 -3
  141. package/dist/shared/@ai-setting/{roy-agent-core-es203w7p.js → roy-agent-core-wqs6yypq.js} +1 -1
  142. package/dist/shared/@ai-setting/roy-agent-core-wxrkcb17.js +56 -0
  143. package/dist/shared/@ai-setting/{roy-agent-core-0snm5hc5.js → roy-agent-core-y6shd91r.js} +1 -1
  144. package/dist/shared/@ai-setting/{roy-agent-core-9vxh6f8h.js → roy-agent-core-y7fqtzk2.js} +7 -7
  145. package/dist/shared/@ai-setting/{roy-agent-core-cpcd24n2.js → roy-agent-core-yet36q5k.js} +2 -2
  146. package/dist/shared/@ai-setting/{roy-agent-core-3fh1f7s7.js → roy-agent-core-yfpnkc4z.js} +5 -5
  147. package/dist/shared/@ai-setting/roy-agent-core-zhwcje5a.js +352 -0
  148. package/package.json +9 -1
@@ -1,20 +1,18 @@
1
1
  import {
2
- WecomCardActionKey,
3
- buildWecomButtonInteractionCard,
4
- buildWecomTextNoticeCard,
5
2
  sendWecomReply,
6
3
  sendWecomTemplateCard
7
- } from "./roy-agent-core-m3vjepzz.js";
4
+ } from "./roy-agent-core-h1e1fm9b.js";
5
+ import {
6
+ WecomCardActionKey,
7
+ buildWecomButtonInteractionCard,
8
+ buildWecomTextNoticeCard
9
+ } from "./roy-agent-core-c4ec7k0p.js";
8
10
  import {
9
11
  getSharedEventStore
10
- } from "./roy-agent-core-na18zwj0.js";
12
+ } from "./roy-agent-core-4t3yskp8.js";
11
13
  import {
12
14
  WORKFLOW_EXTRACT_PROMPT
13
- } from "./roy-agent-core-9vxh6f8h.js";
14
- import {
15
- createCompatibleDatabase,
16
- init_sqlite_task_store
17
- } from "./roy-agent-core-ezvpreqz.js";
15
+ } from "./roy-agent-core-y7fqtzk2.js";
18
16
  import {
19
17
  TaskHookPoints
20
18
  } from "./roy-agent-core-1pg0fepg.js";
@@ -27,32 +25,43 @@ import {
27
25
  renderCandidateKey,
28
26
  renderCandidateValue,
29
27
  writeDecisionHint
30
- } from "./roy-agent-core-tr3ep81v.js";
28
+ } from "./roy-agent-core-d6zdbgtc.js";
31
29
  import {
32
30
  LarkCardPatchNotifier,
31
+ buildSessionCard,
32
+ buildTaskCard,
33
+ createLarkCardStateStore,
34
+ getSharedLarkCardNotifier,
33
35
  humanizeValue
34
- } from "./roy-agent-core-7dd0s3xb.js";
36
+ } from "./roy-agent-core-0n1wwndq.js";
37
+ import {
38
+ RECENT_MESSAGE_LIMIT
39
+ } from "./roy-agent-core-418zc9ha.js";
40
+ import {
41
+ createCompatibleDatabase,
42
+ init_sqlite_task_store
43
+ } from "./roy-agent-core-m66ysky0.js";
35
44
  import {
36
45
  validateWorkflowDefinition
37
- } from "./roy-agent-core-1v4g0d4x.js";
46
+ } from "./roy-agent-core-rm7seqwb.js";
38
47
  import {
39
48
  enqueueToAddFakeUserMessage
40
- } from "./roy-agent-core-h21gngxf.js";
49
+ } from "./roy-agent-core-7yh5wps0.js";
41
50
  import {
42
51
  BasePlugin
43
52
  } from "./roy-agent-core-az13yzmc.js";
44
53
  import {
45
54
  init_context
46
- } from "./roy-agent-core-qqaak24r.js";
55
+ } from "./roy-agent-core-tkpp1ba0.js";
47
56
  import {
48
57
  getEnvContextOrEmpty,
49
58
  init_env_context
50
- } from "./roy-agent-core-7cbpmk4q.js";
59
+ } from "./roy-agent-core-p31ftgq9.js";
51
60
  import {
52
61
  TracedAs,
53
62
  init_decorator,
54
63
  wrapFunction
55
- } from "./roy-agent-core-qq96p0tj.js";
64
+ } from "./roy-agent-core-5s2t8z00.js";
56
65
  import {
57
66
  createLogger,
58
67
  init_logger
@@ -185,7 +194,8 @@ class TaskTagPlugin extends BasePlugin {
185
194
  const keywords = this.extractKeywords(currentTask);
186
195
  if (keywords.length === 0)
187
196
  return;
188
- const similarTasks = await this.tagService?.findSimilarTasksByKeywords?.(keywords, {
197
+ const tagService = (this.taskComponent && typeof this.taskComponent.getTagService === "function" ? this.taskComponent.getTagService() : undefined) ?? this.tagService;
198
+ const similarTasks = await tagService?.findSimilarTasksByKeywords?.(keywords, {
189
199
  limit: 5,
190
200
  excludeTaskId: taskId
191
201
  }) ?? [];
@@ -321,11 +331,11 @@ ${prompt}`;
321
331
  }
322
332
  if (!this._workflowRepo) {
323
333
  try {
324
- const sqlite = await import("./roy-agent-core-dc9zq75z.js");
334
+ const sqlite = await import("./roy-agent-core-0crmsc12.js");
325
335
  this.ensureDataDir();
326
336
  const db = sqlite.getDatabase();
327
337
  sqlite.initializeTables();
328
- const { WorkflowRepository } = await import("./roy-agent-core-xvt7kf7p.js");
338
+ const { WorkflowRepository } = await import("./roy-agent-core-w9ryeg2p.js");
329
339
  this._workflowRepo = new WorkflowRepository(db);
330
340
  return this._workflowRepo;
331
341
  } catch {
@@ -1684,178 +1694,10 @@ init_decorator();
1684
1694
  init_logger();
1685
1695
  init_context();
1686
1696
 
1687
- // src/env/task/plugins/lark-card-state-store.ts
1697
+ // src/env/task/plugins/lark-card-action-handler.ts
1688
1698
  init_logger();
1689
- init_sqlite_task_store();
1690
- import { join } from "path";
1691
- var logger6 = createLogger("lark-cli:card-state");
1692
-
1693
- class MemoryLarkCardStateStore {
1694
- records = new Map;
1695
- upsert(state) {
1696
- this.records.set(state.taskId, { ...state });
1697
- }
1698
- get(taskId) {
1699
- const r = this.records.get(taskId);
1700
- return r ? { ...r } : undefined;
1701
- }
1702
- list() {
1703
- return [...this.records.values()].map((r) => ({ ...r }));
1704
- }
1705
- markPinned(taskId, pinned) {
1706
- const r = this.records.get(taskId);
1707
- if (!r)
1708
- return;
1709
- r.pinned = pinned;
1710
- r.updatedAt = Date.now();
1711
- }
1712
- markPaused(taskId) {
1713
- const r = this.records.get(taskId);
1714
- if (!r)
1715
- return;
1716
- r.status = "paused";
1717
- r.updatedAt = Date.now();
1718
- }
1719
- markReopened(taskId) {
1720
- const r = this.records.get(taskId);
1721
- if (!r)
1722
- return;
1723
- r.status = "open";
1724
- delete r.closedAt;
1725
- r.updatedAt = Date.now();
1726
- }
1727
- markClosed(taskId) {
1728
- const r = this.records.get(taskId);
1729
- if (!r)
1730
- return;
1731
- const now = Date.now();
1732
- r.status = "closed";
1733
- r.closedAt = now;
1734
- r.updatedAt = now;
1735
- }
1736
- delete(taskId) {
1737
- this.records.delete(taskId);
1738
- }
1739
- close() {
1740
- this.records.clear();
1741
- }
1742
- }
1743
- function rowToState(row) {
1744
- const state = {
1745
- taskId: row.task_id,
1746
- messageId: row.message_id,
1747
- chatId: row.chat_id,
1748
- pinned: row.pinned === 1,
1749
- status: row.status ?? "open",
1750
- createdAt: row.created_at,
1751
- updatedAt: row.updated_at
1752
- };
1753
- if (row.profile)
1754
- state.profile = row.profile;
1755
- if (row.closed_at !== null && row.closed_at !== undefined) {
1756
- state.closedAt = row.closed_at;
1757
- }
1758
- if (row.timeline)
1759
- state.timeline = row.timeline;
1760
- return state;
1761
- }
1762
-
1763
- class SqliteLarkCardStateStore {
1764
- db;
1765
- constructor(dbPath) {
1766
- if (dbPath !== ":memory:") {
1767
- const { mkdirSync } = __require("fs");
1768
- const dir = __require("path").dirname(dbPath);
1769
- try {
1770
- mkdirSync(dir, { recursive: true });
1771
- } catch {}
1772
- }
1773
- this.db = createCompatibleDatabase(dbPath);
1774
- try {
1775
- this.db.run("PRAGMA journal_mode = WAL");
1776
- this.db.run("PRAGMA busy_timeout = 5000");
1777
- this.db.run("PRAGMA synchronous = NORMAL");
1778
- } catch {}
1779
- this.db.run(`
1780
- CREATE TABLE IF NOT EXISTS lark_card (
1781
- task_id INTEGER PRIMARY KEY,
1782
- message_id TEXT NOT NULL,
1783
- chat_id TEXT NOT NULL,
1784
- profile TEXT,
1785
- pinned INTEGER NOT NULL DEFAULT 0,
1786
- status TEXT NOT NULL DEFAULT 'open',
1787
- created_at INTEGER NOT NULL,
1788
- updated_at INTEGER NOT NULL,
1789
- closed_at INTEGER,
1790
- timeline TEXT
1791
- )
1792
- `);
1793
- try {
1794
- this.db.run("ALTER TABLE lark_card ADD COLUMN timeline TEXT");
1795
- } catch {}
1796
- this.db.run("CREATE INDEX IF NOT EXISTS idx_lark_card_status ON lark_card(status)");
1797
- }
1798
- upsert(state) {
1799
- this.db.prepare(`INSERT INTO lark_card
1800
- (task_id, message_id, chat_id, profile, pinned, status, created_at, updated_at, closed_at, timeline)
1801
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1802
- ON CONFLICT(task_id) DO UPDATE SET
1803
- message_id = excluded.message_id,
1804
- chat_id = excluded.chat_id,
1805
- profile = excluded.profile,
1806
- pinned = excluded.pinned,
1807
- status = excluded.status,
1808
- updated_at = excluded.updated_at,
1809
- closed_at = excluded.closed_at,
1810
- timeline = excluded.timeline`).run(state.taskId, state.messageId, state.chatId, state.profile ?? null, state.pinned ? 1 : 0, state.status, state.createdAt, state.updatedAt, state.closedAt ?? null, state.timeline ?? null);
1811
- }
1812
- get(taskId) {
1813
- const row = this.db.prepare("SELECT * FROM lark_card WHERE task_id = ?").get(taskId);
1814
- return row ? rowToState(row) : undefined;
1815
- }
1816
- list() {
1817
- const rows = this.db.prepare("SELECT * FROM lark_card ORDER BY task_id ASC").all();
1818
- return rows.map(rowToState);
1819
- }
1820
- markPinned(taskId, pinned) {
1821
- this.db.prepare("UPDATE lark_card SET pinned = ?, updated_at = ? WHERE task_id = ?").run(pinned ? 1 : 0, Date.now(), taskId);
1822
- }
1823
- markPaused(taskId) {
1824
- this.db.prepare("UPDATE lark_card SET status = 'paused', updated_at = ? WHERE task_id = ?").run(Date.now(), taskId);
1825
- }
1826
- markReopened(taskId) {
1827
- this.db.prepare("UPDATE lark_card SET status = 'open', closed_at = NULL, updated_at = ? WHERE task_id = ?").run(Date.now(), taskId);
1828
- }
1829
- markClosed(taskId) {
1830
- const now = Date.now();
1831
- this.db.prepare("UPDATE lark_card SET status = 'closed', closed_at = ?, updated_at = ? WHERE task_id = ?").run(now, now, taskId);
1832
- }
1833
- delete(taskId) {
1834
- this.db.prepare("DELETE FROM lark_card WHERE task_id = ?").run(taskId);
1835
- }
1836
- close() {
1837
- try {
1838
- this.db.close?.();
1839
- } catch {}
1840
- }
1841
- }
1842
- function getDefaultLarkCardDbPath() {
1843
- const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
1844
- return join(home, ".local", "share", "roy-agent", "lark-cards.db");
1845
- }
1846
- function createLarkCardStateStore(dbPath) {
1847
- const isTestEnv = "BUN_TEST_WORKER_ID" in process.env;
1848
- if (!dbPath && !process.env.ROY_AGENT_LARK_CARD_DB && isTestEnv) {
1849
- return new MemoryLarkCardStateStore;
1850
- }
1851
- const path = dbPath ?? process.env.ROY_AGENT_LARK_CARD_DB ?? getDefaultLarkCardDbPath();
1852
- try {
1853
- return new SqliteLarkCardStateStore(path);
1854
- } catch (err) {
1855
- logger6.warn(`LarkCardStateStore: sqlite unavailable at ${path} (${err.message}) ` + `→ falling back to in-memory (卡片仍可发,但重启后无法 patch 旧卡)`);
1856
- return new MemoryLarkCardStateStore;
1857
- }
1858
- }
1699
+ import { exec } from "node:child_process";
1700
+ import { promisify } from "node:util";
1859
1701
 
1860
1702
  // src/env/task/plugins/card-chat-routing.ts
1861
1703
  var CTX_FIELD_LARK_PER_TASK_CHAT_ID = "larkPerTaskChatId";
@@ -1933,16 +1775,781 @@ function decideCardChatTarget(input) {
1933
1775
  };
1934
1776
  }
1935
1777
 
1778
+ // src/env/task/plugins/card-subject.ts
1779
+ function pickCardSubject(value) {
1780
+ const rawSession = value?.sessionId;
1781
+ const sessionId = typeof rawSession === "string" && rawSession.trim() ? rawSession : undefined;
1782
+ if (!sessionId)
1783
+ return;
1784
+ const rawTask = value?.taskId;
1785
+ const taskId = typeof rawTask === "number" ? rawTask : typeof rawTask === "string" && rawTask.trim() ? Number(rawTask.trim()) : undefined;
1786
+ return {
1787
+ sessionId,
1788
+ ...taskId !== undefined && Number.isSafeInteger(taskId) && taskId > 0 ? { taskId } : {}
1789
+ };
1790
+ }
1791
+
1792
+ // src/env/task/plugins/lark-card-action-handler.ts
1793
+ var logger6 = createLogger("lark-card-action-handler");
1794
+ var execAsync = promisify(exec);
1795
+ var KNOWN_CARD_ACTIONS = new Set([
1796
+ "create_group",
1797
+ "disband_group",
1798
+ "refresh_activity"
1799
+ ]);
1800
+ function isCardActionName(v) {
1801
+ return typeof v === "string" && KNOWN_CARD_ACTIONS.has(v);
1802
+ }
1803
+ var ACTION_SUBJECT_FIELD = {
1804
+ create_group: "taskId or sessionId",
1805
+ disband_group: "taskId or sessionId",
1806
+ refresh_activity: "sessionId"
1807
+ };
1808
+ function validateCardActionValue(raw) {
1809
+ const action = raw.action;
1810
+ if (!isCardActionName(action)) {
1811
+ return { ok: false, action: String(action), error: `Unknown action: ${String(action)}` };
1812
+ }
1813
+ const requiredField = ACTION_SUBJECT_FIELD[action];
1814
+ if (action === "create_group" || action === "disband_group") {
1815
+ const rawTaskId = raw.taskId;
1816
+ const rawSessionId2 = typeof raw.sessionId === "string" ? raw.sessionId.trim() : "";
1817
+ if (rawTaskId === undefined || rawTaskId === null || rawTaskId === "") {
1818
+ if (rawSessionId2) {
1819
+ return { ok: true, action, sessionId: rawSessionId2 };
1820
+ }
1821
+ return {
1822
+ ok: false,
1823
+ action,
1824
+ error: `Missing required field: ${requiredField} (action=${action})`
1825
+ };
1826
+ }
1827
+ const taskId = typeof rawTaskId === "number" ? rawTaskId : typeof rawTaskId === "string" ? parseInt(rawTaskId, 10) : Number.NaN;
1828
+ if (!Number.isFinite(taskId) || Number.isNaN(taskId) || taskId <= 0) {
1829
+ return { ok: false, action, error: `Invalid taskId: ${String(rawTaskId)}` };
1830
+ }
1831
+ return {
1832
+ ok: true,
1833
+ action,
1834
+ taskId,
1835
+ ...rawSessionId2 ? { sessionId: rawSessionId2 } : {}
1836
+ };
1837
+ }
1838
+ const rawSessionId = raw.sessionId;
1839
+ const sessionId = typeof rawSessionId === "string" ? rawSessionId.trim() : "";
1840
+ if (!sessionId) {
1841
+ return {
1842
+ ok: false,
1843
+ action,
1844
+ error: `Missing required field: ${requiredField} (action=${action})`
1845
+ };
1846
+ }
1847
+ return { ok: true, action, sessionId };
1848
+ }
1849
+ function extractTargetSessionId(context) {
1850
+ if (!context)
1851
+ return;
1852
+ try {
1853
+ const parsed = JSON.parse(context);
1854
+ for (const key of ["subSessionId", "sessionId"]) {
1855
+ const v = parsed[key];
1856
+ if (typeof v === "string" && v.trim().length > 0)
1857
+ return v.trim();
1858
+ }
1859
+ return;
1860
+ } catch {
1861
+ return;
1862
+ }
1863
+ }
1864
+ async function resolveGroupLinkTargetSessionId(event, ports, taskId, taskContext) {
1865
+ const fromCard = event.actionValue?.sessionId;
1866
+ if (typeof fromCard === "string" && fromCard.trim().length > 0) {
1867
+ return { sessionId: fromCard.trim(), source: "card" };
1868
+ }
1869
+ const fromContext = extractTargetSessionId(taskContext);
1870
+ if (fromContext) {
1871
+ return { sessionId: fromContext, source: "task.context" };
1872
+ }
1873
+ if (ports.resolveSessionIdByTask) {
1874
+ try {
1875
+ const resolved = await ports.resolveSessionIdByTask(taskId);
1876
+ if (typeof resolved === "string" && resolved.trim().length > 0) {
1877
+ return { sessionId: resolved.trim(), source: "resolveSessionIdByTask" };
1878
+ }
1879
+ } catch (err) {
1880
+ logger6.warn(`resolveGroupLinkTargetSessionId: resolveSessionIdByTask(${taskId}) 失败(继续跳过 link): ` + `${err.message}`);
1881
+ }
1882
+ }
1883
+ return { sessionId: undefined, source: "none" };
1884
+ }
1885
+ async function attachGroupSessionForTask(event, ports, taskId, chatId, taskContext) {
1886
+ if (!ports.attachGroupSession)
1887
+ return;
1888
+ const { sessionId: targetSessionId, source } = await resolveGroupLinkTargetSessionId(event, ports, taskId, taskContext);
1889
+ if (!targetSessionId) {
1890
+ logger6.warn(`attachGroupSessionForTask: 取不到目标 session(card/context/resolveSessionIdByTask 全空),` + `跳过关联 —— 群 ${chatId} 将是独立空会话(无背景提示)`);
1891
+ return;
1892
+ }
1893
+ try {
1894
+ await ports.attachGroupSession({
1895
+ chatId,
1896
+ originSessionId: targetSessionId,
1897
+ title: `Lark Chat ${chatId}`
1898
+ });
1899
+ logger6.info(`attachGroupSessionForTask: ${chatId} ← ${targetSessionId}` + `(来源=${source},已注入 1 条 user 事件作为背景提示)`);
1900
+ } catch (err) {
1901
+ logger6.warn(`attachGroupSessionForTask: attach(${chatId} ← ${targetSessionId}) 失败(不阻断建群): ` + `${err.message}`);
1902
+ }
1903
+ }
1904
+ var CTX_FIELD_CHAT_ID = "larkPerTaskChatId";
1905
+ var CTX_FIELD_CHAT_NAME = "larkPerTaskChatName";
1906
+ var CTX_FIELD_CHAT_CREATED_AT = "larkPerTaskChatCreatedAt";
1907
+ var CTX_FIELD_CHAT_TASK_ID = "larkPerTaskChatTaskId";
1908
+ var CTX_FIELD_CHAT_INVITEES = "larkPerTaskChatInvitees";
1909
+ var cardActionInflight = new Map;
1910
+ async function handleCardAction(event, ports) {
1911
+ const rawAction = event.actionValue.action;
1912
+ if (!isCardActionName(rawAction)) {
1913
+ return { ok: false, action: String(rawAction), error: `Unknown action: ${String(rawAction)}` };
1914
+ }
1915
+ if (rawAction === "refresh_activity") {
1916
+ return await handleRefreshSessionCard(event, ports);
1917
+ }
1918
+ const validation = validateCardActionValue(event.actionValue);
1919
+ if (!validation.ok) {
1920
+ const failed = validation;
1921
+ return { ok: false, action: rawAction, error: failed.error };
1922
+ }
1923
+ if (validation.action === "refresh_activity") {
1924
+ return await handleRefreshSessionCard(event, ports);
1925
+ }
1926
+ const groupValidation = validation;
1927
+ const action = groupValidation.action;
1928
+ const taskId = groupValidation.taskId;
1929
+ const subjectSessionId = groupValidation.sessionId;
1930
+ const inflightSubject = event.messageId || (taskId !== undefined ? String(taskId) : subjectSessionId) || "-";
1931
+ const inflightKey = `${event.profile ?? "-"}:${inflightSubject}:${action}`;
1932
+ const inflight = cardActionInflight.get(inflightKey);
1933
+ if (inflight) {
1934
+ logger6.warn(`handleCardAction: ${inflightKey} 已有同操作在处理中(疑似用户重复点击),本次忽略。`);
1935
+ return { ok: false, action, error: `重复点击:${inflightKey} 正在处理中,已忽略` };
1936
+ }
1937
+ const pending = taskId !== undefined ? handleCardActionInner(event, ports, action, taskId) : handleGroupActionSessionOnly(event, ports, action, subjectSessionId);
1938
+ cardActionInflight.set(inflightKey, pending);
1939
+ try {
1940
+ return await pending;
1941
+ } finally {
1942
+ cardActionInflight.delete(inflightKey);
1943
+ }
1944
+ }
1945
+ async function handleRefreshSessionCard(event, ports) {
1946
+ const action = "refresh_activity";
1947
+ let subject = pickCardSubject(event.actionValue);
1948
+ if (!subject) {
1949
+ const legacyTaskId = parseTaskIdLoose(event.actionValue.taskId);
1950
+ if (legacyTaskId !== undefined) {
1951
+ let resolved;
1952
+ try {
1953
+ resolved = await ports.resolveSessionIdByTask?.(legacyTaskId);
1954
+ } catch {
1955
+ resolved = undefined;
1956
+ }
1957
+ if (resolved) {
1958
+ subject = { sessionId: resolved, taskId: legacyTaskId };
1959
+ logger6.info(`handleCardAction: 旧 task 卡 #${legacyTaskId} 无 sessionId,` + `反查得到 ${resolved}(按 session 主键刷新)`);
1960
+ }
1961
+ }
1962
+ }
1963
+ if (!subject) {
1964
+ return {
1965
+ ok: false,
1966
+ action,
1967
+ error: `Missing required field: sessionId (action=${action}; sessionId is required ` + `as the card primary key, got taskId=${String(event.actionValue.taskId)})`
1968
+ };
1969
+ }
1970
+ const canRenderSessionCard = ports.loadSessionActivity !== undefined || typeof ports.notifier?.buildSessionCardWithActivity === "function";
1971
+ if (!canRenderSessionCard && ports.loadSubSessionActivity && subject.taskId !== undefined) {
1972
+ const legacyTask = await ports.getTask?.(subject.taskId);
1973
+ if (legacyTask) {
1974
+ logger6.info(`handleCardAction: 旧装配点(无 loadSessionActivity)—— task #${subject.taskId} ` + `刷新回落到 task 主键路径(保留 #5360 之前的可达性)`);
1975
+ return await handleRefreshActivity(event, ports, legacyTask, subject.taskId);
1976
+ }
1977
+ }
1978
+ if (!canRenderSessionCard && !ports.refreshSessionCard) {
1979
+ logger6.warn(`handleCardAction: refresh_activity 已通过 sessionId 校验(sessionId=${subject.sessionId}),` + `但既没有 ports.refreshSessionCard,也没有 loadSessionActivity / ` + `buildSessionCardWithActivity —— 无 session 卡渲染实现。`);
1980
+ return {
1981
+ ok: false,
1982
+ action,
1983
+ error: `refresh_activity not available: no session card renderer injected ` + `(sessionId=${subject.sessionId}; ports.refreshSessionCard / loadSessionActivity both missing)`
1984
+ };
1985
+ }
1986
+ if (ports.refreshSessionCard) {
1987
+ const inflightKey = `${event.profile ?? "-"}:${event.messageId || subject.sessionId}:${action}`;
1988
+ const inflight = cardActionInflight.get(inflightKey);
1989
+ if (inflight) {
1990
+ logger6.warn(`handleCardAction: ${inflightKey} 已有同操作在处理中(疑似用户重复点击),本次忽略。`);
1991
+ return { ok: false, action, error: `重复点击:${inflightKey} 正在处理中,已忽略` };
1992
+ }
1993
+ const pending = (async () => {
1994
+ try {
1995
+ const result = await ports.refreshSessionCard({
1996
+ sessionId: subject.sessionId,
1997
+ chatId: event.chatId,
1998
+ messageId: event.messageId,
1999
+ token: event.token,
2000
+ profile: event.profile
2001
+ });
2002
+ if (result.error) {
2003
+ return { ok: false, action, error: result.error };
2004
+ }
2005
+ return { ok: true, action, cardUpdate: result.cardUpdate };
2006
+ } catch (err) {
2007
+ logger6.error(`handleCardAction.refresh_activity failed: ${err.message}`);
2008
+ return { ok: false, action, error: err.message };
2009
+ }
2010
+ })();
2011
+ cardActionInflight.set(inflightKey, pending);
2012
+ try {
2013
+ return await pending;
2014
+ } finally {
2015
+ cardActionInflight.delete(inflightKey);
2016
+ }
2017
+ }
2018
+ return await handleRefreshSessionActivity(event, ports, subject);
2019
+ }
2020
+ async function handleGroupActionSessionOnly(event, ports, action, sessionId) {
2021
+ const existing = await lookupSessionGroup(ports, sessionId);
2022
+ if (action === "create_group") {
2023
+ if (existing?.chatId) {
2024
+ logger6.info(`handleGroupActionSessionOnly: 会话 ${sessionId} 已有群 ${existing.chatId}` + `(幂等命中,不重复建群)`);
2025
+ return {
2026
+ ok: true,
2027
+ action,
2028
+ newChatId: existing.chatId,
2029
+ cardUpdate: buildCardAfterSessionGroupChange(event, ports, {
2030
+ sessionId,
2031
+ chatId: existing.chatId
2032
+ })
2033
+ };
2034
+ }
2035
+ if (!ports.createGroupChat) {
2036
+ return { ok: false, action, error: "ports.createGroupChat not injected" };
2037
+ }
2038
+ try {
2039
+ let title;
2040
+ try {
2041
+ const t = await ports.getSessionTitle?.(sessionId);
2042
+ if (typeof t === "string" && t.trim())
2043
+ title = t.trim();
2044
+ } catch (err) {
2045
+ logger6.warn(`handleGroupActionSessionOnly: getSessionTitle(${sessionId}) 失败(用 sessionId 兜底): ` + `${err.message}`);
2046
+ }
2047
+ const renderedName = truncateText(`会话 ${title ?? sessionId}`, 60);
2048
+ const description = truncateText(`roy-agent session ${sessionId}`, 100);
2049
+ const operatorOpenId = extractOperatorOpenId(event.operatorId);
2050
+ const initialUserIds = operatorOpenId ? [operatorOpenId] : undefined;
2051
+ let initialBotIds;
2052
+ try {
2053
+ const botAppId = await resolveBotAppId(event.profile);
2054
+ if (botAppId)
2055
+ initialBotIds = [botAppId];
2056
+ } catch (err) {
2057
+ logger6.warn(`handleGroupActionSessionOnly: resolveBotAppId 失败` + `(bot 可能不在群里,发卡会 230002): ${err.message}`);
2058
+ }
2059
+ const chatId = await ports.createGroupChat({
2060
+ name: renderedName,
2061
+ description,
2062
+ profile: event.profile,
2063
+ initialUserIds,
2064
+ initialBotIds
2065
+ });
2066
+ if (operatorOpenId && !initialUserIds && ports.inviteUser) {
2067
+ try {
2068
+ await ports.inviteUser(chatId, operatorOpenId, event.profile);
2069
+ } catch (err) {
2070
+ logger6.warn(`handleGroupActionSessionOnly: inviteUser(${chatId}, ${operatorOpenId}) 失败` + `(不阻断主流程): ${err.message}`);
2071
+ }
2072
+ }
2073
+ await attachGroupSessionForSession(ports, chatId, sessionId, renderedName);
2074
+ if (ports.notifier?.upsertSessionCard) {
2075
+ try {
2076
+ const cardRes = await ports.notifier.upsertSessionCard({
2077
+ chatId,
2078
+ sessionId: chatId,
2079
+ title: renderedName,
2080
+ lifecycle: "running",
2081
+ perTaskChatId: chatId
2082
+ });
2083
+ logger6.info(`handleGroupActionSessionOnly: 新群卡已发 chatId=${chatId} ` + `messageId=${cardRes.messageId}(session_id=群 session,#5417)`);
2084
+ } catch (err) {
2085
+ logger6.warn(`handleGroupActionSessionOnly: 新群卡发送失败(不阻断建群): ` + `${err.message}`);
2086
+ }
2087
+ }
2088
+ return {
2089
+ ok: true,
2090
+ action,
2091
+ newChatId: chatId,
2092
+ cardUpdate: buildCardAfterSessionGroupChange(event, ports, {
2093
+ sessionId,
2094
+ chatId,
2095
+ chatName: renderedName
2096
+ })
2097
+ };
2098
+ } catch (err) {
2099
+ logger6.error(`handleCardAction.create_group(session-only) failed: ${err.message}`);
2100
+ return { ok: false, action, error: err.message };
2101
+ }
2102
+ }
2103
+ if (!existing?.chatId) {
2104
+ return {
2105
+ ok: false,
2106
+ action,
2107
+ error: `会话 ${sessionId} 没有专属群,无需解散`
2108
+ };
2109
+ }
2110
+ if (!ports.deleteGroupChat) {
2111
+ return { ok: false, action, error: "ports.deleteGroupChat not injected" };
2112
+ }
2113
+ try {
2114
+ await ports.deleteGroupChat(existing.chatId, event.profile);
2115
+ if (!ports.detachGroupSession) {
2116
+ logger6.warn(`handleGroupActionSessionOnly: detachGroupSession 端口未注入 ⇒ 群 ${existing.chatId} ` + `已解散但关联标记残留(反查仍会命中这个群)`);
2117
+ } else {
2118
+ const unlinkTarget = existing.aliasSessionId ?? existing.chatId;
2119
+ try {
2120
+ await ports.detachGroupSession(unlinkTarget);
2121
+ } catch (err) {
2122
+ logger6.warn(`handleGroupActionSessionOnly: detachGroupSession(${unlinkTarget}) 失败` + `(不阻断解散): ${err.message}`);
2123
+ }
2124
+ }
2125
+ return {
2126
+ ok: true,
2127
+ action,
2128
+ cardUpdate: buildCardAfterSessionGroupChange(event, ports, { sessionId })
2129
+ };
2130
+ } catch (err) {
2131
+ logger6.error(`handleCardAction.disband_group(session-only) failed: ${err.message}`);
2132
+ return { ok: false, action, error: err.message };
2133
+ }
2134
+ }
2135
+ async function lookupSessionGroup(ports, sessionId) {
2136
+ if (!ports.findGroupChatForSession)
2137
+ return;
2138
+ try {
2139
+ const r = await ports.findGroupChatForSession(sessionId);
2140
+ if (r && typeof r.chatId === "string" && r.chatId.trim()) {
2141
+ return { chatId: r.chatId.trim(), ...r.aliasSessionId ? { aliasSessionId: r.aliasSessionId } : {} };
2142
+ }
2143
+ return;
2144
+ } catch (err) {
2145
+ logger6.warn(`lookupSessionGroup(${sessionId}) 失败(当作没有群): ${err.message}`);
2146
+ return;
2147
+ }
2148
+ }
2149
+ async function attachGroupSessionForSession(ports, chatId, sessionId, title) {
2150
+ if (!ports.attachGroupSession) {
2151
+ logger6.warn(`attachGroupSessionForSession: 端口未注入 ⇒ 群 ${chatId} 将没有背景提示事件` + `(连不到 ${sessionId} 的溯源信息)`);
2152
+ return;
2153
+ }
2154
+ try {
2155
+ await ports.attachGroupSession({
2156
+ chatId,
2157
+ originSessionId: sessionId,
2158
+ ...title ? { title } : {}
2159
+ });
2160
+ logger6.info(`attachGroupSessionForSession: ${chatId} ← ${sessionId}` + `(已注入 1 条 user 事件作为背景提示)`);
2161
+ } catch (err) {
2162
+ logger6.warn(`attachGroupSessionForSession: attach(${chatId} ← ${sessionId}) 失败(不阻断建群): ` + `${err.message}`);
2163
+ }
2164
+ }
2165
+ function buildCardAfterSessionGroupChange(event, ports, opts) {
2166
+ const preserved = ports.notifier?.applySessionGroupChange?.(event.messageId, {
2167
+ sessionId: opts.sessionId,
2168
+ ...opts.chatId ? { perTaskChatId: opts.chatId } : {},
2169
+ ...opts.chatName ? { perTaskChatName: opts.chatName } : {}
2170
+ });
2171
+ if (preserved)
2172
+ return preserved;
2173
+ logger6.warn(`buildCardAfterSessionGroupChange: notifier 无 messageId=${event.messageId} 的状态,` + `fallback 重建 session 卡(历史里程碑可能丢失)`);
2174
+ return buildSessionCard({
2175
+ sessionId: opts.sessionId,
2176
+ ...opts.chatId ? { perTaskChatId: opts.chatId } : {},
2177
+ ...opts.chatName ? { perTaskChatName: opts.chatName } : {}
2178
+ });
2179
+ }
2180
+ async function handleCardActionInner(event, ports, action, taskId) {
2181
+ if (!ports.getTask) {
2182
+ return { ok: false, action, error: "ports.getTask not injected" };
2183
+ }
2184
+ const task = await ports.getTask(taskId);
2185
+ if (!task) {
2186
+ return { ok: false, action, error: `Task #${taskId} not found` };
2187
+ }
2188
+ const existingChatId = extractPerTaskChatId(task.context);
2189
+ switch (action) {
2190
+ case "create_group":
2191
+ return await handleCreateGroup(event, ports, task, existingChatId);
2192
+ case "disband_group":
2193
+ return await handleDisbandGroup(event, ports, task, existingChatId);
2194
+ }
2195
+ }
2196
+ function buildCardAfterGroupChange(event, ports, task, perTaskChatId, perTaskChatName) {
2197
+ const meta = buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName);
2198
+ const preserved = ports.notifier?.updateMetaAndBuildCard?.(event.messageId, meta, { clearPerTaskChat: !perTaskChatId });
2199
+ if (preserved) {
2200
+ logger6.debug(`buildCardAfterGroupChange: 复用 notifier 累积状态(里程碑历史保留)messageId=${event.messageId}`);
2201
+ return preserved;
2202
+ }
2203
+ logger6.warn(`buildCardAfterGroupChange: notifier 无 messageId=${event.messageId} 的状态,` + `fallback 重建卡片(该卡片的历史里程碑可能丢失)`);
2204
+ return buildTaskCard({
2205
+ status: "running",
2206
+ meta,
2207
+ milestones: [
2208
+ { title: "Task 创建", timestamp: new Date().toISOString(), description: task.current_status }
2209
+ ]
2210
+ });
2211
+ }
2212
+ async function handleCreateGroup(event, ports, task, existingChatId) {
2213
+ if (existingChatId) {
2214
+ const existingName = extractPerTaskChatName(task.context);
2215
+ logger6.info(`handleCreateGroup: Task #${task.id} 已有专属群 ${existingChatId}(幂等命中,不重复建群)`);
2216
+ await attachGroupSessionForTask(event, ports, task.id, existingChatId, task.context);
2217
+ return {
2218
+ ok: true,
2219
+ action: "create_group",
2220
+ newChatId: existingChatId,
2221
+ cardUpdate: buildCardAfterGroupChange(event, ports, task, existingChatId, existingName)
2222
+ };
2223
+ }
2224
+ if (!ports.createGroupChat) {
2225
+ return { ok: false, action: "create_group", error: "ports.createGroupChat not injected" };
2226
+ }
2227
+ try {
2228
+ const renderedName = truncateText(`Task #${task.id} - ${task.title ?? "(无标题)"}`, 60);
2229
+ const description = truncateText(`roy-agent task #${task.id} - ${task.project_path ?? ""}`, 100);
2230
+ const operatorOpenId = extractOperatorOpenId(event.operatorId);
2231
+ const initialUserIds = operatorOpenId ? [operatorOpenId] : undefined;
2232
+ const chatId = await ports.createGroupChat({
2233
+ name: renderedName,
2234
+ description,
2235
+ profile: event.profile,
2236
+ initialUserIds
2237
+ });
2238
+ let invitedOpenIds = [];
2239
+ if (operatorOpenId) {
2240
+ if (initialUserIds) {
2241
+ invitedOpenIds = [operatorOpenId];
2242
+ logger6.debug(`handleCreateGroup: ${operatorOpenId} 已在建群时邀请(initialUserIds 路径)`);
2243
+ } else if (ports.inviteUser) {
2244
+ try {
2245
+ await ports.inviteUser(chatId, operatorOpenId, event.profile);
2246
+ invitedOpenIds = [operatorOpenId];
2247
+ logger6.debug(`handleCreateGroup: 走 inviteUser 端口邀请 ${operatorOpenId} 进 ${chatId}`);
2248
+ } catch (err) {
2249
+ logger6.warn(`handleCreateGroup: inviteUser(${chatId}, ${operatorOpenId}) 失败(不阻断主流程): ${err.message}`);
2250
+ }
2251
+ } else {
2252
+ logger6.warn(`handleCreateGroup: createGroupChat 不支持 initialUserIds 且 inviteUser 未注入,` + `${operatorOpenId} 未被拉进群 ${chatId}。` + `需升级 caller(bridge/real impl)使用 ports.inviteUser 或实现 createGroupChat.initialUserIds 参数。`);
2253
+ }
2254
+ }
2255
+ const newContext = patchContext(task.context ?? "{}", {
2256
+ [CTX_FIELD_CHAT_ID]: chatId,
2257
+ [CTX_FIELD_CHAT_NAME]: renderedName,
2258
+ [CTX_FIELD_CHAT_CREATED_AT]: new Date().toISOString(),
2259
+ [CTX_FIELD_CHAT_TASK_ID]: task.id,
2260
+ [CTX_FIELD_CHAT_INVITEES]: invitedOpenIds.length > 0 ? invitedOpenIds : undefined
2261
+ });
2262
+ if (ports.updateTaskContext) {
2263
+ await ports.updateTaskContext(task.id, newContext);
2264
+ }
2265
+ const cardUpdate = buildCardAfterGroupChange(event, ports, task, chatId, renderedName);
2266
+ await attachGroupSessionForTask(event, ports, task.id, chatId, task.context);
2267
+ return { ok: true, action: "create_group", newChatId: chatId, cardUpdate };
2268
+ } catch (err) {
2269
+ logger6.error(`handleCardAction.create_group failed: ${err.message}`);
2270
+ return { ok: false, action: "create_group", error: err.message };
2271
+ }
2272
+ }
2273
+ function extractOperatorOpenId(raw) {
2274
+ if (!raw)
2275
+ return;
2276
+ if (typeof raw === "string")
2277
+ return raw;
2278
+ if (typeof raw === "object" && typeof raw.open_id === "string")
2279
+ return raw.open_id;
2280
+ return;
2281
+ }
2282
+ function parseTaskIdLoose(raw) {
2283
+ if (typeof raw === "number") {
2284
+ return Number.isSafeInteger(raw) && raw > 0 ? raw : undefined;
2285
+ }
2286
+ if (typeof raw === "string" && /^\d+$/.test(raw.trim())) {
2287
+ const n = Number(raw.trim());
2288
+ return Number.isSafeInteger(n) && n > 0 ? n : undefined;
2289
+ }
2290
+ return;
2291
+ }
2292
+ async function handleRefreshSessionActivity(event, ports, subject) {
2293
+ const action = "refresh_activity";
2294
+ const sessionId = subject.sessionId;
2295
+ let probe;
2296
+ try {
2297
+ probe = await ports.loadSessionActivity?.(sessionId);
2298
+ } catch (err) {
2299
+ logger6.warn(`handleRefreshSessionActivity: loadSessionActivity failed for ${sessionId}: ` + `${err.message}(降级为「暂无数据」)`);
2300
+ probe = undefined;
2301
+ }
2302
+ let taskEnrichment;
2303
+ if (subject.taskId !== undefined) {
2304
+ taskEnrichment = await loadTaskEnrichment(subject.taskId, ports);
2305
+ }
2306
+ let recentMessages;
2307
+ try {
2308
+ recentMessages = await ports.loadSessionMessages?.(sessionId, RECENT_MESSAGE_LIMIT);
2309
+ } catch (err) {
2310
+ logger6.warn(`handleRefreshSessionActivity: loadSessionMessages failed for ${sessionId}: ` + `${err.message}(降级为不渲染最近消息面板)`);
2311
+ recentMessages = undefined;
2312
+ }
2313
+ const snapshot = probe?.activity ?? null;
2314
+ const sessionAlive = probe?.activity !== undefined && !probe?.terminal;
2315
+ const lifecycle = probe?.lifecycle ? probe.lifecycle : probe?.terminal ? "completed" : sessionAlive ? "running" : taskEnrichment?.lifecycle ?? "running";
2316
+ const preserved = ports.notifier?.buildSessionCardWithActivity?.(event.messageId, sessionId, snapshot, {
2317
+ lifecycle,
2318
+ task: taskEnrichment?.info,
2319
+ ...recentMessages !== undefined ? { recentMessages } : {}
2320
+ });
2321
+ if (preserved) {
2322
+ logger6.debug(`handleRefreshSessionActivity: ${sessionId} 活性已刷新 ` + `(messageCount=${snapshot?.messageCount ?? "n/a"}, task=${subject.taskId ?? "none"})`);
2323
+ return { ok: true, action, cardUpdate: preserved };
2324
+ }
2325
+ logger6.warn(`handleRefreshSessionActivity: notifier 无 messageId=${event.messageId} 的状态,` + `fallback 重建 session 卡`);
2326
+ const groupChat = taskEnrichment?.info ? undefined : await lookupSessionGroup(ports, sessionId);
2327
+ return {
2328
+ ok: true,
2329
+ action,
2330
+ cardUpdate: buildSessionCard({
2331
+ sessionId,
2332
+ activity: snapshot,
2333
+ lifecycle,
2334
+ ...taskEnrichment?.info ? { task: taskEnrichment.info } : {},
2335
+ ...groupChat?.chatId ? { perTaskChatId: groupChat.chatId } : {},
2336
+ ...recentMessages !== undefined ? { recentMessages } : {}
2337
+ })
2338
+ };
2339
+ }
2340
+ async function loadTaskEnrichment(taskId, ports) {
2341
+ try {
2342
+ const task = await ports.getTask?.(taskId);
2343
+ if (!task)
2344
+ return;
2345
+ const raw = task;
2346
+ return {
2347
+ lifecycle: lifecycleOfTaskStatus(raw.status),
2348
+ info: {
2349
+ id: typeof raw.id === "number" ? raw.id : taskId,
2350
+ ...raw.title ? { title: raw.title } : {},
2351
+ ...raw.status ? { status: raw.status } : {},
2352
+ ...typeof raw.progress === "number" ? { progress: raw.progress } : {},
2353
+ ...raw.current_status ? { currentStatus: raw.current_status } : {},
2354
+ ...Array.isArray(raw.milestones) && raw.milestones.length > 0 ? { milestones: raw.milestones } : {},
2355
+ ...raw.priority ? { priority: raw.priority } : {},
2356
+ ...raw.goals_and_expected_deliverables || raw.goals ? { goals: raw.goals_and_expected_deliverables ?? raw.goals } : {},
2357
+ ...typeof raw.parent_task_id === "number" && raw.parent_task_id > 0 ? { parentTaskId: raw.parent_task_id } : {},
2358
+ ...raw.created_at || raw.createdAt ? { createdAt: raw.created_at ?? raw.createdAt } : {},
2359
+ ...raw.updated_at || raw.updatedAt ? { updatedAt: raw.updated_at ?? raw.updatedAt } : {}
2360
+ }
2361
+ };
2362
+ } catch (err) {
2363
+ logger6.warn(`loadTaskEnrichment: task #${taskId} 读取失败(降级为无 task 信息): ` + `${err.message}`);
2364
+ return;
2365
+ }
2366
+ }
2367
+ function lifecycleOfTaskStatus(status) {
2368
+ switch (status) {
2369
+ case "completed":
2370
+ return "completed";
2371
+ case "failed":
2372
+ return "failed";
2373
+ case "cancelled":
2374
+ case "archived":
2375
+ case "paused":
2376
+ return "stopped";
2377
+ case "active":
2378
+ case "todo":
2379
+ case "to_validate":
2380
+ return "running";
2381
+ default:
2382
+ return;
2383
+ }
2384
+ }
2385
+ async function handleRefreshActivity(event, ports, task, taskId) {
2386
+ const action = "refresh_activity";
2387
+ let activity;
2388
+ try {
2389
+ activity = await ports.loadSubSessionActivity?.(taskId);
2390
+ } catch (err) {
2391
+ logger6.warn(`handleRefreshActivity: loadSubSessionActivity failed for task #${taskId}: ` + `${err.message}(降级为「暂无数据」)`);
2392
+ activity = undefined;
2393
+ }
2394
+ const snapshot = activity?.activity ?? null;
2395
+ const terminal = activity?.terminal ?? false;
2396
+ const preserved = ports.notifier?.buildCardWithActivity?.(event.messageId, snapshot, {
2397
+ terminal
2398
+ });
2399
+ if (preserved) {
2400
+ logger6.debug(`handleRefreshActivity: task #${taskId} 活性已刷新 ` + `(messageCount=${snapshot?.messageCount ?? "n/a"}, terminal=${terminal})`);
2401
+ return { ok: true, action, cardUpdate: preserved };
2402
+ }
2403
+ logger6.warn(`handleRefreshActivity: notifier 无 messageId=${event.messageId} 的状态,` + `fallback 重建卡片(历史里程碑可能丢失)`);
2404
+ const meta = buildTaskMetaFromTask(task, extractPerTaskChatId(task.context), undefined);
2405
+ return {
2406
+ ok: true,
2407
+ action,
2408
+ cardUpdate: buildTaskCard({
2409
+ status: cardStatusOfTaskStatus(task.status),
2410
+ meta,
2411
+ activity: snapshot,
2412
+ activityTerminal: terminal
2413
+ })
2414
+ };
2415
+ }
2416
+ function cardStatusOfTaskStatus(status) {
2417
+ switch (status) {
2418
+ case "completed":
2419
+ return "completed";
2420
+ case "cancelled":
2421
+ return "cancelled";
2422
+ case "failed":
2423
+ return "failed";
2424
+ case "paused":
2425
+ return "stopped";
2426
+ case "archived":
2427
+ return "stopped";
2428
+ default:
2429
+ return "running";
2430
+ }
2431
+ }
2432
+ async function handleDisbandGroup(event, ports, task, existingChatId) {
2433
+ if (!existingChatId) {
2434
+ return {
2435
+ ok: false,
2436
+ action: "disband_group",
2437
+ error: `Task #${task.id} 无专属群(context.larkPerTaskChatId 空),无需解散`
2438
+ };
2439
+ }
2440
+ if (!ports.deleteGroupChat) {
2441
+ return { ok: false, action: "disband_group", error: "ports.deleteGroupChat not injected" };
2442
+ }
2443
+ try {
2444
+ await ports.deleteGroupChat(existingChatId, event.profile);
2445
+ const newContext = patchContext(task.context ?? "{}", {
2446
+ [CTX_FIELD_CHAT_ID]: undefined,
2447
+ [CTX_FIELD_CHAT_NAME]: undefined,
2448
+ [CTX_FIELD_CHAT_CREATED_AT]: undefined,
2449
+ [CTX_FIELD_CHAT_TASK_ID]: undefined
2450
+ });
2451
+ if (ports.updateTaskContext) {
2452
+ await ports.updateTaskContext(task.id, newContext);
2453
+ }
2454
+ const cardUpdate = buildCardAfterGroupChange(event, ports, task, undefined, undefined);
2455
+ return { ok: true, action: "disband_group", cardUpdate };
2456
+ } catch (err) {
2457
+ logger6.error(`handleCardAction.disband_group failed: ${err.message}`);
2458
+ return { ok: false, action: "disband_group", error: err.message };
2459
+ }
2460
+ }
2461
+ function patchContext(contextStr, changes) {
2462
+ let obj = {};
2463
+ try {
2464
+ const parsed = JSON.parse(contextStr);
2465
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2466
+ obj = parsed;
2467
+ }
2468
+ } catch {
2469
+ obj = {};
2470
+ }
2471
+ for (const [k, v] of Object.entries(changes)) {
2472
+ if (v === undefined) {
2473
+ delete obj[k];
2474
+ } else {
2475
+ obj[k] = v;
2476
+ }
2477
+ }
2478
+ return JSON.stringify(obj);
2479
+ }
2480
+ function buildTaskMetaFromTask(task, perTaskChatId, perTaskChatName) {
2481
+ return {
2482
+ id: task.id,
2483
+ title: task.title,
2484
+ description: task.description,
2485
+ goals: task.goals,
2486
+ parentTaskId: typeof task.parent_task_id === "number" && task.parent_task_id > 0 ? task.parent_task_id : undefined,
2487
+ priority: task.priority,
2488
+ type: task.type,
2489
+ projectPath: task.project_path,
2490
+ context: task.context,
2491
+ currentStatus: task.current_status,
2492
+ perTaskChatId,
2493
+ perTaskChatName
2494
+ };
2495
+ }
2496
+ async function deleteGroupChatViaLarkCli(chatId, profile) {
2497
+ const profileFlag = profile ? `--profile ${profile} ` : "";
2498
+ try {
2499
+ const { stdout: authStdout } = await execAsync(`lark-cli ${profileFlag}auth status --json`, { timeout: 15000 });
2500
+ const appId = JSON.parse(authStdout).appId;
2501
+ if (!appId) {
2502
+ throw new Error(`lark-cli auth status 未返回 appId: ${authStdout.slice(0, 200)}`);
2503
+ }
2504
+ 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`;
2505
+ const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
2506
+ logger6.debug(`deleteGroupChatViaLarkCli(${chatId}) bot 退群 ok: ` + `stdout=${stdout?.slice(0, 300)} stderr=${stderr?.slice(0, 300)}`);
2507
+ } catch (err) {
2508
+ logger6.warn(`deleteGroupChatViaLarkCli(${chatId}) bot 退群失败(task.context 仍会清掉,` + `飞书侧群会保留):${err.message}`);
2509
+ }
2510
+ }
2511
+ function shellQuote(s) {
2512
+ return `'${s.replace(/'/g, `'\\''`)}'`;
2513
+ }
2514
+ async function resolveBotAppId(profile) {
2515
+ const profileFlag = profile ? `--profile ${profile} ` : "";
2516
+ const { stdout } = await execAsync(`lark-cli ${profileFlag}auth status --json`, { timeout: 15000 });
2517
+ const appId = JSON.parse(stdout).appId;
2518
+ if (!appId) {
2519
+ throw new Error(`lark-cli auth status 未返回 appId: ${stdout.slice(0, 200)}`);
2520
+ }
2521
+ return appId;
2522
+ }
2523
+ function truncateText(text, maxLen) {
2524
+ if (!text)
2525
+ return "";
2526
+ if (text.length <= maxLen)
2527
+ return text;
2528
+ return text.substring(0, Math.max(0, maxLen - 3)) + "...";
2529
+ }
2530
+ async function createGroupChatViaLarkCli(opts) {
2531
+ const profileFlag = opts.profile ? `--profile ${opts.profile} ` : "";
2532
+ const usersFlag = opts.initialUserIds && opts.initialUserIds.length > 0 ? `--users ${shellQuote(opts.initialUserIds.join(","))} ` : "";
2533
+ const botsFlag = opts.initialBotIds && opts.initialBotIds.length > 0 ? `--bots ${shellQuote(opts.initialBotIds.join(","))} ` : "";
2534
+ const cmd = `lark-cli ${profileFlag}im +chat-create --name ${shellQuote(truncateText(opts.name, 60))} ` + usersFlag + botsFlag + `--description ${shellQuote(truncateText(opts.description, 100))} --as bot`;
2535
+ const { stdout } = await execAsync(cmd, { timeout: 60000 });
2536
+ const m = stdout.match(/"chat_id"\s*:\s*"(oc_[a-f0-9]+)"/);
2537
+ if (!m) {
2538
+ throw new Error(`lark-cli +chat-create 返回无法解析: ${stdout.slice(0, 300)}`);
2539
+ }
2540
+ return m[1];
2541
+ }
2542
+
1936
2543
  // src/env/task/plugins/lark-cli-task-notify-plugin.ts
1937
2544
  var logger7 = createLogger("lark-cli:task-notify");
1938
- var CTX_FIELD_CHAT_ID = "larkPerTaskChatId";
2545
+ var CTX_FIELD_CHAT_ID2 = "larkPerTaskChatId";
1939
2546
  var CTX_FIELD_CHAT_PROFILE = "larkPerTaskChatProfile";
1940
2547
  function parseLarkPerTaskChatId(ctxStr) {
1941
2548
  if (!ctxStr)
1942
2549
  return;
1943
2550
  try {
1944
2551
  const obj = JSON.parse(ctxStr);
1945
- const id = obj[CTX_FIELD_CHAT_ID];
2552
+ const id = obj[CTX_FIELD_CHAT_ID2];
1946
2553
  return typeof id === "string" ? id : undefined;
1947
2554
  } catch {
1948
2555
  return;
@@ -2019,7 +2626,8 @@ class LarkCliTaskNotifyHook {
2019
2626
  ...config
2020
2627
  };
2021
2628
  if (this.config.cardMode !== false) {
2022
- this.cardNotifier = config.cardNotifier ?? new LarkCardPatchNotifier;
2629
+ const isTestEnv = "BUN_TEST_WORKER_ID" in process.env;
2630
+ this.cardNotifier = config.cardNotifier ?? (isTestEnv ? new LarkCardPatchNotifier : getSharedLarkCardNotifier());
2023
2631
  this.cardStateStore = config.cardStateStore ?? createLarkCardStateStore();
2024
2632
  }
2025
2633
  }
@@ -2061,11 +2669,29 @@ class LarkCliTaskNotifyHook {
2061
2669
  }
2062
2670
  }
2063
2671
  async onAfterCreate(ctx) {
2064
- const { task } = ctx.data;
2672
+ const { task, sessionId: ctxSessionId } = ctx.data;
2065
2673
  if (!task || !this.shouldNotify("task_created" /* TASK_CREATED */))
2066
2674
  return;
2067
2675
  if (!this.isCardModeEnabled())
2068
2676
  return;
2677
+ const sessionId = typeof ctxSessionId === "string" && ctxSessionId.trim() ? ctxSessionId.trim() : this.resolveSessionIdForTask(task.id);
2678
+ if (sessionId) {
2679
+ const merged = await this.patchSessionCardForTask(task, {
2680
+ sessionId,
2681
+ lifecycle: "running",
2682
+ milestones: [
2683
+ {
2684
+ title: "Task 创建",
2685
+ timestamp: new Date().toISOString(),
2686
+ description: task.current_status || undefined
2687
+ }
2688
+ ]
2689
+ });
2690
+ if (merged) {
2691
+ logger7.debug(`onAfterCreate: task #${task.id} 已渲染进 session 卡 ${sessionId}(单卡语义)`);
2692
+ return;
2693
+ }
2694
+ }
2069
2695
  await this.sendCardStart(task);
2070
2696
  }
2071
2697
  async onAfterUpdate(ctx) {
@@ -2080,6 +2706,22 @@ class LarkCliTaskNotifyHook {
2080
2706
  return;
2081
2707
  if (!this.isCardModeEnabled())
2082
2708
  return;
2709
+ const sessionIdTerminal = this.resolveSessionIdForTask(task.id);
2710
+ if (sessionIdTerminal) {
2711
+ const mergedTerminal = await this.patchSessionCardForTask(task, {
2712
+ sessionId: sessionIdTerminal,
2713
+ lifecycle: lifecycleOfTaskStatus(String(task.status)) ?? "running",
2714
+ milestones: [
2715
+ {
2716
+ title: terminalCardStatus === "completed" ? "任务已完成" : "任务状态更新",
2717
+ timestamp: new Date().toISOString(),
2718
+ description: task.current_status || undefined
2719
+ }
2720
+ ]
2721
+ });
2722
+ if (mergedTerminal)
2723
+ return;
2724
+ }
2083
2725
  await this.sendCardFinish(task);
2084
2726
  return;
2085
2727
  }
@@ -2095,6 +2737,15 @@ class LarkCliTaskNotifyHook {
2095
2737
  return;
2096
2738
  if (!this.isCardModeEnabled())
2097
2739
  return;
2740
+ const sessionIdUpdate = this.resolveSessionIdForTask(task.id);
2741
+ if (sessionIdUpdate) {
2742
+ const mergedUpdate = await this.patchSessionCardForTask(task, {
2743
+ sessionId: sessionIdUpdate,
2744
+ lifecycle: lifecycleOfTaskStatus(String(task.status)) ?? "running"
2745
+ });
2746
+ if (mergedUpdate)
2747
+ return;
2748
+ }
2098
2749
  await this.sendCardUpdate(task, changes);
2099
2750
  }
2100
2751
  cardStatusOf(task, changes) {
@@ -2262,6 +2913,12 @@ class LarkCliTaskNotifyHook {
2262
2913
  const rec = this.cardStateStore.get(taskId);
2263
2914
  if (!rec)
2264
2915
  return;
2916
+ if (rec.sessionId) {
2917
+ this.cardNotifier?.bindTaskToSession?.(taskId, rec.sessionId);
2918
+ if (rec.messageId) {
2919
+ this.cardNotifier?.adoptSessionCard?.(rec.sessionId, rec.messageId);
2920
+ }
2921
+ }
2265
2922
  if (!this.restoredTaskIds.has(taskId)) {
2266
2923
  this.restoredTaskIds.add(taskId);
2267
2924
  if (rec.status !== "closed" && rec.messageId) {
@@ -2460,6 +3117,7 @@ class LarkCliTaskNotifyHook {
2460
3117
  logger7.warn(`buildCardState: read existing failed for task #${task.id}: ${err.message}`);
2461
3118
  }
2462
3119
  const now = Date.now();
3120
+ const sessionId = this.resolveSessionIdForTask(task.id);
2463
3121
  return {
2464
3122
  taskId: task.id,
2465
3123
  messageId,
@@ -2467,6 +3125,7 @@ class LarkCliTaskNotifyHook {
2467
3125
  profile,
2468
3126
  pinned,
2469
3127
  status: "open",
3128
+ ...sessionId ? { sessionId } : {},
2470
3129
  createdAt: existing?.createdAt ?? now,
2471
3130
  updatedAt: now
2472
3131
  };
@@ -2756,6 +3415,7 @@ class LarkCliTaskNotifyHook {
2756
3415
  return;
2757
3416
  }
2758
3417
  buildTaskMeta(task) {
3418
+ const sessionId = this.resolveSessionIdForTask(task.id);
2759
3419
  const recordedNames = extractRecordedChatNames(task.context);
2760
3420
  const matchedName = recordedNames.find((n) => new RegExp(`(?:Task\\s*#?\\s*|#)(?<![\\d])(?<!\\d)${task.id}(?!\\d)(?![\\d])`, "i").test(n));
2761
3421
  return {
@@ -2770,7 +3430,101 @@ class LarkCliTaskNotifyHook {
2770
3430
  context: task.context,
2771
3431
  currentStatus: task.current_status,
2772
3432
  perTaskChatId: extractPerTaskChatId(task.context),
2773
- perTaskChatName: matchedName
3433
+ perTaskChatName: matchedName,
3434
+ ...sessionId ? { sessionId } : {}
3435
+ };
3436
+ }
3437
+ resolveSessionIdForTask(taskId) {
3438
+ const fromIndex = this.cardNotifier?.getSessionIdByTask?.(taskId);
3439
+ if (fromIndex)
3440
+ return fromIndex;
3441
+ try {
3442
+ const rec = this.cardStateStore?.get(taskId);
3443
+ if (rec?.sessionId)
3444
+ return rec.sessionId;
3445
+ } catch {}
3446
+ const ctx = getEnvContextOrEmpty();
3447
+ const fromCtx = ctx?.sessionId ?? ctx?.replyChannel?.sessionId;
3448
+ return typeof fromCtx === "string" && fromCtx ? fromCtx : undefined;
3449
+ }
3450
+ async patchSessionCardForTask(task, opts) {
3451
+ if (!this.cardNotifier)
3452
+ return false;
3453
+ const meta = this.buildTaskMeta(task);
3454
+ const sessionId = opts.sessionId;
3455
+ const chatId = meta.perTaskChatId ?? this.resolveChatId(task, getEnvContextOrEmpty());
3456
+ if (!chatId)
3457
+ return false;
3458
+ const profile = this.resolveProfile(task, getEnvContextOrEmpty());
3459
+ const agentType = this.resolveAgentType();
3460
+ const res = await this.cardNotifier.upsertSessionCard({
3461
+ chatId,
3462
+ ...profile ? { profile } : {},
3463
+ sessionId,
3464
+ ...task.title ? { title: task.title } : {},
3465
+ ...opts.statusPhrase ? { statusPhrase: opts.statusPhrase } : {},
3466
+ ...agentType ? { agentType } : {},
3467
+ startedAt: task.createdAt ? String(task.createdAt) : new Date().toISOString(),
3468
+ ...opts.milestones ? { milestones: opts.milestones } : {},
3469
+ lifecycle: opts.lifecycle,
3470
+ task: this.buildSessionCardTask(task, meta),
3471
+ ...meta.perTaskChatId ? { perTaskChatId: meta.perTaskChatId } : {},
3472
+ ...meta.perTaskChatName ? { perTaskChatName: meta.perTaskChatName } : {}
3473
+ });
3474
+ this.recordSessionCardForTask(task, sessionId, chatId, profile, res.messageId);
3475
+ if (this.syncGroupCardFromOriginFn) {
3476
+ await this.cardNotifier.syncGroupCardFromOrigin(sessionId, {
3477
+ lifecycle: opts.lifecycle,
3478
+ ...task.title ? { title: task.title } : {},
3479
+ ...opts.milestones ? { milestones: opts.milestones } : {}
3480
+ }, this.syncGroupCardFromOriginFn);
3481
+ }
3482
+ return true;
3483
+ }
3484
+ syncGroupCardFromOriginFn;
3485
+ recordSessionCardForTask(task, sessionId, chatId, profile, messageIdIn) {
3486
+ this.cardNotifier?.bindTaskToSession?.(task.id, sessionId);
3487
+ if (!this.cardStateStore)
3488
+ return;
3489
+ const messageId = messageIdIn ?? this.cardNotifier?.getSessionCardMessageId?.(sessionId);
3490
+ if (!messageId)
3491
+ return;
3492
+ this.cardMessageIds.set(task.id, messageId);
3493
+ try {
3494
+ const existing = this.cardStateStore.get(task.id);
3495
+ const now = Date.now();
3496
+ this.cardStateStore.upsert({
3497
+ taskId: task.id,
3498
+ messageId,
3499
+ chatId: chatId ?? existing?.chatId ?? "",
3500
+ ...profile ?? existing?.profile ? { profile: profile ?? existing?.profile } : {},
3501
+ pinned: existing?.pinned ?? true,
3502
+ status: existing?.status ?? "open",
3503
+ createdAt: existing?.createdAt ?? now,
3504
+ updatedAt: now,
3505
+ sessionId,
3506
+ ...existing?.timeline ? { timeline: existing.timeline } : {}
3507
+ });
3508
+ } catch (err) {
3509
+ logger7.warn(`recordSessionCardForTask failed for task #${task.id}: ${err.message}`);
3510
+ }
3511
+ }
3512
+ resolveAgentType() {
3513
+ const ctx = getEnvContextOrEmpty();
3514
+ return typeof ctx?.subagentType === "string" && ctx.subagentType ? ctx.subagentType : undefined;
3515
+ }
3516
+ buildSessionCardTask(task, meta) {
3517
+ return {
3518
+ id: task.id,
3519
+ ...task.title ? { title: task.title } : {},
3520
+ ...task.status ? { status: String(task.status) } : {},
3521
+ ...typeof task.progress === "number" ? { progress: task.progress } : {},
3522
+ ...task.current_status ? { currentStatus: task.current_status } : {},
3523
+ ...meta.priority ? { priority: meta.priority } : {},
3524
+ ...typeof meta.parentTaskId === "number" ? { parentTaskId: meta.parentTaskId } : {},
3525
+ ...meta.goals ? { goals: meta.goals } : {},
3526
+ ...task.createdAt ? { createdAt: String(task.createdAt) } : {},
3527
+ ...task.updatedAt ? { updatedAt: String(task.updatedAt) } : {}
2774
3528
  };
2775
3529
  }
2776
3530
  deriveTimelineFromOperation(operation) {
@@ -2850,7 +3604,7 @@ init_env_context();
2850
3604
  // src/env/task/plugins/wecom-card-state-store.ts
2851
3605
  init_logger();
2852
3606
  init_sqlite_task_store();
2853
- import { join as join2 } from "path";
3607
+ import { join } from "path";
2854
3608
  var logger8 = createLogger("wecom:card-state");
2855
3609
 
2856
3610
  class MemoryWecomCardStateStore {
@@ -2899,7 +3653,7 @@ class MemoryWecomCardStateStore {
2899
3653
  function createMemoryWecomCardStore() {
2900
3654
  return new MemoryWecomCardStateStore;
2901
3655
  }
2902
- function rowToState2(row) {
3656
+ function rowToState(row) {
2903
3657
  const state = {
2904
3658
  taskId: row.task_id,
2905
3659
  chatId: row.chat_id,
@@ -2963,11 +3717,11 @@ class SqliteWecomCardStateStore {
2963
3717
  }
2964
3718
  get(taskId) {
2965
3719
  const row = this.db.prepare("SELECT * FROM wecom_card WHERE task_id = ?").get(taskId);
2966
- return row ? rowToState2(row) : undefined;
3720
+ return row ? rowToState(row) : undefined;
2967
3721
  }
2968
3722
  list() {
2969
3723
  const rows = this.db.prepare("SELECT * FROM wecom_card ORDER BY task_id ASC").all();
2970
- return rows.map(rowToState2);
3724
+ return rows.map(rowToState);
2971
3725
  }
2972
3726
  markPaused(taskId) {
2973
3727
  this.db.prepare("UPDATE wecom_card SET status = 'paused', updated_at = ? WHERE task_id = ?").run(Date.now(), taskId);
@@ -2990,7 +3744,7 @@ class SqliteWecomCardStateStore {
2990
3744
  }
2991
3745
  function getDefaultWecomCardDbPath() {
2992
3746
  const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
2993
- return join2(home, ".local", "share", "roy-agent", "wecom-cards.db");
3747
+ return join(home, ".local", "share", "roy-agent", "wecom-cards.db");
2994
3748
  }
2995
3749
  function createWecomCardStateStore(dbPath) {
2996
3750
  const isTestEnv = "BUN_TEST_WORKER_ID" in process.env;
@@ -3126,6 +3880,7 @@ class WecomTaskNotifyHook {
3126
3880
  config;
3127
3881
  sender;
3128
3882
  cardStateStore;
3883
+ esStatusNotifications;
3129
3884
  lastNotifyTimestamps = new Map;
3130
3885
  constructor(config = {}) {
3131
3886
  this.name = config.pluginName || "WecomTaskNotifyHook";
@@ -3144,6 +3899,7 @@ class WecomTaskNotifyHook {
3144
3899
  ],
3145
3900
  ...config
3146
3901
  };
3902
+ this.esStatusNotifications = config.statusNotifications === true;
3147
3903
  this.sender = config.sender ?? {
3148
3904
  send: (input) => this.defaultSend(input)
3149
3905
  };
@@ -3414,8 +4170,14 @@ class WecomTaskNotifyHook {
3414
4170
  terminal: input.terminal
3415
4171
  });
3416
4172
  const cardResult = await this.deliverTemplateCard(input);
3417
- if (cardResult)
4173
+ if (cardResult) {
4174
+ if (this.statusNotificationsEnabled()) {
4175
+ await this.sendStatusMarkdown(input, content).catch((err) => {
4176
+ logger9.warn(`sendStatusMarkdown failed for task #${input.task.id}: ` + `${err.message}(不影响卡片送达)`);
4177
+ });
4178
+ }
3418
4179
  return true;
4180
+ }
3419
4181
  try {
3420
4182
  const r = await this.sender.send({
3421
4183
  chatId: input.chatId,
@@ -3431,6 +4193,19 @@ class WecomTaskNotifyHook {
3431
4193
  return false;
3432
4194
  }
3433
4195
  }
4196
+ statusNotificationsEnabled() {
4197
+ try {
4198
+ return this.esStatusNotifications === true;
4199
+ } catch {
4200
+ return false;
4201
+ }
4202
+ }
4203
+ async sendStatusMarkdown(input, content) {
4204
+ const r = await this.sender.send({ chatId: input.chatId, content });
4205
+ if (!r.ok) {
4206
+ logger9.warn(`sendStatusMarkdown failed for task #${input.task.id}: ` + `${r.error ?? "unknown"}(不影响卡片送达)`);
4207
+ }
4208
+ }
3434
4209
  async deliverTemplateCard(input) {
3435
4210
  try {
3436
4211
  const env = getEnvContextOrEmpty();
@@ -3456,7 +4231,12 @@ class WecomTaskNotifyHook {
3456
4231
  changeSummary: input.changeSummary,
3457
4232
  milestones: input.milestones,
3458
4233
  buttons: [
3459
- { text: "刷新详情", key: WecomCardActionKey.VIEW, style: 1 }
4234
+ { text: "刷新详情", key: WecomCardActionKey.VIEW, style: 1 },
4235
+ {
4236
+ text: "刷新活性",
4237
+ key: WecomCardActionKey.REFRESH_ACTIVITY,
4238
+ style: 2
4239
+ }
3460
4240
  ]
3461
4241
  });
3462
4242
  const r = await sendWecomTemplateCard({
@@ -3584,10 +4364,10 @@ var logger10 = createLogger("lark-cli:per-task-group");
3584
4364
  function isPluginDebugEnabled() {
3585
4365
  return process.env.ROY_TASK_NOTIFY_DEBUG === "1" || process.env.ROY_TRACE_DEBUG === "1";
3586
4366
  }
3587
- var CTX_FIELD_CHAT_ID2 = "larkPerTaskChatId";
4367
+ var CTX_FIELD_CHAT_ID3 = "larkPerTaskChatId";
3588
4368
  var CTX_FIELD_CHAT_PROFILE2 = "larkPerTaskChatProfile";
3589
- var CTX_FIELD_CHAT_CREATED_AT = "larkPerTaskChatCreatedAt";
3590
- var CTX_FIELD_CHAT_TASK_ID = "larkPerTaskChatTaskId";
4369
+ var CTX_FIELD_CHAT_CREATED_AT2 = "larkPerTaskChatCreatedAt";
4370
+ var CTX_FIELD_CHAT_TASK_ID2 = "larkPerTaskChatTaskId";
3591
4371
 
3592
4372
  class PerTaskGroupChatPlugin {
3593
4373
  name;
@@ -3623,7 +4403,7 @@ class PerTaskGroupChatPlugin {
3623
4403
  resolveToTriggerChatFallback: config.resolveToTriggerChatFallback ?? false,
3624
4404
  cardInitialMessage: config.cardInitialMessage !== false,
3625
4405
  autoCreateGroup: config.autoCreateGroup === true,
3626
- cardNotifier: config.cardNotifier ?? new LarkCardPatchNotifier
4406
+ cardNotifier: config.cardNotifier ?? getSharedLarkCardNotifier()
3627
4407
  };
3628
4408
  this.cardNotifier = this.config.cardNotifier;
3629
4409
  this.name = this.config.pluginName;
@@ -3918,10 +4698,10 @@ class PerTaskGroupChatPlugin {
3918
4698
  } catch {
3919
4699
  parsed = {};
3920
4700
  }
3921
- parsed[CTX_FIELD_CHAT_ID2] = chatId;
4701
+ parsed[CTX_FIELD_CHAT_ID3] = chatId;
3922
4702
  parsed[CTX_FIELD_CHAT_PROFILE2] = profile;
3923
- parsed[CTX_FIELD_CHAT_CREATED_AT] = new Date().toISOString();
3924
- parsed[CTX_FIELD_CHAT_TASK_ID] = task.id;
4703
+ parsed[CTX_FIELD_CHAT_CREATED_AT2] = new Date().toISOString();
4704
+ parsed[CTX_FIELD_CHAT_TASK_ID2] = task.id;
3925
4705
  const newContext = JSON.stringify(parsed);
3926
4706
  if (!this.taskUpdater) {
3927
4707
  logger10.warn(`attachChatIdToTask[${this.pluginName}]: ⚠️ no taskUpdater injected, ` + `chat_id ${chatId} NOT persisted to db for task #${task.id}. ` + `后果:下次 AFTER_UPDATE 从 DB 加载 task 时 getTaskChatId() 返回 undefined,` + `所有 update/complete/operation 通知 silently 丢失。` + `修复:event-source-handlers.ts:registerPluginHooks 应自动注入 setTaskUpdater。`);
@@ -3960,7 +4740,7 @@ class PerTaskGroupChatPlugin {
3960
4740
  return;
3961
4741
  try {
3962
4742
  const obj = JSON.parse(ctxStr);
3963
- const id = obj[CTX_FIELD_CHAT_ID2];
4743
+ const id = obj[CTX_FIELD_CHAT_ID3];
3964
4744
  return typeof id === "string" ? id : undefined;
3965
4745
  } catch {
3966
4746
  return;
@@ -4222,4 +5002,4 @@ __legacyDecorateClassTS([
4222
5002
  function createPerTaskGroupChatPlugin(config = {}) {
4223
5003
  return new PerTaskGroupChatPlugin(config);
4224
5004
  }
4225
- export { createAutoTaskPlugin, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, LAYA_COMPLETION_THRESHOLD, buildCompletionHint, renderCompletionState, buildCompletionQuestion, LayaCompletionPlugin, createLayaCompletionPlugin, registerLayaCompletionTaskHook, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, MemoryLarkCardStateStore, SqliteLarkCardStateStore, getDefaultLarkCardDbPath, createLarkCardStateStore, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, MemoryWecomCardStateStore, createMemoryWecomCardStore, SqliteWecomCardStateStore, getDefaultWecomCardDbPath, createWecomCardStateStore, WecomNotifyType, extractWecomFields, buildWecomTaskCard, WecomTaskNotifyHook, createWecomTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
5005
+ export { createAutoTaskPlugin, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, LAYA_COMPLETION_THRESHOLD, buildCompletionHint, renderCompletionState, buildCompletionQuestion, LayaCompletionPlugin, createLayaCompletionPlugin, registerLayaCompletionTaskHook, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, handleCardAction, deleteGroupChatViaLarkCli, createGroupChatViaLarkCli, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, MemoryWecomCardStateStore, createMemoryWecomCardStore, SqliteWecomCardStateStore, getDefaultWecomCardDbPath, createWecomCardStateStore, WecomNotifyType, extractWecomFields, buildWecomTaskCard, WecomTaskNotifyHook, createWecomTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };