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

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