@ai-setting/roy-agent-core 1.7.77 → 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/config-protection.d.ts +89 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +23 -7
- package/dist/env/agent/agent-component.d.ts +43 -22
- package/dist/env/agent/agent-component.js +13 -12
- package/dist/env/agent/index.js +13 -12
- package/dist/env/agent/types.d.ts +46 -12
- 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.d.ts +6 -0
- package/dist/env/error-reply.js +1 -1
- package/dist/env/event-source/bounty-im-handler.d.ts +8 -0
- package/dist/env/event-source/es-command.d.ts +39 -0
- package/dist/env/event-source/es-plugins.d.ts +68 -0
- package/dist/env/event-source/event-source-component.d.ts +15 -0
- package/dist/env/event-source/event-source-handlers.d.ts +84 -0
- package/dist/env/event-source/event-source-handlers.js +40 -23
- package/dist/env/event-source/event-store.js +2 -2
- package/dist/env/event-source/index.d.ts +5 -1
- package/dist/env/event-source/index.js +75 -23
- package/dist/env/event-source/timer-handler.d.ts +98 -0
- package/dist/env/event-source/types.d.ts +154 -2
- package/dist/env/event-source/wecom-card-action-handler.d.ts +373 -0
- package/dist/env/event-source/wecom-card-builder.d.ts +426 -0
- package/dist/env/event-source/wecom-cli-handler.d.ts +291 -0
- package/dist/env/event-source/wecom-cli-sender.d.ts +88 -0
- package/dist/env/event-source/wecom-media.d.ts +130 -0
- package/dist/env/event-source/wecom-respond-registry.d.ts +151 -0
- package/dist/env/event-source/wecom-respond-registry.js +33 -0
- 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/event-source/wecom-ws-bridge.d.ts +213 -0
- package/dist/env/hook/index.js +5 -5
- package/dist/env/index.js +51 -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 +132 -7
- package/dist/env/task/delegate/delegate-tool.js +13 -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.d.ts +2 -0
- package/dist/env/task/plugins/index.js +39 -16
- package/dist/env/task/plugins/lark-card-action-handler.d.ts +261 -6
- package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +542 -2
- 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 +61 -0
- package/dist/env/task/plugins/sub-session-activity.d.ts +415 -0
- package/dist/env/task/plugins/wecom-card-state-store.d.ts +121 -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 +264 -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.d.ts +2 -0
- package/dist/env/tool/built-in/index.js +9 -2
- package/dist/env/tool/index.js +14 -9
- 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 +118 -62
- 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-aa2j7bk9.js → roy-agent-core-0sbmdz8d.js} +171 -36
- 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-2stt4rp5.js +134 -0
- 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-6cy43nss.js → roy-agent-core-9c565frx.js} +49 -9
- 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-ew6qb5yx.js → roy-agent-core-an35dzbf.js} +6 -6
- package/dist/shared/@ai-setting/{roy-agent-core-mww0mnd9.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-rhtdspx6.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-99nwgj84.js → roy-agent-core-fzw9xzm1.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-3gpygqcx.js → roy-agent-core-gr5h1dyr.js} +2753 -358
- package/dist/shared/@ai-setting/roy-agent-core-h1e1fm9b.js +380 -0
- 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-8ez6mccb.js → roy-agent-core-k3gcvybs.js} +5 -1
- 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-v85n42ve.js → roy-agent-core-mx855td4.js} +17 -38
- package/dist/shared/@ai-setting/{roy-agent-core-v87njmq7.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-rsn44n9y.js → roy-agent-core-ra66sf50.js} +2526 -942
- 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-w0qjeq2v.js → roy-agent-core-shb1qsyg.js} +1041 -18
- 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-9vhq7fvv.js → roy-agent-core-tpm1dd1g.js} +34 -88
- 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-bxkjk7zx.js → roy-agent-core-vx8zc847.js} +4 -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-hd3n57zs.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-0v27y99n.js → roy-agent-core-yh68z1bg.js} +65 -4
- package/package.json +18 -1
- /package/dist/shared/@ai-setting/{roy-agent-core-b4x6x4k2.js → roy-agent-core-m1fgtnrc.js} +0 -0
|
@@ -67,6 +67,14 @@ export interface LarkCardState {
|
|
|
67
67
|
updatedAt: number;
|
|
68
68
|
/** 终结时间(ms epoch);仅 status=closed 有值 */
|
|
69
69
|
closedAt?: number;
|
|
70
|
+
/**
|
|
71
|
+
* ★ Task #5364 返工:该任务卡片实际归属的 **sub-session id**。
|
|
72
|
+
*
|
|
73
|
+
* 用途:进程重启后,`task:after.update` 这类只带 taskId 的事件仍能知道
|
|
74
|
+
* 「该 patch 哪张 **session** 卡」(一个 session 一张卡的前提)。
|
|
75
|
+
* 缺省(旧行 / 非 session 卡)⇒ 走传统 task 卡路径。
|
|
76
|
+
*/
|
|
77
|
+
sessionId?: string;
|
|
70
78
|
/**
|
|
71
79
|
* 卡片自持的累积内容(Task #4536 / xval MAJOR 3)
|
|
72
80
|
*
|
|
@@ -135,3 +143,55 @@ export declare function getDefaultLarkCardDbPath(): string;
|
|
|
135
143
|
* 持久化层故障让通知路径整体崩掉(诉求 C:健壮性)。
|
|
136
144
|
*/
|
|
137
145
|
export declare function createLarkCardStateStore(dbPath?: string): LarkCardStateStore;
|
|
146
|
+
/** 取进程级共享持久层(首次调用时创建)。 */
|
|
147
|
+
export declare function getSharedLarkCardStateStore(): LarkCardStateStore;
|
|
148
|
+
/** 测试钩子:丢弃单例(避免用例间串味 / 连到真实库)。 */
|
|
149
|
+
export declare function __resetSharedLarkCardStateStoreForTest(): void;
|
|
150
|
+
/**
|
|
151
|
+
* 为什么需要独立表(不能塞进 `lark_card`)
|
|
152
|
+
*
|
|
153
|
+
* `lark_card.task_id INTEGER PRIMARY KEY` 装不下 `session_xxx` 字符串;
|
|
154
|
+
* 而本轮的核心语义是「**一个 session 一张卡**」—— 无 task 的 session 也必须
|
|
155
|
+
* 能重启后找回自己的卡,否则每次都多发一张(用户硬要求被静默破坏)。
|
|
156
|
+
*
|
|
157
|
+
* 表:`lark_session_card`,主键 `session_id`。零迁移风险(新表 IF NOT EXISTS)。
|
|
158
|
+
*/
|
|
159
|
+
export interface LarkSessionCardRecord {
|
|
160
|
+
sessionId: string;
|
|
161
|
+
messageId: string;
|
|
162
|
+
chatId: string;
|
|
163
|
+
profile?: string;
|
|
164
|
+
createdAt: number;
|
|
165
|
+
updatedAt: number;
|
|
166
|
+
}
|
|
167
|
+
/** session 卡持久化端口 */
|
|
168
|
+
export interface LarkSessionCardStore {
|
|
169
|
+
upsert(rec: LarkSessionCardRecord): void;
|
|
170
|
+
get(sessionId: string): LarkSessionCardRecord | undefined;
|
|
171
|
+
list(): LarkSessionCardRecord[];
|
|
172
|
+
delete(sessionId: string): void;
|
|
173
|
+
close(): void;
|
|
174
|
+
}
|
|
175
|
+
/** 内存实现(测试 / SQLite 不可用时降级) */
|
|
176
|
+
export declare class MemoryLarkSessionCardStore implements LarkSessionCardStore {
|
|
177
|
+
private readonly records;
|
|
178
|
+
upsert(rec: LarkSessionCardRecord): void;
|
|
179
|
+
get(sessionId: string): LarkSessionCardRecord | undefined;
|
|
180
|
+
list(): LarkSessionCardRecord[];
|
|
181
|
+
delete(sessionId: string): void;
|
|
182
|
+
close(): void;
|
|
183
|
+
}
|
|
184
|
+
/** SQLite 实现 */
|
|
185
|
+
export declare class SqliteLarkSessionCardStore implements LarkSessionCardStore {
|
|
186
|
+
private readonly db;
|
|
187
|
+
constructor(dbPath: string);
|
|
188
|
+
upsert(rec: LarkSessionCardRecord): void;
|
|
189
|
+
get(sessionId: string): LarkSessionCardRecord | undefined;
|
|
190
|
+
list(): LarkSessionCardRecord[];
|
|
191
|
+
delete(sessionId: string): void;
|
|
192
|
+
close(): void;
|
|
193
|
+
}
|
|
194
|
+
/** 创建 session 卡持久层(与 task 卡同一套降级语义) */
|
|
195
|
+
export declare function createLarkSessionCardStore(dbPath?: string): LarkSessionCardStore;
|
|
196
|
+
export declare function getSharedLarkSessionCardStore(): LarkSessionCardStore;
|
|
197
|
+
export declare function __resetSharedLarkSessionCardStoreForTest(): void;
|
|
@@ -339,6 +339,22 @@ export declare class LarkCliTaskNotifyHook implements Hook {
|
|
|
339
339
|
* 持久层 `pinned=false`(如实反映真实状态,不谎报)。
|
|
340
340
|
*/
|
|
341
341
|
private repinAfterResume;
|
|
342
|
+
/**
|
|
343
|
+
* ★ Task #5182 — reopen 复用原卡(用户明令 2026-11-08 15:56)
|
|
344
|
+
*
|
|
345
|
+
* 场景:lark-cards.db 里该 task 的记录是 `closed`,但任务被重新激活
|
|
346
|
+
* (status → active 等活跃态)。旧实现发一张新卡(同 task 两张卡、
|
|
347
|
+
* 历史时间线留在旧卡上);新实现:
|
|
348
|
+
*
|
|
349
|
+
* 1. patch 原 messageId:内容 = 当前任务态 + 「🔄 任务重新打开」里程碑
|
|
350
|
+
* 2. 新增一条 reopen 操作记录进卡片时间线(用户可见的 reopen 痕迹)
|
|
351
|
+
* 3. 重新 pin(终态时被 unpin 过;pin 状态必须跟任务状态一致)
|
|
352
|
+
* 4. 持久层 markReopened(closed → open,messageId 不变)
|
|
353
|
+
*
|
|
354
|
+
* @returns true = 原卡复用成功(caller 直接返回,不再走建卡路径);
|
|
355
|
+
* false = 原卡 patch 失败(caller 降级走发新卡路径)
|
|
356
|
+
*/
|
|
357
|
+
private reopenReuseCard;
|
|
342
358
|
/** 合并「写记录」所需的公共字段(读旧记录失败也不抛,降级为 now) */
|
|
343
359
|
private buildCardState;
|
|
344
360
|
/** 对外只读:某 task 的持久化卡片记录(debug / 排查用) */
|
|
@@ -401,6 +417,51 @@ export declare class LarkCliTaskNotifyHook implements Hook {
|
|
|
401
417
|
* LarkCardPatchNotifier.renderContextAsLines 负责(卡片里绝不出现 JSON)。
|
|
402
418
|
*/
|
|
403
419
|
private buildTaskMeta;
|
|
420
|
+
/**
|
|
421
|
+
* ★ Task #5364 返工:解析该 task 所属的 sub-session id。
|
|
422
|
+
*
|
|
423
|
+
* 顺序(都不猜):
|
|
424
|
+
* 1. notifier 反向索引(本进程发过卡 / 事件里绑定过)
|
|
425
|
+
* 2. 持久层 `lark_card.session_id`(重启后仍有效)
|
|
426
|
+
* 3. EnvContext 的 replyChannel / envEvent(sub-agent 运行时带)
|
|
427
|
+
*/
|
|
428
|
+
private resolveSessionIdForTask;
|
|
429
|
+
/**
|
|
430
|
+
* ★ Task #5364 返工(BLOCKER-5/6):把 task 事件渲染进**已有的 session 卡**。
|
|
431
|
+
*
|
|
432
|
+
* ## 用户定稿(2026-11-12 08:13 / 08:15)
|
|
433
|
+
*
|
|
434
|
+
* - 「一个 session 只有一张卡」
|
|
435
|
+
* - 「有 task → task 信息渲染在这张 session 卡上」(合并,不是并列)
|
|
436
|
+
* - 飞书:**任务状态更新自动同步**(自动 patch 同一张卡)
|
|
437
|
+
*
|
|
438
|
+
* ## 实现
|
|
439
|
+
*
|
|
440
|
+
* 已有该 session 的卡 → `updateSessionCard`(patch 同一 messageId,带 task 区块)
|
|
441
|
+
* ;没有 → 返回 false,调用方退回落旧的 task 卡路径(首次由 session 生命周期
|
|
442
|
+
* 事件发卡;task 先到就先由这里发「session 卡 + task 区块」)。
|
|
443
|
+
*/
|
|
444
|
+
private patchSessionCardForTask;
|
|
445
|
+
/**
|
|
446
|
+
* ★ Task #5417:群 session 反查端口(由 ES 装配点注入)。
|
|
447
|
+
* 未注入 ⇒ task 侧双写退化为 no-op(不影响原卡链路)。
|
|
448
|
+
*/
|
|
449
|
+
syncGroupCardFromOriginFn?: (sid: string) => Promise<{
|
|
450
|
+
chatId: string;
|
|
451
|
+
aliasSessionId?: string;
|
|
452
|
+
} | undefined>;
|
|
453
|
+
/**
|
|
454
|
+
* ★ Task #5364 返工(BLOCKER-4 重启分支):把「task 的卡 = 那张 session 卡」
|
|
455
|
+
* 写进持久层。
|
|
456
|
+
*
|
|
457
|
+
* 重启后 `restoreCardState` 读到 `{messageId: <session 卡>, sessionId}` ⇒
|
|
458
|
+
* `adoptSessionCard` 恢复内存索引 ⇒ 后续 patch 命中同一张卡(不再多发)。
|
|
459
|
+
*/
|
|
460
|
+
private recordSessionCardForTask;
|
|
461
|
+
/** sub-agent 类型(有则显示「执行 Agent」行) */
|
|
462
|
+
private resolveAgentType;
|
|
463
|
+
/** TaskMeta → buildSessionCard 的 task 区块载荷 */
|
|
464
|
+
private buildSessionCardTask;
|
|
404
465
|
/**
|
|
405
466
|
* operation → 卡片时间线条目(里程碑 + 过程步骤 + 当前步骤)
|
|
406
467
|
*
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Task #5360 — sub-session 活性(liveness)公共纯函数层
|
|
3
|
+
*
|
|
4
|
+
* ## 用户需求(2026-11-11 20:50)
|
|
5
|
+
*
|
|
6
|
+
* > 「卡片新增「刷新」按钮(类似企微卡片的刷新按钮):按下后展示该任务对应
|
|
7
|
+
* > subsession 的消息数 + 最近一条 msg(截断)—— 用来判断有无 LLM 推进」
|
|
8
|
+
*
|
|
9
|
+
* ## 为什么需要这一层
|
|
10
|
+
*
|
|
11
|
+
* 飞书卡片(`lark-card-action-handler`)和企微卡片(`wecom-card-action-handler`)
|
|
12
|
+
* 都要展示同一份活性数据。如果两边各写一份:
|
|
13
|
+
*
|
|
14
|
+
* - liveness 阈值(5min / 20min)会漂移 → 同一个任务在两个渠道显示不同颜色
|
|
15
|
+
* - 截断规则会漂移 → 一边切词一边不切
|
|
16
|
+
* - `taskId → subSessionId` 反查会漂移 → 一边查得到一边查不到
|
|
17
|
+
*
|
|
18
|
+
* 所以**判定逻辑全部纯函数**放这里,两侧只负责渲染(飞书 markdown / 企微 quote)。
|
|
19
|
+
*
|
|
20
|
+
* ## 数据从哪来(不新造轮子)
|
|
21
|
+
*
|
|
22
|
+
* `BackgroundTaskManager.getSubSessionActivity(bgProcessId)`(delegate-tool.ts:2406)
|
|
23
|
+
* 早就存在,此前只被 `query_background_process` 工具消费(Task #4720 Fix B)。
|
|
24
|
+
* 它内部读 `session.messageCount` / `session.updatedAt`,任何失败返回 undefined
|
|
25
|
+
* (不抛)—— 本模块沿用这一语义。
|
|
26
|
+
*
|
|
27
|
+
* 本模块**不**直接依赖 SessionComponent / BackgroundTaskManager 的类型:
|
|
28
|
+
* 用最小结构端口(`SubSessionActivityPorts`),便于单测注入假实现,
|
|
29
|
+
* 也避免 core 内部循环依赖。
|
|
30
|
+
*/
|
|
31
|
+
/** sub-session 活性快照(飞书 / 企微 / CLI 统一形状) */
|
|
32
|
+
export interface SubSessionActivity {
|
|
33
|
+
/** sub-session id(仅诊断用,不渲染给用户) */
|
|
34
|
+
subSessionId: string;
|
|
35
|
+
/** 消息总数(`session.messageCount`) */
|
|
36
|
+
messageCount: number;
|
|
37
|
+
/** 最后写入时间(ms epoch) */
|
|
38
|
+
updatedAt: number;
|
|
39
|
+
/** 最近一条有内容的消息(已截断) */
|
|
40
|
+
lastMessage?: ActivityMessage;
|
|
41
|
+
}
|
|
42
|
+
/** 最近一条消息(已截断) */
|
|
43
|
+
export interface ActivityMessage {
|
|
44
|
+
/** 角色(user / assistant / tool / system …) */
|
|
45
|
+
role: string;
|
|
46
|
+
/** 已截断的正文 */
|
|
47
|
+
content: string;
|
|
48
|
+
/** 消息时间戳(ms epoch,可选) */
|
|
49
|
+
timestamp?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* ★ Task #5420 返工:装配点传入消息的**最小**结构。
|
|
53
|
+
*
|
|
54
|
+
* 为什么不直接用 `SessionMessage`:装配点(event-source-handlers.ts)
|
|
55
|
+
* 拿到的 session store 消息可能带 parts(新格式:content="",正文在
|
|
56
|
+
* parts),这里只需要 role/content/parts 三个字段,收窄依赖面。
|
|
57
|
+
*/
|
|
58
|
+
export interface SessionMessageLite {
|
|
59
|
+
role?: string;
|
|
60
|
+
content?: string;
|
|
61
|
+
timestamp?: number;
|
|
62
|
+
/** 新格式消息正文(content 为空时从这里提取) */
|
|
63
|
+
parts?: Array<Record<string, unknown>>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 活性档位。
|
|
67
|
+
*
|
|
68
|
+
* ★ 阈值理由(不是拍脑袋):`task.lastUpdatedAt` 的**心跳周期就是 20 分钟**
|
|
69
|
+
* (见 delegate-tool.ts 里 "subSessionUpdatedAt / 不再依赖缩短心跳" 的注释)。
|
|
70
|
+
* 也就是说,一个**健康**的长时间任务最多 20 分钟没有新消息。所以:
|
|
71
|
+
* - `< 5min` → 明显在跑(一轮 ReAct 通常几分钟)
|
|
72
|
+
* - `5~20min` → 还在心跳窗口内,但当前这一轮比较久 → 慢
|
|
73
|
+
* - `≥ 20min` → 连心跳窗口都超了 → 疑似停滞(该看 trace 了)
|
|
74
|
+
*/
|
|
75
|
+
export type ActivityLevel = "active" | "slow" | "stalled" | "terminal" | "unknown";
|
|
76
|
+
/** liveness 判定结果 */
|
|
77
|
+
export interface ActivityLiveness {
|
|
78
|
+
level: ActivityLevel;
|
|
79
|
+
/** 单字符信号灯:🟢 / 🟡 / 🔴 / ⚪ / ❔ */
|
|
80
|
+
dot: string;
|
|
81
|
+
/** 档位短语("活跃" / "缓慢" / "疑似停滞" / "已结束" / "未知") */
|
|
82
|
+
label: string;
|
|
83
|
+
/** 多久之前("3 分钟前" / "刚刚");updatedAt 缺失时 undefined */
|
|
84
|
+
agoText?: string;
|
|
85
|
+
}
|
|
86
|
+
/** 活跃档位阈值(毫秒) */
|
|
87
|
+
export declare const ACTIVITY_ACTIVE_MS: number;
|
|
88
|
+
export declare const ACTIVITY_SLOW_MS: number;
|
|
89
|
+
/** 最近消息正文的默认截断长度 */
|
|
90
|
+
export declare const DEFAULT_ACTIVITY_MESSAGE_MAX = 200;
|
|
91
|
+
/**
|
|
92
|
+
* 把「多久之前」渲染成中文短语。
|
|
93
|
+
*
|
|
94
|
+
* - 负数 / NaN / 未来时间 → "刚刚"(时钟回拨不该渲染 "−3 分钟前")
|
|
95
|
+
* - < 60s → "刚刚"
|
|
96
|
+
* - < 60min → "N 分钟前"
|
|
97
|
+
* - < 24h → "N 小时前"
|
|
98
|
+
* - 其它 → "N 天前"
|
|
99
|
+
*/
|
|
100
|
+
export declare function formatAgo(updatedAt: number | undefined, now: number): string | undefined;
|
|
101
|
+
/** `20:47:12` 形态的钟点(本地时间,卡片窄,不要日期) */
|
|
102
|
+
export declare function formatClockTime(ts: number | undefined): string | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* 判定活性档位。
|
|
105
|
+
*
|
|
106
|
+
* @param activity 活性快照(undefined = 拿不到数据)
|
|
107
|
+
* @param now 当前时间(注入便于测试)
|
|
108
|
+
* @param terminal 该任务是否已终态(终态任务永远不是 "stalled",是 "已结束")
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveActivityLiveness(activity: SubSessionActivity | undefined, now: number, terminal?: boolean): ActivityLiveness;
|
|
111
|
+
/**
|
|
112
|
+
* 截断最近消息正文。
|
|
113
|
+
*
|
|
114
|
+
* 为什么不用 `String.slice`:卡片上出现「半个单词 + …」很难读
|
|
115
|
+
* (企微侧已有同款踩坑记录,见 wecom-card-builder 的 `clipWord`)。
|
|
116
|
+
*
|
|
117
|
+
* 策略(与 `clipWord` 对齐但不复用 —— 那个是 wecom 私有且带 CJK 交界逻辑):
|
|
118
|
+
* 1. 先 `flattenInline` 压平换行 / 折叠空白(卡片是单行渲染)
|
|
119
|
+
* 2. 有空格 → 回退到最后一个空格边界
|
|
120
|
+
* 3. 否则硬截(CJK 无空格,硬截是正确的;不该给中文塞省略号断词)
|
|
121
|
+
*/
|
|
122
|
+
export declare function truncateActivityMessage(text: string | undefined, max?: number): string;
|
|
123
|
+
/**
|
|
124
|
+
* 渲染成飞书 markdown 行(`⚡ 子会话活性` 面板正文)。
|
|
125
|
+
*
|
|
126
|
+
* ## 输出形态
|
|
127
|
+
*
|
|
128
|
+
* 有数据:
|
|
129
|
+
* ```
|
|
130
|
+
* 🟢 活跃(3 分钟前有写入) · 消息数 128 条
|
|
131
|
+
* 🕐 最近写入:20:47:12
|
|
132
|
+
* 💬 最近消息:[assistant] 已定位到 wecom-card-builder.ts:485 …
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* 无数据(**绝不返回空数组** —— 否则用户分不清"没数据"和"按钮没生效"):
|
|
136
|
+
* ```
|
|
137
|
+
* ❔ 暂无数据(子会话已归档或未落盘)
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare function formatActivityLines(activity: SubSessionActivity | undefined, now: number, terminal?: boolean): string[];
|
|
141
|
+
/**
|
|
142
|
+
* 渲染成企微 `quote_area`(`{title, text}`)。
|
|
143
|
+
*
|
|
144
|
+
* ## 为什么是 quote_area 而不是 horizontal_content_list
|
|
145
|
+
*
|
|
146
|
+
* 企微 `horizontal_content_list` 协议上限 **6 条**(`MAX_HORIZONTAL_ROWS`),
|
|
147
|
+
* 进展/更新卡的 6 条已被「状态 + 5 条时间线」占满。把活性塞进去必然挤掉
|
|
148
|
+
* 时间线 = 违反用户「之前展示的信息一个都不能少」。
|
|
149
|
+
*
|
|
150
|
+
* ## 为什么 text 是单行
|
|
151
|
+
*
|
|
152
|
+
* `quote_area.quote_text` 真网实证只渲染 ~2 行(见 wecom-card-builder 头注释里
|
|
153
|
+
* Task #5203 的取证)。所以压成 1~2 行短文本,完整消息截断到 80 字。
|
|
154
|
+
*
|
|
155
|
+
* @returns `{title, text, overflow}` —— `overflow=true` 时调用方应把 text
|
|
156
|
+
* 降级到 `sub_title_text`(见 buildWecomCardUpdateFrame)
|
|
157
|
+
*/
|
|
158
|
+
export declare function formatActivityQuote(activity: SubSessionActivity | undefined, now: number, terminal?: boolean): {
|
|
159
|
+
title: string;
|
|
160
|
+
text: string;
|
|
161
|
+
overflow: boolean;
|
|
162
|
+
};
|
|
163
|
+
/** `listTasks()` 返回元素的**最小**结构(不依赖 BackgroundTask 全量类型) */
|
|
164
|
+
export interface BackgroundTaskLite {
|
|
165
|
+
id: string;
|
|
166
|
+
subSessionId: string;
|
|
167
|
+
associatedTaskId?: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 在后台任务表里按 roy 任务号反查 `{bgProcessId, subSessionId}`。
|
|
171
|
+
*
|
|
172
|
+
* ## 为什么要这个反查(而不是直接读 task.context)
|
|
173
|
+
*
|
|
174
|
+
* `task.background.*` 事件 payload 里 `backgroundTaskId` / `subSessionId` /
|
|
175
|
+
* `associatedTaskId` 三个 id 齐全 —— 但那是**事件 payload**,不是持久层。
|
|
176
|
+
* 卡片回调(`card.action.trigger`)只带回 `messageId` + `action.value.taskId`,
|
|
177
|
+
* **没有** backgroundTaskId。而 `lark_card` / `wecom_cards` 表都只按 taskId 索引。
|
|
178
|
+
*
|
|
179
|
+
* 所以必须从 `BackgroundTaskManager.listTasks()` 反查。命中率:
|
|
180
|
+
* - 活跃任务:100%(在 `tasks` Map 里)
|
|
181
|
+
* - 已终结任务:也在(`listTasks()` 返回 `tasks ∪ terminalTasks`,
|
|
182
|
+
* delegate-tool.ts 注释已证)—— 所以「点已完成任务卡片的刷新」也能拿到
|
|
183
|
+
* - 进程重启后:内存表空 → undefined → 卡片渲染「暂无数据」(不抛、不阻断 patch)
|
|
184
|
+
*
|
|
185
|
+
* 多命中时取**最后一个**(`listTasks()` 是 Map 插入序,同 taskId 的复用
|
|
186
|
+
* sub-session 场景下,越晚的越新鲜)。
|
|
187
|
+
*/
|
|
188
|
+
export declare function findBackgroundTaskByAssociatedTaskId(tasks: readonly BackgroundTaskLite[] | undefined, taskId: number): BackgroundTaskLite | undefined;
|
|
189
|
+
/**
|
|
190
|
+
* session-only 活性 probe 的端口。
|
|
191
|
+
*
|
|
192
|
+
* ## 与 `createActivityProbe` 的区别(为什么不能复用)
|
|
193
|
+
*
|
|
194
|
+
* `createActivityProbe(taskId)` 走的是
|
|
195
|
+
* ```
|
|
196
|
+
* taskId → findBackgroundTaskByAssociatedTaskId → bgTask.subSessionId → 活性
|
|
197
|
+
* ```
|
|
198
|
+
* 这条链**必须先有 task**(`associatedTaskId` 来自 task 表)。而本次需求是
|
|
199
|
+
* 「有 sub-session 但**无** task」—— 那条链的第一环就不存在。
|
|
200
|
+
*
|
|
201
|
+
* 所以新增一条**直达**路径:调用方已经把 sessionId 拿在手里,直接读 store。
|
|
202
|
+
*
|
|
203
|
+
* ## 共用部分(刻意复用,避免阈值漂移)
|
|
204
|
+
*
|
|
205
|
+
* 拿到的 `{messageCount, updatedAt, lastMessage}` 之后,仍走同一套
|
|
206
|
+
* `resolveActivityLiveness` / `formatActivityLines` / `formatActivityQuote`
|
|
207
|
+
* —— 两个渠道、两种主键的卡片,颜色与文案口径必须完全一致。
|
|
208
|
+
*/
|
|
209
|
+
export interface SessionActivityPorts {
|
|
210
|
+
/** 读 session 元信息(`sessionComponent.get()`);缺失/抛错 ⇒ undefined */
|
|
211
|
+
getSession?: (sessionId: string) => Promise<{
|
|
212
|
+
messageCount?: number;
|
|
213
|
+
updatedAt?: number;
|
|
214
|
+
} | null | undefined> | {
|
|
215
|
+
messageCount?: number;
|
|
216
|
+
updatedAt?: number;
|
|
217
|
+
} | null | undefined;
|
|
218
|
+
/**
|
|
219
|
+
* ★ CV BLOCKER-3:读该 session 的**生命周期**(哪个后台任务在跑它、状态如何)。
|
|
220
|
+
*
|
|
221
|
+
* 这是「超时/完成/停止体现在卡片上」的唯一可信来源 —— 不能靠 task.status
|
|
222
|
+
* (没 task 就没有),也不能硬写 false。装配点实现:
|
|
223
|
+
* `sessionId → BackgroundTaskManager.listTasks() 里 subSessionId 命中 → status`
|
|
224
|
+
* (running/pending → running;completed/timeout/failed/stopped 直接映射)。
|
|
225
|
+
*
|
|
226
|
+
* 不注入 / 抛错 / 找不到 → 视为 `running`(乐观:宁可显示在跑,
|
|
227
|
+
* 也不要凭空宣布结束)。
|
|
228
|
+
*/
|
|
229
|
+
getLifecycle?: (sessionId: string) => Promise<SessionLifecycle | undefined> | SessionLifecycle | undefined;
|
|
230
|
+
/** 读最近一条消息(`getMessages(id, {offset:-1, limit:1})`) */
|
|
231
|
+
getLastMessage?: (sessionId: string) => Promise<ActivityMessage | undefined> | ActivityMessage | undefined;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 造 `sessionId → ActivityProbeResult` 的异步 probe(无 task 场景)。
|
|
235
|
+
*
|
|
236
|
+
* ## 全链路 best-effort(与 task 路径同一纪律)
|
|
237
|
+
*
|
|
238
|
+
* - `getSession` 缺失 / 返回空 / 抛错 ⇒ `{activity: undefined}`(渲染「暂无数据」)
|
|
239
|
+
* - `getLastMessage` 抛错 ⇒ activity 保留(只是没有最近消息)
|
|
240
|
+
* - **永不抛**:刷新卡片绝不能因为取数失败而失败
|
|
241
|
+
*
|
|
242
|
+
* ## 为什么 `messageCount` 允许缺省
|
|
243
|
+
*
|
|
244
|
+
* `sessionComponent.get()` 在部分存储实现下可能不带 `messageCount`
|
|
245
|
+
* (旧行 / memory 后端)。此时用 0 起步而不是报错 —— 卡片仍能显示
|
|
246
|
+
* 「最近消息 + 最后写入时间」,那才是判断 LLM 有无推进的关键信号。
|
|
247
|
+
*/
|
|
248
|
+
export declare function createSessionActivityProbe(ports: SessionActivityPorts): (sessionId: string) => Promise<ActivityProbeResult>;
|
|
249
|
+
/**
|
|
250
|
+
* probe 依赖的最小端口集。
|
|
251
|
+
*
|
|
252
|
+
* 全部 optional:任一缺失只降级(返回 undefined),绝不抛 —— 与
|
|
253
|
+
* `getSubSessionActivity` 既有语义一致。
|
|
254
|
+
*/
|
|
255
|
+
export interface SubSessionActivityPorts {
|
|
256
|
+
/** 列后台任务(`BackgroundTaskManager.listTasks()`) */
|
|
257
|
+
listBackgroundTasks?: () => readonly BackgroundTaskLite[] | undefined;
|
|
258
|
+
/** 读 sub-session 消息数 + 最后写入时间(`getSubSessionActivity`) */
|
|
259
|
+
getSubSessionActivity?: (bgProcessId: string) => Promise<{
|
|
260
|
+
messageCount: number;
|
|
261
|
+
updatedAt: number;
|
|
262
|
+
} | undefined> | {
|
|
263
|
+
messageCount: number;
|
|
264
|
+
updatedAt: number;
|
|
265
|
+
} | undefined;
|
|
266
|
+
/** 读最近一条消息(`sessionComponent.getMessages(id, {offset:-1, limit:1})`) */
|
|
267
|
+
getLastMessage?: (subSessionId: string) => Promise<ActivityMessage | undefined> | ActivityMessage | undefined;
|
|
268
|
+
/** 该任务是否终态(决定 liveness 是否报 "已结束" 而不是 "疑似停滞") */
|
|
269
|
+
isTerminalTask?: (taskId: number) => Promise<boolean> | boolean;
|
|
270
|
+
}
|
|
271
|
+
/** session 层生命周期(★ CV BLOCKER-3:比 boolean 更有表达力) */
|
|
272
|
+
export type SessionLifecycle = "running" | "completed" | "timeout" | "failed" | "stopped";
|
|
273
|
+
/** probe 返回的完整活性快照(含生命周期,供渲染层用) */
|
|
274
|
+
export interface ActivityProbeResult {
|
|
275
|
+
activity?: SubSessionActivity;
|
|
276
|
+
/** 是否终态(便于老调用方继续用布尔判断) */
|
|
277
|
+
terminal: boolean;
|
|
278
|
+
/**
|
|
279
|
+
* ★ CV BLOCKER-3:具体生命周期。
|
|
280
|
+
*
|
|
281
|
+
* 为什么不能只有 `terminal: boolean`:用户明令「超时、完成等生命周期事件
|
|
282
|
+
* 也要体现在卡片上」—— 单一布尔把 timeout / failed / stopped 全压成
|
|
283
|
+
* 「已完成」,用户看不出到底是哪种结束。有 task 时可由 task.status 映射,
|
|
284
|
+
* **无 task 时必须由本字段承载**。
|
|
285
|
+
*
|
|
286
|
+
* 缺省(不传)⇒ 由 `terminal` 推导(true→completed / false→running)。
|
|
287
|
+
*/
|
|
288
|
+
lifecycle?: SessionLifecycle;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 造一个 `taskId → ActivityProbeResult` 的异步 probe。
|
|
292
|
+
*
|
|
293
|
+
* ## 全链路 best-effort
|
|
294
|
+
*
|
|
295
|
+
* 每一步都 try/catch 后降级:
|
|
296
|
+
* - `listBackgroundTasks` 缺失/抛错 → 返回 `{activity: undefined}`(渲染「暂无数据」)
|
|
297
|
+
* - 反查不到 bg 任务 → 同上
|
|
298
|
+
* - `getSubSessionActivity` 抛错/undefined → 仍返回 activity(messageCount=0,
|
|
299
|
+
* updatedAt 用 `Date.now()` 兜底会让 liveness 假报"活跃",所以此时**返回 undefined**)
|
|
300
|
+
* - `getLastMessage` 抛错 → activity 保留,只是没有 `lastMessage`
|
|
301
|
+
*
|
|
302
|
+
* ⇒ 卡片 patch 永远不会因为活性取数失败而失败。
|
|
303
|
+
*/
|
|
304
|
+
export declare function createActivityProbe(ports: SubSessionActivityPorts): (taskId: number) => Promise<ActivityProbeResult>;
|
|
305
|
+
/**
|
|
306
|
+
* 把一条 `SessionMessage` 归一化成 `ActivityMessage`(截断 + 角色提取)。
|
|
307
|
+
*
|
|
308
|
+
* 供两侧装配点在 `getLastMessage` 端口实现里复用,避免两份截断逻辑。
|
|
309
|
+
*
|
|
310
|
+
* ★ CV MINOR-2(#5420):空 content **不再返回 undefined** ——
|
|
311
|
+
* tool 角色消息常把 payload 放 parts、content 为空,此前被
|
|
312
|
+
* 装配点 filter 掉 ⇒ 「最新 5 条」排不满、与事实不符。
|
|
313
|
+
* 现在保留消息、content 用占位「(无文本内容)」,
|
|
314
|
+
* 条数与库内事实一致。原语义(活性面板的 lastMessage)
|
|
315
|
+
* 不受影响:那里拿到空占位同样可读。
|
|
316
|
+
*/
|
|
317
|
+
export declare function toActivityMessage(msg: {
|
|
318
|
+
role?: string;
|
|
319
|
+
content?: string;
|
|
320
|
+
timestamp?: number;
|
|
321
|
+
parts?: Array<Record<string, unknown>>;
|
|
322
|
+
} | undefined, max?: number): ActivityMessage | undefined;
|
|
323
|
+
/**
|
|
324
|
+
* 把 `BackgroundTaskManager.listTasks()` 里那条任务的 status 映射成 session 生命周期。
|
|
325
|
+
*
|
|
326
|
+
* 为什么单独抽出来并导出:飞书(`event-source-handlers.ts`)与企微
|
|
327
|
+
* (`wecom-cli-handler.ts`)两个装配点都要做同一件事,各写一份必然漂移 ——
|
|
328
|
+
* 同一个 session 在飞书显示「已完成」、企微显示「运行中」是最糟的体验。
|
|
329
|
+
*
|
|
330
|
+
* 未知/缺失 → `running`(乐观:宁可显示还在跑,也不要凭空宣布结束)。
|
|
331
|
+
*/
|
|
332
|
+
export declare function mapBgTaskStatusToSessionLifecycle(status: string | undefined): SessionLifecycle;
|
|
333
|
+
/** 最近消息默认条数(用户拍板:A 五条) */
|
|
334
|
+
export declare const RECENT_MESSAGE_LIMIT = 5;
|
|
335
|
+
/** 每条消息内容(reason)的截断长度(卡片窄,5 条 × 100 字不撑爆) */
|
|
336
|
+
export declare const RECENT_MESSAGE_MAX = 100;
|
|
337
|
+
/**
|
|
338
|
+
* role → 展示名称。
|
|
339
|
+
*
|
|
340
|
+
* session store 里**没有真实发送者名**,只有 role —— 用户拍板
|
|
341
|
+
* 「名称(发送者,session 消息取 role user/assistant)」,
|
|
342
|
+
* 所以名称 = role 的友好映射。未知 role 原样标注(不猜)。
|
|
343
|
+
*/
|
|
344
|
+
export declare function recentMessageRoleLabel(role: string | undefined): string;
|
|
345
|
+
/**
|
|
346
|
+
* 把最近消息列表渲染成卡片 markdown 行。
|
|
347
|
+
*
|
|
348
|
+
* ## 形态(★ Task #5420 返工:标题去重 + 工具名/reason)
|
|
349
|
+
*
|
|
350
|
+
* ```
|
|
351
|
+
* - 👤 用户:先完成工具改造吧…
|
|
352
|
+
* - 🤖 助手:task_create:建返工任务…
|
|
353
|
+
* ```
|
|
354
|
+
*
|
|
355
|
+
* 标题只出现在面板 title(「💬 最近消息」)一处;
|
|
356
|
+
* 列表首行**不再**重复「最近消息(最新 N 条):」
|
|
357
|
+
* (用户实测截图:标题重复两次)。
|
|
358
|
+
*
|
|
359
|
+
* ## 三态语义
|
|
360
|
+
*
|
|
361
|
+
* - 5 条 → 5 行
|
|
362
|
+
* - 不足 5 条 → 实际条数(诚实显示)
|
|
363
|
+
* - 0 条 / undefined → 单行「暂无消息」(不渲染空面板假象)
|
|
364
|
+
*
|
|
365
|
+
* 每条内容复用 `truncateActivityMessage`(flattenInline 单行化 +
|
|
366
|
+
* 词边界截断),与活性面板同口径,不漂移。
|
|
367
|
+
*/
|
|
368
|
+
export declare function formatRecentMessageLines(messages: ActivityMessage[] | undefined, max?: number): string[];
|
|
369
|
+
/** 一条工具调用的展示形态 */
|
|
370
|
+
export interface ToolCallView {
|
|
371
|
+
/** 工具名(ToolCallPart.toolName) */
|
|
372
|
+
toolName: string;
|
|
373
|
+
/** 调用意图(ToolCallPart.arguments.reason,#5422 注入);旧调用可能没有 */
|
|
374
|
+
reason?: string;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* 从 session 消息列表提取**工具调用**(tool-call part)。
|
|
378
|
+
*
|
|
379
|
+
* ## 真实结构(生产 sessions.db 勘察,#5420 返工)
|
|
380
|
+
*
|
|
381
|
+
* assistant 消息 `parts` 含 ToolCallPart:
|
|
382
|
+
* `{type:'tool-call', toolName, arguments, toolCallId, state}`。
|
|
383
|
+
* reason 是 #5422 注入的必填参数(`arguments.reason`);
|
|
384
|
+
* `arguments` 可能是对象或 JSON 字符串(两种都兼容)。
|
|
385
|
+
*
|
|
386
|
+
* ## 降级
|
|
387
|
+
*
|
|
388
|
+
* - 非工具消息(text/user/tool-result)→ 跳过
|
|
389
|
+
* - 无 reason 的旧调用 / arguments 解析失败 → `reason: undefined`
|
|
390
|
+
* (保留该调用,渲染层显示「—」;不抛)
|
|
391
|
+
*/
|
|
392
|
+
export declare function extractToolCallsFromMessages(messages: Array<{
|
|
393
|
+
role?: string;
|
|
394
|
+
content?: string;
|
|
395
|
+
timestamp?: number;
|
|
396
|
+
parts?: Array<Record<string, unknown>>;
|
|
397
|
+
}> | undefined): ToolCallView[];
|
|
398
|
+
/**
|
|
399
|
+
* 把工具调用列表渲染成卡片 markdown 行(★ #5420 返工)。
|
|
400
|
+
*
|
|
401
|
+
* ## 形态(用户澄清 08:21:展示工具名称和 reason)
|
|
402
|
+
*
|
|
403
|
+
* ```
|
|
404
|
+
* 🔧 最近工具调用(最新 5 条):
|
|
405
|
+
* - bash:查看目录结构
|
|
406
|
+
* - read_file:读源码定位根因
|
|
407
|
+
* ```
|
|
408
|
+
*
|
|
409
|
+
* ## 三态语义(与旧 formatRecentMessageLines 同纪律)
|
|
410
|
+
*
|
|
411
|
+
* - 有调用 → 标题(**只出现一次**,修旧版标题双写)+ 每行「工具名:reason」
|
|
412
|
+
* - 无 reason 的旧调用 → 「—」
|
|
413
|
+
* - 0 条 / undefined → 单行「暂无工具调用」(不渲染空面板假象)
|
|
414
|
+
*/
|
|
415
|
+
export declare function formatRecentToolCallLines(calls: ToolCallView[] | undefined, max?: number): string[];
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview WecomCardStateStore — 企微任务进展消息的持久化记录(Task #5193)
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么需要(与 lark-cards.db 同因)
|
|
5
|
+
*
|
|
6
|
+
* 企微 wecom-cli **没有** interactive card / 消息更新 API ——
|
|
7
|
+
* 进展卡片是**每次更新发一条新 markdown**。但节流判定、
|
|
8
|
+
* 「无卡片不凭空发卡」守卫、终态收尾都需要知道
|
|
9
|
+
* 「这个 task 是否发过、发到哪个 chat、当前什么状态」。
|
|
10
|
+
*
|
|
11
|
+
* ## 与 lark-card-state-store 的关系
|
|
12
|
+
*
|
|
13
|
+
* 结构对齐(同一套接口语义),但**独立库文件**(wecom-cards.db):
|
|
14
|
+
* - lark-cards.db 有 messageId(可 patch 同一张卡)
|
|
15
|
+
* - wecom-cards.db 无 messageId 语义(每条都是新消息),
|
|
16
|
+
* 记录 `taskId → chatId / status / timeline / updateCount`
|
|
17
|
+
*
|
|
18
|
+
* ## 记录生命周期
|
|
19
|
+
*
|
|
20
|
+
* ```
|
|
21
|
+
* create 成功 → upsert({status:"open"})
|
|
22
|
+
* update 成功 → touch(updateCount+1,timeline 追加)
|
|
23
|
+
* 节流 → touch timeline(数据不丢,只是不发)
|
|
24
|
+
* completed/failed/cancelled → markClosed()
|
|
25
|
+
* delete → markClosed()(保留记录便于追溯)
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* ★ 终态**不删除**记录:保留 chatId / timeline 便于事后追溯。
|
|
29
|
+
*/
|
|
30
|
+
/** 记录状态(与 LarkCardRecordStatus 语义一致) */
|
|
31
|
+
export type WecomCardRecordStatus = "open" | "paused" | "closed";
|
|
32
|
+
/** 一条企微卡片持久化记录 */
|
|
33
|
+
export interface WecomCardState {
|
|
34
|
+
/** 任务 ID(主键) */
|
|
35
|
+
taskId: number;
|
|
36
|
+
/** 企微 chat_id(单聊=userid,群聊=chatid) */
|
|
37
|
+
chatId: string;
|
|
38
|
+
/** 当前 pin 状态(企微无 pin,恒 false;保留字段对齐接口) */
|
|
39
|
+
pinned: boolean;
|
|
40
|
+
/** 记录状态 */
|
|
41
|
+
status: WecomCardRecordStatus;
|
|
42
|
+
/** 首次发卡时间(ms epoch) */
|
|
43
|
+
createdAt: number;
|
|
44
|
+
/** 最后更新时间(ms epoch) */
|
|
45
|
+
updatedAt: number;
|
|
46
|
+
/** 终结时间(ms epoch);仅 status=closed 有值 */
|
|
47
|
+
closedAt?: number;
|
|
48
|
+
/** 已发送的更新条数(诊断用) */
|
|
49
|
+
updateCount: number;
|
|
50
|
+
/**
|
|
51
|
+
* 累积时间线(JSON 字符串:Milestone[])
|
|
52
|
+
*
|
|
53
|
+
* ★ 企微每次发新消息都要带**完整历史时间线**(无 patch 能力),
|
|
54
|
+
* 所以时间线必须持久化 —— 重启后第一条 update 也要能渲染
|
|
55
|
+
* 完整历史。
|
|
56
|
+
*/
|
|
57
|
+
timeline?: string;
|
|
58
|
+
}
|
|
59
|
+
/** 持久化端口(hook 只依赖这个接口,便于测试注入内存实现) */
|
|
60
|
+
export interface WecomCardStateStore {
|
|
61
|
+
/** 写入或覆盖一条记录 */
|
|
62
|
+
upsert(state: WecomCardState): void;
|
|
63
|
+
/** 按 taskId 读取 */
|
|
64
|
+
get(taskId: number): WecomCardState | undefined;
|
|
65
|
+
/** 列出全部记录(诊断用) */
|
|
66
|
+
list(): WecomCardState[];
|
|
67
|
+
/** 标记为暂停(保留记录) */
|
|
68
|
+
markPaused(taskId: number): void;
|
|
69
|
+
/** 从暂停/终结翻回进行中(resume) */
|
|
70
|
+
markReopened(taskId: number): void;
|
|
71
|
+
/** 标记终结(completed / failed / cancelled / deleted) */
|
|
72
|
+
markClosed(taskId: number): void;
|
|
73
|
+
/** 真删除(仅测试/手工清理用) */
|
|
74
|
+
delete(taskId: number): void;
|
|
75
|
+
/** 释放资源 */
|
|
76
|
+
close(): void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 内存实现(测试 / 降级)
|
|
80
|
+
*
|
|
81
|
+
* ★ 独立导出(`createMemoryWecomCardStore`),测试直接用,
|
|
82
|
+
* 不经过 `createWecomCardStateStore` 的测试环境守卫。
|
|
83
|
+
*/
|
|
84
|
+
export declare class MemoryWecomCardStateStore implements WecomCardStateStore {
|
|
85
|
+
private readonly records;
|
|
86
|
+
upsert(state: WecomCardState): void;
|
|
87
|
+
get(taskId: number): WecomCardState | undefined;
|
|
88
|
+
list(): WecomCardState[];
|
|
89
|
+
markPaused(taskId: number): void;
|
|
90
|
+
markReopened(taskId: number): void;
|
|
91
|
+
markClosed(taskId: number): void;
|
|
92
|
+
delete(taskId: number): void;
|
|
93
|
+
close(): void;
|
|
94
|
+
}
|
|
95
|
+
/** 测试工厂:内存 store */
|
|
96
|
+
export declare function createMemoryWecomCardStore(): WecomCardStateStore;
|
|
97
|
+
export declare class SqliteWecomCardStateStore implements WecomCardStateStore {
|
|
98
|
+
private readonly db;
|
|
99
|
+
constructor(dbPath: string);
|
|
100
|
+
upsert(state: WecomCardState): void;
|
|
101
|
+
get(taskId: number): WecomCardState | undefined;
|
|
102
|
+
list(): WecomCardState[];
|
|
103
|
+
markPaused(taskId: number): void;
|
|
104
|
+
markReopened(taskId: number): void;
|
|
105
|
+
markClosed(taskId: number): void;
|
|
106
|
+
delete(taskId: number): void;
|
|
107
|
+
close(): void;
|
|
108
|
+
}
|
|
109
|
+
/** 默认库文件路径:`$HOME/.local/share/roy-agent/wecom-cards.db` */
|
|
110
|
+
export declare function getDefaultWecomCardDbPath(): string;
|
|
111
|
+
/**
|
|
112
|
+
* 创建持久化 store
|
|
113
|
+
*
|
|
114
|
+
* ★ 失败降级:SQLite 打不开时**不抛错**,回落内存实现并 warn ——
|
|
115
|
+
* 卡片该发还得发,绝不能因为持久化层故障让通知路径整体崩掉。
|
|
116
|
+
*
|
|
117
|
+
* ★ 测试环境保护(与 createLarkCardStateStore 同配方):
|
|
118
|
+
* bun test 环境 + 未显式指定路径 → 一律返回内存实现(零副作用),
|
|
119
|
+
* 防止测试污染真实用户库。
|
|
120
|
+
*/
|
|
121
|
+
export declare function createWecomCardStateStore(dbPath?: string): WecomCardStateStore;
|