@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.
- package/dist/config/index.js +7 -7
- package/dist/env/agent/agent-component.d.ts +1 -1
- package/dist/env/agent/agent-component.js +13 -12
- package/dist/env/agent/index.js +13 -12
- package/dist/env/agent/types.d.ts +14 -0
- package/dist/env/commands/index.js +7 -7
- package/dist/env/context/index.js +3 -3
- package/dist/env/debug/index.js +7 -7
- package/dist/env/error-reply.js +1 -1
- package/dist/env/event-source/event-source-handlers.d.ts +18 -2
- package/dist/env/event-source/event-source-handlers.js +26 -21
- package/dist/env/event-source/event-store.js +2 -2
- package/dist/env/event-source/index.js +25 -22
- package/dist/env/event-source/timer-handler.d.ts +98 -0
- package/dist/env/event-source/types.d.ts +53 -1
- package/dist/env/event-source/wecom-card-action-handler.d.ts +235 -3
- package/dist/env/event-source/wecom-card-builder.d.ts +207 -1
- package/dist/env/event-source/wecom-cli-handler.d.ts +25 -0
- package/dist/{shared/@ai-setting/roy-agent-core-vyr6svt6.js → env/event-source/wecom-respond-registry.js} +8 -6
- package/dist/env/event-source/wecom-session-card-sender.d.ts +135 -0
- package/dist/env/event-source/wecom-session-card-sender.js +153 -0
- package/dist/env/hook/index.js +5 -5
- package/dist/env/index.js +49 -45
- package/dist/env/llm/index.js +9 -8
- package/dist/env/log-trace/index.js +7 -7
- package/dist/env/log-trace/trace-filters.d.ts +6 -1
- package/dist/env/mcp/index.js +7 -7
- package/dist/env/memory/index.js +7 -7
- package/dist/env/plugin/index.js +7 -7
- package/dist/env/prompt/index.js +8 -8
- package/dist/env/prompt/prompts-index.d.ts +1 -1
- package/dist/env/session/group-session-link.d.ts +99 -0
- package/dist/env/session/index.js +11 -10
- package/dist/env/session/session-component.d.ts +89 -0
- package/dist/env/session/storage/index.js +5 -4
- package/dist/env/session/types.d.ts +10 -0
- package/dist/env/skill/index.js +7 -7
- package/dist/env/task/delegate/delegate-channel-hint.d.ts +39 -0
- package/dist/env/task/delegate/delegate-tool.d.ts +71 -6
- package/dist/env/task/delegate/delegate-tool.js +11 -9
- package/dist/env/task/delegate/index.js +8 -8
- package/dist/env/task/index.js +18 -18
- package/dist/env/task/mcp/index.js +4 -4
- package/dist/env/task/plugins/card-subject.d.ts +117 -0
- package/dist/env/task/plugins/index.js +19 -17
- package/dist/env/task/plugins/lark-card-action-handler.d.ts +261 -6
- package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +501 -0
- package/dist/env/task/plugins/lark-card-patch-notifier.js +12 -2
- package/dist/env/task/plugins/lark-card-state-store.d.ts +60 -0
- package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +45 -0
- package/dist/env/task/plugins/sub-session-activity.d.ts +415 -0
- package/dist/env/task/plugins/wecom-session-card-store.d.ts +134 -0
- package/dist/env/task/plugins/wecom-session-card-store.js +23 -0
- package/dist/env/task/plugins/wecom-task-notify-plugin.d.ts +40 -0
- package/dist/env/task/storage/index.js +3 -3
- package/dist/env/task/tools/index.js +4 -4
- package/dist/env/tool/built-in/index.js +6 -4
- package/dist/env/tool/index.js +14 -10
- package/dist/env/tool/types.d.ts +25 -0
- package/dist/env/tool/validator.d.ts +8 -0
- package/dist/env/workflow/engine/index.js +12 -12
- package/dist/env/workflow/hooks/index.js +7 -7
- package/dist/env/workflow/index.js +24 -23
- package/dist/env/workflow/nodes/index.js +2 -2
- package/dist/env/workflow/plugins/index.js +10 -8
- package/dist/env/workflow/service/index.js +2 -2
- package/dist/env/workflow/storage/index.js +3 -3
- package/dist/env/workflow/tools/index.js +16 -15
- package/dist/env/workflow/utils/index.js +4 -4
- package/dist/index.js +65 -60
- package/dist/shared/@ai-setting/{roy-agent-core-dc9zq75z.js → roy-agent-core-0crmsc12.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-cha0jc7n.js → roy-agent-core-0sbmdz8d.js} +137 -16
- package/dist/shared/@ai-setting/{roy-agent-core-87942bw4.js → roy-agent-core-1mhyh4c5.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-v6vr6za7.js → roy-agent-core-1vv4fx15.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-nwbfepba.js → roy-agent-core-3e040tc5.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-418zc9ha.js +288 -0
- package/dist/shared/@ai-setting/{roy-agent-core-r5wt1h3c.js → roy-agent-core-4fkxy2gv.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-na18zwj0.js → roy-agent-core-4t3yskp8.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-5pr4zp88.js → roy-agent-core-5kzhaap1.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-qq96p0tj.js → roy-agent-core-5s2t8z00.js} +22 -1
- package/dist/shared/@ai-setting/{roy-agent-core-6rh8k6ba.js → roy-agent-core-6gj1hc5z.js} +7 -7
- package/dist/shared/@ai-setting/{roy-agent-core-ezvpreqz.js → roy-agent-core-6k8mb7zx.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-g8htfwxj.js → roy-agent-core-7f10p7m2.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-7kvpc0w5.js +177 -0
- package/dist/shared/@ai-setting/{roy-agent-core-h21gngxf.js → roy-agent-core-7yh5wps0.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-efkjyh69.js → roy-agent-core-9710zwwz.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-rykf7c04.js → roy-agent-core-9c565frx.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-fghc2m8z.js → roy-agent-core-aa8kv18f.js} +34 -2
- package/dist/shared/@ai-setting/{roy-agent-core-p6ks90tb.js → roy-agent-core-an35dzbf.js} +6 -6
- package/dist/shared/@ai-setting/{roy-agent-core-7bpj859q.js → roy-agent-core-azyz78ky.js} +7 -7
- package/dist/shared/@ai-setting/{roy-agent-core-264qexac.js → roy-agent-core-b7h39vrc.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-dqxec05w.js → roy-agent-core-bjtc9d35.js} +2 -2
- package/dist/shared/@ai-setting/roy-agent-core-c4ec7k0p.js +426 -0
- package/dist/shared/@ai-setting/{roy-agent-core-ddyk1rc0.js → roy-agent-core-cre2y2ct.js} +160 -4
- package/dist/shared/@ai-setting/{roy-agent-core-tr3ep81v.js → roy-agent-core-d6zdbgtc.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-0qyrk5k0.js → roy-agent-core-ds6qzmf8.js} +14 -5
- package/dist/shared/@ai-setting/{roy-agent-core-cgtq6427.js → roy-agent-core-e08bvpaq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-12n08pg5.js → roy-agent-core-e69qxdyy.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-qk9hd89p.js → roy-agent-core-fan8g0z1.js} +10 -10
- package/dist/shared/@ai-setting/{roy-agent-core-xcp8vwax.js → roy-agent-core-fepm835e.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-6j02sbee.js → roy-agent-core-fhg03v43.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-dvtg4sn0.js → roy-agent-core-g8yr733d.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-xf6z96qy.js → roy-agent-core-gr5h1dyr.js} +818 -346
- package/dist/shared/@ai-setting/{roy-agent-core-m3vjepzz.js → roy-agent-core-h1e1fm9b.js} +38 -202
- package/dist/shared/@ai-setting/{roy-agent-core-c2gbt68e.js → roy-agent-core-h83zdsrq.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-tk98f92b.js → roy-agent-core-k3gcvybs.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-pgvcf91v.js → roy-agent-core-m39v7qpx.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-tm1mxttk.js → roy-agent-core-mf5p38yf.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-83bewqx8.js → roy-agent-core-mvh0td7p.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-ze5cqpf2.js → roy-agent-core-mx855td4.js} +17 -38
- package/dist/shared/@ai-setting/{roy-agent-core-0dh8npnz.js → roy-agent-core-n197medr.js} +13 -13
- package/dist/shared/@ai-setting/{roy-agent-core-cmprygrp.js → roy-agent-core-nqkfkpf4.js} +4 -4
- package/dist/shared/@ai-setting/{roy-agent-core-7cbpmk4q.js → roy-agent-core-p31ftgq9.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-vffccgf6.js → roy-agent-core-qwd2dq5q.js} +10 -3
- package/dist/shared/@ai-setting/{roy-agent-core-b31v3qpv.js → roy-agent-core-ra66sf50.js} +990 -211
- package/dist/shared/@ai-setting/{roy-agent-core-1v4g0d4x.js → roy-agent-core-rm7seqwb.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-7dd0s3xb.js → roy-agent-core-shb1qsyg.js} +1027 -14
- package/dist/shared/@ai-setting/{roy-agent-core-ax9e9szb.js → roy-agent-core-smf94h73.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-acj8ag08.js → roy-agent-core-tjnabyvq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-qqaak24r.js → roy-agent-core-tkpp1ba0.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-dkrvmjsh.js → roy-agent-core-tpm1dd1g.js} +23 -15
- package/dist/shared/@ai-setting/{roy-agent-core-ed233k5z.js → roy-agent-core-v1n0106v.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-zxz5fkwj.js → roy-agent-core-v6yshv0z.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-mcy5apay.js → roy-agent-core-vf9sp6s3.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-q813vafd.js → roy-agent-core-vpcxv1ty.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-xvt7kf7p.js → roy-agent-core-w9ryeg2p.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-es203w7p.js → roy-agent-core-wqs6yypq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-d1n4jbza.js → roy-agent-core-wr1qexzq.js} +5 -5
- package/dist/shared/@ai-setting/roy-agent-core-wxrkcb17.js +56 -0
- package/dist/shared/@ai-setting/{roy-agent-core-0snm5hc5.js → roy-agent-core-y6shd91r.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-9vxh6f8h.js → roy-agent-core-y7fqtzk2.js} +7 -7
- package/dist/shared/@ai-setting/{roy-agent-core-cpcd24n2.js → roy-agent-core-yet36q5k.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-3fh1f7s7.js → roy-agent-core-yfpnkc4z.js} +5 -5
- package/dist/shared/@ai-setting/{roy-agent-core-begm7ytz.js → roy-agent-core-yh68z1bg.js} +3 -3
- 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-
|
|
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-
|
|
12
|
+
} from "./roy-agent-core-4t3yskp8.js";
|
|
11
13
|
import {
|
|
12
14
|
WORKFLOW_EXTRACT_PROMPT
|
|
13
|
-
} from "./roy-agent-core-
|
|
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-
|
|
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-
|
|
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-
|
|
46
|
+
} from "./roy-agent-core-rm7seqwb.js";
|
|
38
47
|
import {
|
|
39
48
|
enqueueToAddFakeUserMessage
|
|
40
|
-
} from "./roy-agent-core-
|
|
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-
|
|
55
|
+
} from "./roy-agent-core-tkpp1ba0.js";
|
|
47
56
|
import {
|
|
48
57
|
getEnvContextOrEmpty,
|
|
49
58
|
init_env_context
|
|
50
|
-
} from "./roy-agent-core-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
1696
|
+
// src/env/task/plugins/lark-card-action-handler.ts
|
|
1688
1697
|
init_logger();
|
|
1689
|
-
|
|
1690
|
-
import {
|
|
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
|
|
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[
|
|
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
|
-
|
|
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
|
|
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
|
|
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 ?
|
|
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(
|
|
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
|
|
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
|
|
4366
|
+
var CTX_FIELD_CHAT_ID3 = "larkPerTaskChatId";
|
|
3588
4367
|
var CTX_FIELD_CHAT_PROFILE2 = "larkPerTaskChatProfile";
|
|
3589
|
-
var
|
|
3590
|
-
var
|
|
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 ??
|
|
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[
|
|
4700
|
+
parsed[CTX_FIELD_CHAT_ID3] = chatId;
|
|
3922
4701
|
parsed[CTX_FIELD_CHAT_PROFILE2] = profile;
|
|
3923
|
-
parsed[
|
|
3924
|
-
parsed[
|
|
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[
|
|
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,
|
|
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 };
|