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