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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/config/config-protection.d.ts +89 -0
  2. package/dist/config/index.d.ts +1 -0
  3. package/dist/config/index.js +16 -0
  4. package/dist/env/agent/agent-component.d.ts +42 -21
  5. package/dist/env/agent/agent-component.js +3 -3
  6. package/dist/env/agent/index.js +3 -3
  7. package/dist/env/agent/types.d.ts +32 -12
  8. package/dist/env/error-reply.d.ts +6 -0
  9. package/dist/env/error-reply.js +1 -1
  10. package/dist/env/event-source/bounty-im-handler.d.ts +8 -0
  11. package/dist/env/event-source/es-command.d.ts +39 -0
  12. package/dist/env/event-source/es-plugins.d.ts +68 -0
  13. package/dist/env/event-source/event-source-component.d.ts +15 -0
  14. package/dist/env/event-source/event-source-handlers.d.ts +68 -0
  15. package/dist/env/event-source/event-source-handlers.js +22 -10
  16. package/dist/env/event-source/index.d.ts +5 -1
  17. package/dist/env/event-source/index.js +59 -10
  18. package/dist/env/event-source/types.d.ts +101 -1
  19. package/dist/env/event-source/wecom-card-action-handler.d.ts +141 -0
  20. package/dist/env/event-source/wecom-card-builder.d.ts +220 -0
  21. package/dist/env/event-source/wecom-cli-handler.d.ts +266 -0
  22. package/dist/env/event-source/wecom-cli-sender.d.ts +88 -0
  23. package/dist/env/event-source/wecom-media.d.ts +130 -0
  24. package/dist/env/event-source/wecom-respond-registry.d.ts +151 -0
  25. package/dist/env/event-source/wecom-ws-bridge.d.ts +213 -0
  26. package/dist/env/index.js +19 -17
  27. package/dist/env/task/backend/index.js +37 -1
  28. package/dist/env/task/backend/task-store-backend.d.ts +12 -0
  29. package/dist/env/task/backend/types.d.ts +19 -0
  30. package/dist/env/task/delegate/delegate-tool.d.ts +62 -2
  31. package/dist/env/task/delegate/delegate-tool.js +3 -1
  32. package/dist/env/task/delegate/index.js +1 -1
  33. package/dist/env/task/index.js +4 -4
  34. package/dist/env/task/mcp/index.js +3 -0
  35. package/dist/env/task/mcp/task-mcp-server.d.ts +1 -1
  36. package/dist/env/task/plugins/fake-user-message-helper.d.ts +10 -2
  37. package/dist/env/task/plugins/index.d.ts +3 -0
  38. package/dist/env/task/plugins/index.js +36 -4
  39. package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +41 -2
  40. package/dist/env/task/plugins/lark-card-patch-notifier.js +1 -1
  41. package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +16 -0
  42. package/dist/env/task/plugins/task-op-watermark-plugin.d.ts +112 -0
  43. package/dist/env/task/plugins/wecom-card-state-store.d.ts +121 -0
  44. package/dist/env/task/plugins/wecom-task-notify-plugin.d.ts +224 -0
  45. package/dist/env/task/storage/index.js +2 -2
  46. package/dist/env/task/storage/sqlite-task-store.d.ts +54 -0
  47. package/dist/env/task/task-component.d.ts +16 -0
  48. package/dist/env/task/tools/operation/operation-types.d.ts +13 -13
  49. package/dist/env/tool/built-in/index.d.ts +2 -0
  50. package/dist/env/tool/built-in/index.js +6 -1
  51. package/dist/env/tool/index.js +3 -2
  52. package/dist/env/workflow/index.js +3 -3
  53. package/dist/env/workflow/plugins/index.js +2 -2
  54. package/dist/env/workflow/tools/index.js +2 -2
  55. package/dist/env/workflow/types/event.d.ts +4 -4
  56. package/dist/index.d.ts +1 -1
  57. package/dist/index.js +71 -20
  58. package/dist/shared/@ai-setting/{roy-agent-core-v87njmq7.js → roy-agent-core-0dh8npnz.js} +1 -1
  59. package/dist/shared/@ai-setting/roy-agent-core-2stt4rp5.js +134 -0
  60. package/dist/shared/@ai-setting/{roy-agent-core-mww0mnd9.js → roy-agent-core-7bpj859q.js} +1 -1
  61. package/dist/shared/@ai-setting/{roy-agent-core-nmx9934r.js → roy-agent-core-7dd0s3xb.js} +15 -5
  62. package/dist/shared/@ai-setting/{roy-agent-core-ghnagzrh.js → roy-agent-core-b31v3qpv.js} +853 -48
  63. package/dist/shared/@ai-setting/{roy-agent-core-cxy143m4.js → roy-agent-core-begm7ytz.js} +63 -2
  64. package/dist/shared/@ai-setting/{roy-agent-core-aa2j7bk9.js → roy-agent-core-cha0jc7n.js} +40 -26
  65. package/dist/shared/@ai-setting/{roy-agent-core-p3zv1mx5.js → roy-agent-core-d1n4jbza.js} +1 -1
  66. package/dist/shared/@ai-setting/{roy-agent-core-q5r75vev.js → roy-agent-core-dkrvmjsh.js} +17 -75
  67. package/dist/shared/@ai-setting/{roy-agent-core-abqhkbn3.js → roy-agent-core-ezvpreqz.js} +23 -0
  68. package/dist/shared/@ai-setting/{roy-agent-core-99nwgj84.js → roy-agent-core-fzw9xzm1.js} +1 -1
  69. package/dist/shared/@ai-setting/roy-agent-core-h21gngxf.js +247 -0
  70. package/dist/shared/@ai-setting/roy-agent-core-m3vjepzz.js +544 -0
  71. package/dist/shared/@ai-setting/{roy-agent-core-zycadbpx.js → roy-agent-core-p6ks90tb.js} +1 -1
  72. package/dist/shared/@ai-setting/{roy-agent-core-ntq0gz7q.js → roy-agent-core-q813vafd.js} +1 -1
  73. package/dist/shared/@ai-setting/{roy-agent-core-z43aj03k.js → roy-agent-core-qk9hd89p.js} +21 -2
  74. package/dist/shared/@ai-setting/{roy-agent-core-6cy43nss.js → roy-agent-core-rykf7c04.js} +49 -9
  75. package/dist/shared/@ai-setting/{roy-agent-core-8ez6mccb.js → roy-agent-core-tk98f92b.js} +4 -0
  76. package/dist/shared/@ai-setting/{roy-agent-core-bxkjk7zx.js → roy-agent-core-vx8zc847.js} +4 -1
  77. package/dist/shared/@ai-setting/roy-agent-core-vyr6svt6.js +31 -0
  78. package/dist/shared/@ai-setting/{roy-agent-core-t3rrb959.js → roy-agent-core-xf6z96qy.js} +2086 -163
  79. package/package.json +10 -1
  80. package/dist/shared/@ai-setting/roy-agent-core-f77k3gqt.js +0 -71
  81. /package/dist/shared/@ai-setting/{roy-agent-core-b4x6x4k2.js → roy-agent-core-m1fgtnrc.js} +0 -0
  82. /package/dist/shared/@ai-setting/{roy-agent-core-v85n42ve.js → roy-agent-core-ze5cqpf2.js} +0 -0
@@ -1,3 +1,10 @@
1
+ import {
2
+ WecomCardActionKey,
3
+ buildWecomButtonInteractionCard,
4
+ buildWecomTextNoticeCard,
5
+ sendWecomReply,
6
+ sendWecomTemplateCard
7
+ } from "./roy-agent-core-m3vjepzz.js";
1
8
  import {
2
9
  getSharedEventStore
3
10
  } from "./roy-agent-core-na18zwj0.js";
@@ -7,7 +14,7 @@ import {
7
14
  import {
8
15
  createCompatibleDatabase,
9
16
  init_sqlite_task_store
10
- } from "./roy-agent-core-abqhkbn3.js";
17
+ } from "./roy-agent-core-ezvpreqz.js";
11
18
  import {
12
19
  TaskHookPoints
13
20
  } from "./roy-agent-core-1pg0fepg.js";
@@ -24,13 +31,13 @@ import {
24
31
  import {
25
32
  LarkCardPatchNotifier,
26
33
  humanizeValue
27
- } from "./roy-agent-core-nmx9934r.js";
34
+ } from "./roy-agent-core-7dd0s3xb.js";
28
35
  import {
29
36
  validateWorkflowDefinition
30
37
  } from "./roy-agent-core-1v4g0d4x.js";
31
38
  import {
32
39
  enqueueToAddFakeUserMessage
33
- } from "./roy-agent-core-f77k3gqt.js";
40
+ } from "./roy-agent-core-h21gngxf.js";
34
41
  import {
35
42
  BasePlugin
36
43
  } from "./roy-agent-core-az13yzmc.js";
@@ -38,7 +45,8 @@ import {
38
45
  init_context
39
46
  } from "./roy-agent-core-qqaak24r.js";
40
47
  import {
41
- getEnvContextOrEmpty
48
+ getEnvContextOrEmpty,
49
+ init_env_context
42
50
  } from "./roy-agent-core-7cbpmk4q.js";
43
51
  import {
44
52
  TracedAs,
@@ -2384,6 +2392,66 @@ class LarkCliTaskNotifyHook {
2384
2392
  logger7.warn(`repinAfterResume failed (non-fatal) for task #${task.id}: ${err.message}`);
2385
2393
  }
2386
2394
  }
2395
+ async reopenReuseCard(task, persisted, changes) {
2396
+ if (!this.cardNotifier)
2397
+ return false;
2398
+ const messageId = persisted.messageId;
2399
+ if (!messageId) {
2400
+ logger7.debug(`reopenReuseCard: task #${task.id} 记录无 messageId → 走建卡路径`);
2401
+ return false;
2402
+ }
2403
+ const ctx = getEnvContextOrEmpty();
2404
+ const profile = this.resolveProfile(task, ctx);
2405
+ this.cardNotifier.unfinaliseCard?.(messageId);
2406
+ const reopenMilestone = {
2407
+ title: "\uD83D\uDD04 任务重新打开",
2408
+ timestamp: new Date().toISOString(),
2409
+ description: task.current_status || undefined
2410
+ };
2411
+ const changeKeys = Object.keys(changes ?? {});
2412
+ const reopenSteps = changeKeys.length > 0 ? [
2413
+ {
2414
+ title: "任务重新打开",
2415
+ timestamp: new Date().toISOString(),
2416
+ description: changeKeys.map((k) => `${k}=${humanizeValue(changes[k], 80)}`).join(" · ")
2417
+ }
2418
+ ] : [];
2419
+ const hasState = this.cardNotifier.exportState?.(messageId) !== undefined;
2420
+ if (!hasState) {
2421
+ const persistedRec = this.cardStateStore?.get(task.id);
2422
+ if (persistedRec?.timeline) {
2423
+ this.hydrateNotifierState(persistedRec);
2424
+ logger7.debug(`reopenReuseCard: task #${task.id} 内存态缺失 → ` + `从持久层快照恢复卡片内容(历史时间线保留)`);
2425
+ }
2426
+ }
2427
+ const merged = this.cardNotifier.mergeTimelineState?.(messageId, [reopenMilestone], reopenSteps) ?? false;
2428
+ try {
2429
+ await this.cardNotifier.updateTask({
2430
+ messageId,
2431
+ profile,
2432
+ status: "running",
2433
+ meta: this.buildTaskMeta(task),
2434
+ progress: typeof task.progress === "number" && task.progress > 0 ? task.progress : undefined,
2435
+ milestones: merged ? [] : [reopenMilestone],
2436
+ processSteps: merged ? [] : reopenSteps
2437
+ });
2438
+ } catch (err) {
2439
+ logger7.warn(`reopenReuseCard: task #${task.id} 原卡 patch 失败: ` + `${err.message}`);
2440
+ return false;
2441
+ }
2442
+ this.persistReopened(task.id);
2443
+ this.refreshTimeline(task.id);
2444
+ try {
2445
+ await this.repinAfterResume(task, {
2446
+ ...persisted,
2447
+ status: "open"
2448
+ });
2449
+ } catch (pinErr) {
2450
+ logger7.warn(`reopenReuseCard: task #${task.id} 重新 pin 失败(非致命,卡片内容已更新): ` + `${pinErr.message}`);
2451
+ }
2452
+ logger7.info(`reopenReuseCard: task #${task.id} 已复用原卡 ${messageId} ` + `(patch + reopen 里程碑 + 重新 pin)`);
2453
+ return true;
2454
+ }
2387
2455
  buildCardState(task, messageId, chatId, profile, pinned) {
2388
2456
  let existing;
2389
2457
  try {
@@ -2542,7 +2610,12 @@ class LarkCliTaskNotifyHook {
2542
2610
  logger7.debug(`sendCardUpdate: task #${task.id} 卡片已终结(closed)且任务仍终态 → skip patch`);
2543
2611
  return;
2544
2612
  }
2545
- logger7.info(`sendCardUpdate: task #${task.id} 已 reopen(closed 记录 + 当前 status=${task.status})` + `→ 重新建卡`);
2613
+ const reused = await this.reopenReuseCard(task, persisted, changes);
2614
+ if (reused) {
2615
+ this.cardMessageIds.set(task.id, persisted.messageId);
2616
+ return;
2617
+ }
2618
+ logger7.info(`sendCardUpdate: task #${task.id} 已 reopen 但原卡 patch 失败 → 降级发新卡`);
2546
2619
  this.cardMessageIds.delete(task.id);
2547
2620
  this.dropStaleMessageId(task.id);
2548
2621
  }
@@ -2605,7 +2678,7 @@ class LarkCliTaskNotifyHook {
2605
2678
  messageId,
2606
2679
  chatId,
2607
2680
  profile,
2608
- status: "running",
2681
+ status: String(task.status) === "to_validate" ? "to_validate" : "running",
2609
2682
  meta: this.buildTaskMeta(task),
2610
2683
  progress: typeof task.progress === "number" && task.progress > 0 ? task.progress : undefined,
2611
2684
  milestones: [],
@@ -2770,12 +2843,744 @@ __legacyDecorateClassTS([
2770
2843
  function createLarkCliTaskNotifyHook(config = {}) {
2771
2844
  return new LarkCliTaskNotifyHook(config);
2772
2845
  }
2846
+ // src/env/task/plugins/wecom-task-notify-plugin.ts
2847
+ init_logger();
2848
+ init_env_context();
2849
+
2850
+ // src/env/task/plugins/wecom-card-state-store.ts
2851
+ init_logger();
2852
+ init_sqlite_task_store();
2853
+ import { join as join2 } from "path";
2854
+ var logger8 = createLogger("wecom:card-state");
2855
+
2856
+ class MemoryWecomCardStateStore {
2857
+ records = new Map;
2858
+ upsert(state) {
2859
+ this.records.set(state.taskId, { ...state });
2860
+ }
2861
+ get(taskId) {
2862
+ const r = this.records.get(taskId);
2863
+ return r ? { ...r } : undefined;
2864
+ }
2865
+ list() {
2866
+ return [...this.records.values()].map((r) => ({ ...r }));
2867
+ }
2868
+ markPaused(taskId) {
2869
+ const r = this.records.get(taskId);
2870
+ if (!r)
2871
+ return;
2872
+ r.status = "paused";
2873
+ r.updatedAt = Date.now();
2874
+ }
2875
+ markReopened(taskId) {
2876
+ const r = this.records.get(taskId);
2877
+ if (!r)
2878
+ return;
2879
+ r.status = "open";
2880
+ delete r.closedAt;
2881
+ r.updatedAt = Date.now();
2882
+ }
2883
+ markClosed(taskId) {
2884
+ const r = this.records.get(taskId);
2885
+ if (!r)
2886
+ return;
2887
+ const now = Date.now();
2888
+ r.status = "closed";
2889
+ r.closedAt = now;
2890
+ r.updatedAt = now;
2891
+ }
2892
+ delete(taskId) {
2893
+ this.records.delete(taskId);
2894
+ }
2895
+ close() {
2896
+ this.records.clear();
2897
+ }
2898
+ }
2899
+ function createMemoryWecomCardStore() {
2900
+ return new MemoryWecomCardStateStore;
2901
+ }
2902
+ function rowToState2(row) {
2903
+ const state = {
2904
+ taskId: row.task_id,
2905
+ chatId: row.chat_id,
2906
+ pinned: row.pinned === 1,
2907
+ status: row.status ?? "open",
2908
+ createdAt: row.created_at,
2909
+ updatedAt: row.updated_at,
2910
+ updateCount: row.update_count ?? 0
2911
+ };
2912
+ if (row.closed_at !== null && row.closed_at !== undefined) {
2913
+ state.closedAt = row.closed_at;
2914
+ }
2915
+ if (row.timeline)
2916
+ state.timeline = row.timeline;
2917
+ return state;
2918
+ }
2919
+
2920
+ class SqliteWecomCardStateStore {
2921
+ db;
2922
+ constructor(dbPath) {
2923
+ if (dbPath !== ":memory:") {
2924
+ const { mkdirSync } = __require("fs");
2925
+ const dir = __require("path").dirname(dbPath);
2926
+ try {
2927
+ mkdirSync(dir, { recursive: true });
2928
+ } catch {}
2929
+ }
2930
+ this.db = createCompatibleDatabase(dbPath);
2931
+ try {
2932
+ this.db.run("PRAGMA journal_mode = WAL");
2933
+ this.db.run("PRAGMA busy_timeout = 5000");
2934
+ this.db.run("PRAGMA synchronous = NORMAL");
2935
+ } catch {}
2936
+ this.db.run(`
2937
+ CREATE TABLE IF NOT EXISTS wecom_card (
2938
+ task_id INTEGER PRIMARY KEY,
2939
+ chat_id TEXT NOT NULL,
2940
+ pinned INTEGER NOT NULL DEFAULT 0,
2941
+ status TEXT NOT NULL DEFAULT 'open',
2942
+ created_at INTEGER NOT NULL,
2943
+ updated_at INTEGER NOT NULL,
2944
+ closed_at INTEGER,
2945
+ update_count INTEGER NOT NULL DEFAULT 0,
2946
+ timeline TEXT
2947
+ )
2948
+ `);
2949
+ this.db.run("CREATE INDEX IF NOT EXISTS idx_wecom_card_status ON wecom_card(status)");
2950
+ }
2951
+ upsert(state) {
2952
+ this.db.prepare(`INSERT INTO wecom_card
2953
+ (task_id, chat_id, pinned, status, created_at, updated_at, closed_at, update_count, timeline)
2954
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
2955
+ ON CONFLICT(task_id) DO UPDATE SET
2956
+ chat_id = excluded.chat_id,
2957
+ pinned = excluded.pinned,
2958
+ status = excluded.status,
2959
+ updated_at = excluded.updated_at,
2960
+ closed_at = excluded.closed_at,
2961
+ update_count = excluded.update_count,
2962
+ timeline = excluded.timeline`).run(state.taskId, state.chatId, state.pinned ? 1 : 0, state.status, state.createdAt, state.updatedAt, state.closedAt ?? null, state.updateCount, state.timeline ?? null);
2963
+ }
2964
+ get(taskId) {
2965
+ const row = this.db.prepare("SELECT * FROM wecom_card WHERE task_id = ?").get(taskId);
2966
+ return row ? rowToState2(row) : undefined;
2967
+ }
2968
+ list() {
2969
+ const rows = this.db.prepare("SELECT * FROM wecom_card ORDER BY task_id ASC").all();
2970
+ return rows.map(rowToState2);
2971
+ }
2972
+ markPaused(taskId) {
2973
+ this.db.prepare("UPDATE wecom_card SET status = 'paused', updated_at = ? WHERE task_id = ?").run(Date.now(), taskId);
2974
+ }
2975
+ markReopened(taskId) {
2976
+ this.db.prepare("UPDATE wecom_card SET status = 'open', closed_at = NULL, updated_at = ? WHERE task_id = ?").run(Date.now(), taskId);
2977
+ }
2978
+ markClosed(taskId) {
2979
+ const now = Date.now();
2980
+ this.db.prepare("UPDATE wecom_card SET status = 'closed', closed_at = ?, updated_at = ? WHERE task_id = ?").run(now, now, taskId);
2981
+ }
2982
+ delete(taskId) {
2983
+ this.db.prepare("DELETE FROM wecom_card WHERE task_id = ?").run(taskId);
2984
+ }
2985
+ close() {
2986
+ try {
2987
+ this.db.close?.();
2988
+ } catch {}
2989
+ }
2990
+ }
2991
+ function getDefaultWecomCardDbPath() {
2992
+ const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
2993
+ return join2(home, ".local", "share", "roy-agent", "wecom-cards.db");
2994
+ }
2995
+ function createWecomCardStateStore(dbPath) {
2996
+ const isTestEnv = "BUN_TEST_WORKER_ID" in process.env;
2997
+ if (!dbPath && !process.env.ROY_AGENT_WECOM_CARD_DB && isTestEnv) {
2998
+ return new MemoryWecomCardStateStore;
2999
+ }
3000
+ const path = dbPath ?? process.env.ROY_AGENT_WECOM_CARD_DB ?? getDefaultWecomCardDbPath();
3001
+ try {
3002
+ return new SqliteWecomCardStateStore(path);
3003
+ } catch (err) {
3004
+ logger8.warn(`WecomCardStateStore: sqlite unavailable at ${path} (${err.message}) ` + `→ falling back to in-memory (卡片仍可发,但重启后时间线丢失)`);
3005
+ return new MemoryWecomCardStateStore;
3006
+ }
3007
+ }
3008
+
3009
+ // src/env/task/plugins/wecom-task-notify-plugin.ts
3010
+ var logger9 = createLogger("wecom:task-notify");
3011
+ var WecomNotifyType = {
3012
+ TASK_CREATED: "task_created",
3013
+ TASK_UPDATED: "task_updated",
3014
+ TASK_COMPLETED: "task_completed",
3015
+ OPERATION_CREATED: "operation_created"
3016
+ };
3017
+ var WECOM_EVENT_TYPE = "event-source.event.wecom-cli";
3018
+ var WECOM_CHANNEL_TYPE = "wecom-cli";
3019
+ function extractWecomFields(env, fallbackSourceId) {
3020
+ const evt = env.envEvent;
3021
+ const rcType = env.replyChannel?.type;
3022
+ const isWecom = evt?.type === WECOM_EVENT_TYPE || rcType === WECOM_CHANNEL_TYPE;
3023
+ if (!isWecom)
3024
+ return { isWecom: false };
3025
+ const chatId = env.replyChannel?.chatId || (typeof evt?.metadata?.chatId === "string" ? evt.metadata.chatId : undefined);
3026
+ const sourceId = (typeof evt?.metadata?.sourceId === "string" ? evt.metadata.sourceId : undefined) ?? fallbackSourceId ?? undefined;
3027
+ return { isWecom: true, chatId, sourceId };
3028
+ }
3029
+ function statusLabelOf(task, terminalStatus) {
3030
+ const s = String(terminalStatus ?? task.status ?? "");
3031
+ switch (s) {
3032
+ case "todo":
3033
+ return "\uD83D\uDCDD 待办";
3034
+ case "active":
3035
+ return "\uD83D\uDD04 进行中";
3036
+ case "to_validate":
3037
+ return "\uD83D\uDD0D 待验证";
3038
+ case "completed":
3039
+ return "\uD83C\uDF89 已完成";
3040
+ case "paused":
3041
+ return "⏸️ 已暂停";
3042
+ case "cancelled":
3043
+ return "\uD83D\uDEAB 已取消";
3044
+ case "archived":
3045
+ return "\uD83D\uDCE6 已归档";
3046
+ case "failed":
3047
+ case "timeout":
3048
+ return "\uD83D\uDEA8 已失败";
3049
+ default:
3050
+ return "\uD83D\uDD04 进行中";
3051
+ }
3052
+ }
3053
+ function isTerminalUpdate(task, changes) {
3054
+ const effective = String(changes?.status ?? task.status ?? "");
3055
+ return effective === "completed" || effective === "cancelled" || effective === "failed" || effective === "timeout" || effective === "archived" || effective === "paused";
3056
+ }
3057
+ function isHardTerminal(status) {
3058
+ return status === "completed" || status === "cancelled" || status === "failed" || status === "timeout" || status === "archived";
3059
+ }
3060
+ function clip(text, max = 200) {
3061
+ if (!text)
3062
+ return "";
3063
+ const t = text.trim();
3064
+ return t.length > max ? `${t.slice(0, max)}…` : t;
3065
+ }
3066
+ function buildWecomTaskCard(input) {
3067
+ const { task, statusLabel, milestones } = input;
3068
+ const lines = [];
3069
+ lines.push(`### \uD83D\uDCCB Task #${task.id} · ${task.title}`);
3070
+ lines.push("");
3071
+ const progress = typeof task.progress === "number" && task.progress > 0 ? `(${task.progress}%)` : "";
3072
+ lines.push(`**状态**:${statusLabel}${progress}`);
3073
+ if (input.terminal) {
3074
+ lines.push(`---`);
3075
+ }
3076
+ lines.push("");
3077
+ const desc = clip(task.description, 120);
3078
+ if (desc) {
3079
+ lines.push(`> ${desc}`);
3080
+ lines.push("");
3081
+ }
3082
+ if (input.changeSummary) {
3083
+ lines.push(`**本次更新**:${clip(input.changeSummary, 120)}`);
3084
+ lines.push("");
3085
+ }
3086
+ if (milestones.length > 0) {
3087
+ lines.push(`**时间线**`);
3088
+ for (const m of milestones) {
3089
+ const ts = formatTs(m.timestamp);
3090
+ const d = m.description ? `:${clip(m.description, 100)}` : "";
3091
+ lines.push(`- \uD83D\uDD50 ${ts} — ${m.title}${d}`);
3092
+ }
3093
+ lines.push("");
3094
+ }
3095
+ return lines.join(`
3096
+ `).trim();
3097
+ }
3098
+ function formatTs(iso) {
3099
+ try {
3100
+ const d = new Date(iso);
3101
+ if (isNaN(d.getTime()))
3102
+ return iso;
3103
+ const pad = (n) => String(n).padStart(2, "0");
3104
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` + `${pad(d.getHours())}:${pad(d.getMinutes())}`;
3105
+ } catch {
3106
+ return iso;
3107
+ }
3108
+ }
3109
+ function milestoneFromOperation(op) {
3110
+ const raw = op;
3111
+ const title = op.milestoneTitle ?? raw.action_title ?? String(op.milestoneType ?? "里程碑");
3112
+ const detail = op.milestoneDescription ?? raw.action_description ?? undefined;
3113
+ return {
3114
+ title,
3115
+ timestamp: op.timestamp ?? new Date().toISOString(),
3116
+ description: detail ?? op.processDescription ?? undefined
3117
+ };
3118
+ }
3119
+
3120
+ class WecomTaskNotifyHook {
3121
+ name;
3122
+ priority;
3123
+ description;
3124
+ pluginName;
3125
+ sourceId;
3126
+ config;
3127
+ sender;
3128
+ cardStateStore;
3129
+ lastNotifyTimestamps = new Map;
3130
+ constructor(config = {}) {
3131
+ this.name = config.pluginName || "WecomTaskNotifyHook";
3132
+ this.priority = config.priority ?? 5;
3133
+ this.description = "Wecom 任务进展通知 Hook(markdown 卡片)";
3134
+ this.pluginName = config.pluginName;
3135
+ this.sourceId = config.sourceId;
3136
+ this.config = {
3137
+ enabled: true,
3138
+ priority: 5,
3139
+ notifyTypes: [
3140
+ WecomNotifyType.TASK_CREATED,
3141
+ WecomNotifyType.TASK_UPDATED,
3142
+ WecomNotifyType.TASK_COMPLETED,
3143
+ WecomNotifyType.OPERATION_CREATED
3144
+ ],
3145
+ ...config
3146
+ };
3147
+ this.sender = config.sender ?? {
3148
+ send: (input) => this.defaultSend(input)
3149
+ };
3150
+ this.cardStateStore = config.cardStateStore ?? createWecomCardStateStore();
3151
+ }
3152
+ async defaultSend(input) {
3153
+ const env = getEnvContextOrEmpty();
3154
+ const { sourceId } = extractWecomFields(env, this.sourceId);
3155
+ return sendWecomReply({
3156
+ sourceId: sourceId ?? "unknown",
3157
+ rawChatId: input.chatId,
3158
+ content: input.content
3159
+ });
3160
+ }
3161
+ async execute(ctx) {
3162
+ if (!this.config.enabled)
3163
+ return;
3164
+ const env = getEnvContextOrEmpty();
3165
+ if (this.pluginName && this.sourceId) {
3166
+ const key = `${this.sourceId}:${this.pluginName}`;
3167
+ if (env.pluginEnabled && env.pluginEnabled[key] !== true) {
3168
+ logger9.debug(`Plugin not enabled for source: ${key}`);
3169
+ return;
3170
+ }
3171
+ }
3172
+ const fields = extractWecomFields(env, this.sourceId);
3173
+ if (!fields.isWecom) {
3174
+ logger9.debug("not a wecom event, skip");
3175
+ return;
3176
+ }
3177
+ switch (ctx.hookPoint) {
3178
+ case TaskHookPoints.AFTER_CREATE:
3179
+ await this.onAfterCreate(ctx);
3180
+ break;
3181
+ case TaskHookPoints.AFTER_UPDATE:
3182
+ await this.onAfterUpdate(ctx);
3183
+ break;
3184
+ case TaskHookPoints.AFTER_COMPLETE:
3185
+ await this.onAfterComplete(ctx);
3186
+ break;
3187
+ case TaskHookPoints.OPERATION_AFTER_CREATE:
3188
+ await this.onAfterOperationCreate(ctx);
3189
+ break;
3190
+ case TaskHookPoints.AFTER_DELETE:
3191
+ await this.onAfterDelete(ctx);
3192
+ break;
3193
+ }
3194
+ }
3195
+ async onAfterCreate(ctx) {
3196
+ const { task } = ctx.data;
3197
+ if (!task || !this.shouldNotify(WecomNotifyType.TASK_CREATED))
3198
+ return;
3199
+ await this.sendCardStart(task);
3200
+ }
3201
+ async onAfterUpdate(ctx) {
3202
+ const data = ctx.data;
3203
+ if (!data?.task)
3204
+ return;
3205
+ const { task, changes } = data;
3206
+ const changeKeys = Object.keys(changes ?? {});
3207
+ if (changeKeys.length === 0)
3208
+ return;
3209
+ if (isTerminalUpdate(task, changes)) {
3210
+ if (!this.shouldNotify(WecomNotifyType.TASK_COMPLETED))
3211
+ return;
3212
+ await this.sendCardFinish(task, String(changes?.status ?? task.status));
3213
+ return;
3214
+ }
3215
+ if (!this.shouldNotify(WecomNotifyType.TASK_UPDATED))
3216
+ return;
3217
+ await this.sendCardUpdate(task, changes);
3218
+ }
3219
+ async onAfterComplete(ctx) {
3220
+ const data = ctx.data;
3221
+ const task = data?.task ?? data;
3222
+ if (!task || !this.shouldNotify(WecomNotifyType.TASK_COMPLETED))
3223
+ return;
3224
+ const terminalStatus = data.terminalStatus;
3225
+ if (terminalStatus === "to_validate" || task.status === "to_validate") {
3226
+ return;
3227
+ }
3228
+ await this.sendCardFinish(task, String(task.status));
3229
+ }
3230
+ async onAfterDelete(ctx) {
3231
+ const data = ctx.data;
3232
+ const taskId = data?.id;
3233
+ if (data?.deleted !== true || typeof taskId !== "number")
3234
+ return;
3235
+ if (!this.shouldNotify(WecomNotifyType.TASK_COMPLETED))
3236
+ return;
3237
+ const rec = this.getRecord(taskId);
3238
+ if (!rec) {
3239
+ logger9.debug(`onAfterDelete: task #${taskId} 无卡片记录,skip`);
3240
+ return;
3241
+ }
3242
+ const milestones = this.parseTimeline(rec.timeline);
3243
+ milestones.push({
3244
+ title: "任务已取消(删除)",
3245
+ timestamp: new Date().toISOString()
3246
+ });
3247
+ await this.deliver({
3248
+ chatId: rec.chatId,
3249
+ task: {
3250
+ id: taskId,
3251
+ title: `(已删除)`,
3252
+ description: "",
3253
+ status: "cancelled",
3254
+ progress: 0
3255
+ },
3256
+ statusLabel: statusLabelOf({ status: "cancelled" }),
3257
+ milestones,
3258
+ terminal: true
3259
+ });
3260
+ this.cardStateStore.markClosed(taskId);
3261
+ logger9.debug(`onAfterDelete: task #${taskId} 卡片已收尾`);
3262
+ }
3263
+ async onAfterOperationCreate(ctx) {
3264
+ const { operation } = ctx.data;
3265
+ if (!operation)
3266
+ return;
3267
+ if (!this.shouldNotify(WecomNotifyType.OPERATION_CREATED))
3268
+ return;
3269
+ const rec = this.getRecord(operation.taskId);
3270
+ if (!rec) {
3271
+ logger9.debug(`onAfterOperationCreate: task #${operation.taskId} 无卡片记录,skip`);
3272
+ return;
3273
+ }
3274
+ if (rec.status !== "open") {
3275
+ logger9.debug(`onAfterOperationCreate: task #${operation.taskId} status=${rec.status}, skip`);
3276
+ return;
3277
+ }
3278
+ const milestone = milestoneFromOperation(operation);
3279
+ const milestones = this.parseTimeline(rec.timeline);
3280
+ milestones.push(milestone);
3281
+ const trimmed = this.trimTimeline(milestones);
3282
+ this.persistTimeline(operation.taskId, rec, trimmed, rec.updateCount);
3283
+ logger9.debug(`[single-card] OPERATION_CREATED for task ` + `#${operation.taskId} merged into timeline`);
3284
+ }
3285
+ async sendCardStart(task) {
3286
+ const env = getEnvContextOrEmpty();
3287
+ const fields = extractWecomFields(env, this.sourceId);
3288
+ const chatId = fields.chatId;
3289
+ if (!chatId) {
3290
+ logger9.debug(`sendCardStart: no chatId for task #${task.id}, skip`);
3291
+ return;
3292
+ }
3293
+ const existing = this.getRecord(task.id);
3294
+ if (existing?.status === "open") {
3295
+ logger9.debug(`sendCardStart: task #${task.id} 已有 open 记录 → 跳过重复发卡`);
3296
+ return;
3297
+ }
3298
+ const milestones = [
3299
+ {
3300
+ title: "Task 创建",
3301
+ timestamp: new Date().toISOString(),
3302
+ description: task.current_status || undefined
3303
+ }
3304
+ ];
3305
+ const ok = await this.deliver({
3306
+ chatId,
3307
+ task,
3308
+ statusLabel: statusLabelOf(task),
3309
+ milestones
3310
+ });
3311
+ if (ok) {
3312
+ this.cardStateStore.upsert({
3313
+ taskId: task.id,
3314
+ chatId,
3315
+ pinned: false,
3316
+ status: "open",
3317
+ createdAt: Date.now(),
3318
+ updatedAt: Date.now(),
3319
+ updateCount: 1,
3320
+ timeline: JSON.stringify(milestones)
3321
+ });
3322
+ } else {
3323
+ this.cardStateStore.upsert({
3324
+ taskId: task.id,
3325
+ chatId,
3326
+ pinned: false,
3327
+ status: "open",
3328
+ createdAt: Date.now(),
3329
+ updatedAt: Date.now(),
3330
+ updateCount: 0,
3331
+ timeline: JSON.stringify(milestones)
3332
+ });
3333
+ logger9.warn(`sendCardStart: task #${task.id} 首卡发送失败(已落库待重试)`);
3334
+ }
3335
+ }
3336
+ async sendCardUpdate(task, changes) {
3337
+ const rec = this.getRecord(task.id);
3338
+ if (!rec) {
3339
+ if (isHardTerminal(String(task.status))) {
3340
+ logger9.debug(`sendCardUpdate: task #${task.id} 终态且无卡片 → skip`);
3341
+ return;
3342
+ }
3343
+ logger9.debug(`sendCardUpdate: task #${task.id} 无卡片记录 → ` + `单卡语义不补发(进展落时间线)`);
3344
+ return;
3345
+ }
3346
+ if (rec.status === "closed" && isHardTerminal(String(task.status))) {
3347
+ logger9.debug(`sendCardUpdate: task #${task.id} closed + 终态 → skip`);
3348
+ return;
3349
+ }
3350
+ const reopened = rec.status === "closed" && !isHardTerminal(String(task.status));
3351
+ if (reopened) {
3352
+ this.cardStateStore.markReopened(task.id);
3353
+ logger9.debug(`sendCardUpdate: task #${task.id} reopen(复用记录)`);
3354
+ }
3355
+ const fresh = reopened ? this.getRecord(task.id) : rec;
3356
+ const milestones = this.parseTimeline(fresh.timeline);
3357
+ if (reopened) {
3358
+ const last = milestones[milestones.length - 1];
3359
+ if (last?.title !== REOPEN_TITLE) {
3360
+ milestones.push({
3361
+ title: REOPEN_TITLE,
3362
+ timestamp: new Date().toISOString(),
3363
+ description: task.current_status ? task.current_status.slice(0, 80) : undefined
3364
+ });
3365
+ }
3366
+ }
3367
+ this.persistTimeline(task.id, fresh, milestones, fresh.updateCount);
3368
+ logger9.debug(`sendCardUpdate: task #${task.id} 单卡语义(不发新卡,` + `进展走时间线;点「刷新详情」可见)`);
3369
+ }
3370
+ async sendCardFinish(task, rawStatus) {
3371
+ const rec = this.getRecord(task.id);
3372
+ if (!rec) {
3373
+ logger9.debug(`sendCardFinish: no card record for task #${task.id}, skip(不发新卡)`);
3374
+ return;
3375
+ }
3376
+ if (rec.status === "closed" && rec.closedAt !== undefined && Date.now() - rec.closedAt < 60000) {
3377
+ const milestones2 = this.parseTimeline(rec.timeline);
3378
+ milestones2.push({
3379
+ title: FINISH_TITLE[rawStatus] ?? "任务收尾",
3380
+ timestamp: new Date().toISOString(),
3381
+ description: task.current_status || undefined
3382
+ });
3383
+ this.persistTimeline(task.id, rec, milestones2, rec.updateCount);
3384
+ logger9.debug(`sendCardFinish: task #${task.id} 已收尾(60s 内重复终态)→ ` + `合并不发新卡`);
3385
+ return;
3386
+ }
3387
+ const milestones = this.parseTimeline(rec.timeline);
3388
+ milestones.push({
3389
+ title: FINISH_TITLE[rawStatus] ?? "任务收尾",
3390
+ timestamp: new Date().toISOString(),
3391
+ description: task.current_status || undefined
3392
+ });
3393
+ await this.deliver({
3394
+ chatId: rec.chatId,
3395
+ task,
3396
+ statusLabel: statusLabelOf(task, rawStatus),
3397
+ milestones,
3398
+ terminal: true
3399
+ });
3400
+ this.persistTimeline(task.id, rec, milestones, rec.updateCount + 1);
3401
+ if (rawStatus === "paused") {
3402
+ this.cardStateStore.markPaused(task.id);
3403
+ } else {
3404
+ this.cardStateStore.markClosed(task.id);
3405
+ }
3406
+ logger9.debug(`sendCardFinish: task #${task.id} → status=${rawStatus}`);
3407
+ }
3408
+ async deliver(input) {
3409
+ const content = buildWecomTaskCard({
3410
+ task: input.task,
3411
+ statusLabel: input.statusLabel,
3412
+ milestones: input.milestones,
3413
+ changeSummary: input.changeSummary,
3414
+ terminal: input.terminal
3415
+ });
3416
+ const cardResult = await this.deliverTemplateCard(input);
3417
+ if (cardResult)
3418
+ return true;
3419
+ try {
3420
+ const r = await this.sender.send({
3421
+ chatId: input.chatId,
3422
+ content
3423
+ });
3424
+ if (!r.ok) {
3425
+ logger9.warn(`deliver failed for task #${input.task.id}: ${r.error ?? "unknown"}`);
3426
+ return false;
3427
+ }
3428
+ return true;
3429
+ } catch (err) {
3430
+ logger9.error(`deliver threw for task #${input.task.id}: ${err.message}`);
3431
+ return false;
3432
+ }
3433
+ }
3434
+ async deliverTemplateCard(input) {
3435
+ try {
3436
+ const env = getEnvContextOrEmpty();
3437
+ const fields = extractWecomFields(env, this.sourceId);
3438
+ const sourceId = fields.sourceId ?? "unknown";
3439
+ const chatType = env.replyChannel?.params?.chatType === "group" || fields.chatId !== input.chatId ? "group" : "single";
3440
+ const notice = buildWecomTextNoticeCard({
3441
+ taskId: input.task.id,
3442
+ title: input.task.title,
3443
+ statusLabel: input.statusLabel,
3444
+ progress: Number(input.task.progress ?? 0),
3445
+ description: input.task.description,
3446
+ changeSummary: input.changeSummary,
3447
+ milestones: input.milestones,
3448
+ terminal: input.terminal
3449
+ });
3450
+ const card = input.terminal ? notice : buildWecomButtonInteractionCard({
3451
+ taskId: input.task.id,
3452
+ title: input.task.title,
3453
+ statusLabel: input.statusLabel,
3454
+ progress: Number(input.task.progress ?? 0),
3455
+ description: input.task.description,
3456
+ changeSummary: input.changeSummary,
3457
+ milestones: input.milestones,
3458
+ buttons: [
3459
+ { text: "刷新详情", key: WecomCardActionKey.VIEW, style: 1 }
3460
+ ]
3461
+ });
3462
+ const r = await sendWecomTemplateCard({
3463
+ sourceId,
3464
+ chatId: input.chatId,
3465
+ chatType,
3466
+ card
3467
+ });
3468
+ if (!r.ok) {
3469
+ logger9.warn(`deliverTemplateCard failed for task #${input.task.id}: ` + `${r.error ?? "unknown"}(回落 markdown)`);
3470
+ return false;
3471
+ }
3472
+ return true;
3473
+ } catch (err) {
3474
+ logger9.warn(`deliverTemplateCard threw for task #${input.task.id}: ` + `${err.message}(回落 markdown)`);
3475
+ return false;
3476
+ }
3477
+ }
3478
+ getRecord(taskId) {
3479
+ try {
3480
+ return this.cardStateStore.get(taskId);
3481
+ } catch (err) {
3482
+ logger9.warn(`getRecord failed for task #${taskId}: ${err.message}`);
3483
+ return;
3484
+ }
3485
+ }
3486
+ parseTimeline(timeline) {
3487
+ if (!timeline)
3488
+ return [];
3489
+ try {
3490
+ const arr = JSON.parse(timeline);
3491
+ return Array.isArray(arr) ? arr : [];
3492
+ } catch {
3493
+ return [];
3494
+ }
3495
+ }
3496
+ trimTimeline(milestones) {
3497
+ const max = this.config.maxTimelineEntries ?? 20;
3498
+ if (milestones.length <= max)
3499
+ return milestones;
3500
+ return milestones.slice(milestones.length - max);
3501
+ }
3502
+ persistTimeline(taskId, rec, milestones, updateCount) {
3503
+ try {
3504
+ this.cardStateStore.upsert({
3505
+ ...rec,
3506
+ updatedAt: Date.now(),
3507
+ updateCount,
3508
+ timeline: JSON.stringify(milestones)
3509
+ });
3510
+ } catch (err) {
3511
+ logger9.warn(`persistTimeline failed for task #${taskId}: ${err.message}`);
3512
+ }
3513
+ }
3514
+ hydrateTask(taskId, rec) {
3515
+ return {
3516
+ id: taskId,
3517
+ title: `Task #${taskId}`,
3518
+ description: "",
3519
+ status: "active",
3520
+ priority: "medium",
3521
+ type: "normal",
3522
+ progress: 0,
3523
+ current_status: "",
3524
+ goals_and_expected_deliverables: "",
3525
+ createdAt: new Date(rec.createdAt).toISOString(),
3526
+ updatedAt: new Date(rec.updatedAt).toISOString(),
3527
+ project_path: "",
3528
+ context: "{}"
3529
+ };
3530
+ }
3531
+ shouldThrottleOperation(operation) {
3532
+ const throttleMs = this.config.operationThrottleMs ?? 5000;
3533
+ if (throttleMs <= 0)
3534
+ return false;
3535
+ const key = `op:${operation.taskId}`;
3536
+ const now = Date.now();
3537
+ const lastTime = this.lastNotifyTimestamps.get(key);
3538
+ if (lastTime && now - lastTime < throttleMs) {
3539
+ logger9.debug(`[throttle] skip OPERATION_CREATED send for task #${operation.taskId} ` + `(${now - lastTime}ms ago)`);
3540
+ return true;
3541
+ }
3542
+ this.lastNotifyTimestamps.set(key, now);
3543
+ return false;
3544
+ }
3545
+ shouldNotify(type) {
3546
+ const { notifyTypes } = this.config;
3547
+ if (!notifyTypes || notifyTypes.length === 0)
3548
+ return true;
3549
+ return notifyTypes.includes(type);
3550
+ }
3551
+ }
3552
+ __legacyDecorateClassTS([
3553
+ TracedExecute
3554
+ ], WecomTaskNotifyHook.prototype, "execute", null);
3555
+ var FINISH_TITLE = {
3556
+ completed: "\uD83C\uDF89 任务完成",
3557
+ cancelled: "\uD83D\uDEAB 任务取消",
3558
+ failed: "\uD83D\uDEA8 任务失败",
3559
+ timeout: "\uD83D\uDEA8 任务超时",
3560
+ archived: "\uD83D\uDCE6 任务归档",
3561
+ paused: "⏸️ 任务暂停"
3562
+ };
3563
+ var REOPEN_TITLE = "\uD83D\uDD04 已重开";
3564
+ function createWecomTaskNotifyHook(config = {}) {
3565
+ return new WecomTaskNotifyHook(config);
3566
+ }
3567
+ function TracedExecute(_target, _propertyKey, descriptor) {
3568
+ const original = descriptor.value;
3569
+ descriptor.value = async function(...args) {
3570
+ try {
3571
+ return await original.apply(this, args);
3572
+ } catch (err) {
3573
+ logger9.error(`wecom-task-notify execute threw: ${err.message}`);
3574
+ }
3575
+ };
3576
+ return descriptor;
3577
+ }
2773
3578
  // src/env/task/plugins/per-task-group-chat-plugin.ts
2774
3579
  init_decorator();
2775
3580
  init_logger();
2776
3581
  init_context();
2777
3582
  import { spawn } from "child_process";
2778
- var logger8 = createLogger("lark-cli:per-task-group");
3583
+ var logger10 = createLogger("lark-cli:per-task-group");
2779
3584
  function isPluginDebugEnabled() {
2780
3585
  return process.env.ROY_TASK_NOTIFY_DEBUG === "1" || process.env.ROY_TRACE_DEBUG === "1";
2781
3586
  }
@@ -2830,7 +3635,7 @@ class PerTaskGroupChatPlugin {
2830
3635
  const renderedName = this.renderTemplate(this.config.nameTemplate, task);
2831
3636
  const existingChatId = this.getTaskChatId(task);
2832
3637
  if (existingChatId) {
2833
- logger8.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 已有专属群 ${existingChatId} → 复用(不重复建群)`);
3638
+ logger10.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 已有专属群 ${existingChatId} → 复用(不重复建群)`);
2834
3639
  return {
2835
3640
  chatId: existingChatId,
2836
3641
  profile: this.parseProfileFromContext(task.context),
@@ -2839,7 +3644,7 @@ class PerTaskGroupChatPlugin {
2839
3644
  }
2840
3645
  const inflight = this.groupChatInflight.get(task.id);
2841
3646
  if (inflight) {
2842
- logger8.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 建群已在进行中 → 复用同一 promise`);
3647
+ logger10.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 建群已在进行中 → 复用同一 promise`);
2843
3648
  return inflight;
2844
3649
  }
2845
3650
  const pending = this.doEnsureGroupChat(task, renderedName, replyChannelOverride);
@@ -2853,7 +3658,7 @@ class PerTaskGroupChatPlugin {
2853
3658
  async doEnsureGroupChat(task, name, replyChannelOverride) {
2854
3659
  const replyChannel = replyChannelOverride ?? getEnvContextOrEmpty().replyChannel;
2855
3660
  if (!replyChannel?.chatId && this.config.requireReplyChannel) {
2856
- logger8.warn(`ensureGroupChat[${this.pluginName}]: no replyChannel.chatId for task #${task.id} ` + `(requireReplyChannel=true) → 无法建群`);
3661
+ logger10.warn(`ensureGroupChat[${this.pluginName}]: no replyChannel.chatId for task #${task.id} ` + `(requireReplyChannel=true) → 无法建群`);
2857
3662
  return;
2858
3663
  }
2859
3664
  const description = this.renderTemplate(this.config.descriptionTemplate, task);
@@ -2871,30 +3676,30 @@ class PerTaskGroupChatPlugin {
2871
3676
  await this.writeChatBinding(task, chatId, profile, "creating");
2872
3677
  await this.attachChatIdToTask(task, chatId, profile);
2873
3678
  await this.writeChatBinding(task, chatId, profile, "ready");
2874
- logger8.info(`ensureGroupChat[${this.pluginName}]: created group ${chatId} for task #${task.id}`);
3679
+ logger10.info(`ensureGroupChat[${this.pluginName}]: created group ${chatId} for task #${task.id}`);
2875
3680
  return { chatId, profile, name };
2876
3681
  } catch (error) {
2877
- logger8.error(`ensureGroupChat[${this.pluginName}]: failed for task #${task.id}: ${error}`);
3682
+ logger10.error(`ensureGroupChat[${this.pluginName}]: failed for task #${task.id}: ${error}`);
2878
3683
  return;
2879
3684
  }
2880
3685
  }
2881
3686
  setTaskUpdater(updater) {
2882
3687
  this.taskUpdater = updater;
2883
- logger8.debug(`TaskUpdater injected for plugin ${this.name} (sourceId=${this.sourceId})`);
3688
+ logger10.debug(`TaskUpdater injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2884
3689
  }
2885
3690
  hasTaskUpdater() {
2886
3691
  return this.taskUpdater !== undefined;
2887
3692
  }
2888
3693
  setTaskGetter(getter) {
2889
3694
  this.taskGetter = getter;
2890
- logger8.debug(`TaskGetter injected for plugin ${this.name} (sourceId=${this.sourceId})`);
3695
+ logger10.debug(`TaskGetter injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2891
3696
  }
2892
3697
  hasTaskGetter() {
2893
3698
  return this.taskGetter !== undefined;
2894
3699
  }
2895
3700
  setSessionComponent(sessionComponent) {
2896
3701
  this.sessionComponent = sessionComponent;
2897
- logger8.debug(`SessionComponent injected for plugin ${this.name} (sourceId=${this.sourceId})`);
3702
+ logger10.debug(`SessionComponent injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2898
3703
  }
2899
3704
  hasSessionComponent() {
2900
3705
  return this.sessionComponent !== undefined;
@@ -2907,7 +3712,7 @@ class PerTaskGroupChatPlugin {
2907
3712
  if (process.env.ROY_HOOK_DEBUG === "1") {
2908
3713
  console.error(`[DEBUG per-task-group-chat.execute] → SKIP (config.enabled=false)`);
2909
3714
  }
2910
- logger8.debug(`execute: plugin disabled (pluginName=${this.pluginName})`);
3715
+ logger10.debug(`execute: plugin disabled (pluginName=${this.pluginName})`);
2911
3716
  return;
2912
3717
  }
2913
3718
  if (this.pluginName && this.sourceId) {
@@ -2920,13 +3725,13 @@ class PerTaskGroupChatPlugin {
2920
3725
  if (process.env.ROY_HOOK_DEBUG === "1") {
2921
3726
  console.error(`[DEBUG per-task-group-chat.execute] → SKIP (plugin NOT enabled for ${key})`);
2922
3727
  }
2923
- logger8.debug(`execute[${this.pluginName}]: plugin NOT enabled (key=${key}, ` + `env.pluginEnabled=${JSON.stringify(env.pluginEnabled)}, ` + `hookPoint=${ctx.hookPoint}). ` + `→ skip. 修复:roy-agent eventsource update <id> 必须包含 PerTaskGroupChatPlugin`);
3728
+ logger10.debug(`execute[${this.pluginName}]: plugin NOT enabled (key=${key}, ` + `env.pluginEnabled=${JSON.stringify(env.pluginEnabled)}, ` + `hookPoint=${ctx.hookPoint}). ` + `→ skip. 修复:roy-agent eventsource update <id> 必须包含 PerTaskGroupChatPlugin`);
2924
3729
  return;
2925
3730
  }
2926
3731
  const enabledState = env.pluginEnabled ? env.pluginEnabled[key] === true ? "✅ enabled" : "❌ NOT enabled" : "⚠️ env.pluginEnabled 未设置 (env 没创建 / 插件未注册到 eventsource)";
2927
- logger8.debug(`execute[${this.pluginName}]: ${enabledState} (key=${key}, ` + `hookPoint=${ctx.hookPoint}, taskUpdaterInjected=${this.hasTaskUpdater()})`);
3732
+ logger10.debug(`execute[${this.pluginName}]: ${enabledState} (key=${key}, ` + `hookPoint=${ctx.hookPoint}, taskUpdaterInjected=${this.hasTaskUpdater()})`);
2928
3733
  } else {
2929
- logger8.debug(`execute[${this.pluginName}]: missing pluginName/sourceId metadata ` + `(pluginName=${this.pluginName}, sourceId=${this.sourceId}), ` + `skipping pluginEnabled check. hookPoint=${ctx.hookPoint}`);
3734
+ logger10.debug(`execute[${this.pluginName}]: missing pluginName/sourceId metadata ` + `(pluginName=${this.pluginName}, sourceId=${this.sourceId}), ` + `skipping pluginEnabled check. hookPoint=${ctx.hookPoint}`);
2930
3735
  }
2931
3736
  if (process.env.ROY_HOOK_DEBUG === "1") {
2932
3737
  console.error(`[DEBUG per-task-group-chat.execute] → PASSED pluginEnabled check, will dispatch hookPoint=${ctx.hookPoint}`);
@@ -2945,13 +3750,13 @@ class PerTaskGroupChatPlugin {
2945
3750
  await this.onAfterComplete(ctx);
2946
3751
  break;
2947
3752
  default:
2948
- logger8.debug(`execute: unhandled hookPoint=${ctx.hookPoint}, skip`);
3753
+ logger10.debug(`execute: unhandled hookPoint=${ctx.hookPoint}, skip`);
2949
3754
  }
2950
3755
  }
2951
3756
  async onAfterCreate(ctx) {
2952
3757
  const data = ctx.data;
2953
3758
  if (!data?.task) {
2954
- logger8.debug("onAfterCreate: task is missing, skip");
3759
+ logger10.debug("onAfterCreate: task is missing, skip");
2955
3760
  return;
2956
3761
  }
2957
3762
  const { task } = data;
@@ -2962,38 +3767,38 @@ class PerTaskGroupChatPlugin {
2962
3767
  console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.messageId=${env.messageId}`);
2963
3768
  console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.sourceId=${env.sourceId}`);
2964
3769
  }
2965
- logger8.debug(`onAfterCreate[${this.pluginName}]: enter task=${task.id} "${task.title}" ` + `parent_task_id=${task.parent_task_id ?? "none"} ` + `requireReplyChannel=${this.config.requireReplyChannel} ` + `disableSubTaskSkip=${this.config.disableSubTaskSkip} ` + `sendWelcomeMessage=${this.config.sendWelcomeMessage} ` + `autoCreateGroup=${this.config.autoCreateGroup}`);
3770
+ logger10.debug(`onAfterCreate[${this.pluginName}]: enter task=${task.id} "${task.title}" ` + `parent_task_id=${task.parent_task_id ?? "none"} ` + `requireReplyChannel=${this.config.requireReplyChannel} ` + `disableSubTaskSkip=${this.config.disableSubTaskSkip} ` + `sendWelcomeMessage=${this.config.sendWelcomeMessage} ` + `autoCreateGroup=${this.config.autoCreateGroup}`);
2966
3771
  if (!this.config.autoCreateGroup) {
2967
- logger8.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} autoCreateGroup=false ` + `→ skip group chat creation(按需建群:用户在卡片顶部按钮触发)`);
3772
+ logger10.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} autoCreateGroup=false ` + `→ skip group chat creation(按需建群:用户在卡片顶部按钮触发)`);
2968
3773
  return;
2969
3774
  }
2970
3775
  const replyChannel = this.getReplyChannelFromCtx(ctx);
2971
3776
  if (!replyChannel?.chatId && this.config.requireReplyChannel) {
2972
- logger8.debug(`onAfterCreate[${this.pluginName}]: no replyChannel.chatId for task #${task.id}, skip. ` + `修复:eventsource 收到 im.message.receive_v1 事件时 replyChannel 应有值;` + `或在 plugin config 设 requireReplyChannel=false 强制创建群聊(profile 降级到 fallbackProfile/无)`);
3777
+ logger10.debug(`onAfterCreate[${this.pluginName}]: no replyChannel.chatId for task #${task.id}, skip. ` + `修复:eventsource 收到 im.message.receive_v1 事件时 replyChannel 应有值;` + `或在 plugin config 设 requireReplyChannel=false 强制创建群聊(profile 降级到 fallbackProfile/无)`);
2973
3778
  return;
2974
3779
  }
2975
3780
  if (!replyChannel?.chatId && !this.config.requireReplyChannel) {
2976
- logger8.debug(`onAfterCreate[${this.pluginName}]: no replyChannel but requireReplyChannel=false, ` + `proceeding with profile fallback (fallbackProfile="${this.config.fallbackProfile}")`);
3781
+ logger10.debug(`onAfterCreate[${this.pluginName}]: no replyChannel but requireReplyChannel=false, ` + `proceeding with profile fallback (fallbackProfile="${this.config.fallbackProfile}")`);
2977
3782
  }
2978
3783
  if (this.isSubTask(task) && !this.config.disableSubTaskSkip) {
2979
- logger8.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task, ` + `skip group chat creation. 关闭方式:config.disableSubTaskSkip=true`);
3784
+ logger10.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task, ` + `skip group chat creation. 关闭方式:config.disableSubTaskSkip=true`);
2980
3785
  return;
2981
3786
  }
2982
3787
  if (this.isSubTask(task) && this.config.disableSubTaskSkip) {
2983
- logger8.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task but ` + `disableSubTaskSkip=true, proceeding to create group chat`);
3788
+ logger10.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task but ` + `disableSubTaskSkip=true, proceeding to create group chat`);
2984
3789
  }
2985
3790
  try {
2986
3791
  const created = await this.ensureGroupChat(task, replyChannel);
2987
3792
  if (!created?.chatId) {
2988
- logger8.error(`onAfterCreate[${this.pluginName}]: ensureGroupChat 未返回 chatId for task #${task.id}`);
3793
+ logger10.error(`onAfterCreate[${this.pluginName}]: ensureGroupChat 未返回 chatId for task #${task.id}`);
2989
3794
  if (!this.config.fallbackToMain) {
2990
3795
  throw new Error(`per-task group chat unavailable for task #${task.id}`);
2991
3796
  }
2992
3797
  return;
2993
3798
  }
2994
- logger8.info(`onAfterCreate[${this.pluginName}]: task #${task.id} → group ${created.chatId} ` + `(initial card owned by LarkCliTaskNotifyHook)`);
3799
+ logger10.info(`onAfterCreate[${this.pluginName}]: task #${task.id} → group ${created.chatId} ` + `(initial card owned by LarkCliTaskNotifyHook)`);
2995
3800
  } catch (error) {
2996
- logger8.error(`onAfterCreate[${this.pluginName}]: group chat failed for task #${task.id}: ${error}`);
3801
+ logger10.error(`onAfterCreate[${this.pluginName}]: group chat failed for task #${task.id}: ${error}`);
2997
3802
  if (!this.config.fallbackToMain)
2998
3803
  throw error;
2999
3804
  }
@@ -3001,7 +3806,7 @@ class PerTaskGroupChatPlugin {
3001
3806
  async writeChatBinding(task, chatId, profile, status, errorMessage) {
3002
3807
  if (!this.sessionComponent) {
3003
3808
  if (isPluginDebugEnabled()) {
3004
- logger8.debug(`writeChatBinding[${this.pluginName}]: no sessionComponent injected, ` + `skip chat-task binding for chat ${chatId} (status=${status})`);
3809
+ logger10.debug(`writeChatBinding[${this.pluginName}]: no sessionComponent injected, ` + `skip chat-task binding for chat ${chatId} (status=${status})`);
3005
3810
  }
3006
3811
  return;
3007
3812
  }
@@ -3014,37 +3819,37 @@ class PerTaskGroupChatPlugin {
3014
3819
  errorMessage: errorMessage ?? null
3015
3820
  });
3016
3821
  } catch (bindErr) {
3017
- logger8.warn(`writeChatBinding[${this.pluginName}]: failed to write chat-task binding for ` + `chat ${chatId} / task #${task.id} (status=${status}): ${bindErr}. ` + `群消息 handler 将无法自动注入 parentTaskId。`);
3822
+ logger10.warn(`writeChatBinding[${this.pluginName}]: failed to write chat-task binding for ` + `chat ${chatId} / task #${task.id} (status=${status}): ${bindErr}. ` + `群消息 handler 将无法自动注入 parentTaskId。`);
3018
3823
  }
3019
3824
  }
3020
3825
  async onAfterUpdate(ctx) {
3021
3826
  const data = ctx.data;
3022
3827
  if (!data?.task) {
3023
- logger8.debug("onAfterUpdate: task is missing, skip");
3828
+ logger10.debug("onAfterUpdate: task is missing, skip");
3024
3829
  return;
3025
3830
  }
3026
- logger8.debug(`onAfterUpdate[${this.pluginName}]: task #${data.task.id} 卡片由 ` + `LarkCliTaskNotifyHook 负责(本插件不再 patch),skip`);
3831
+ logger10.debug(`onAfterUpdate[${this.pluginName}]: task #${data.task.id} 卡片由 ` + `LarkCliTaskNotifyHook 负责(本插件不再 patch),skip`);
3027
3832
  }
3028
3833
  async onAfterOperationCreate(ctx) {
3029
3834
  const data = ctx.data;
3030
3835
  if (!data?.operation) {
3031
- logger8.debug("onAfterOperationCreate: operation is missing, skip");
3836
+ logger10.debug("onAfterOperationCreate: operation is missing, skip");
3032
3837
  return;
3033
3838
  }
3034
- logger8.debug(`onAfterOperationCreate[${this.pluginName}]: operation #${data.operation.id} ` + `卡片由 LarkCliTaskNotifyHook 负责,skip`);
3839
+ logger10.debug(`onAfterOperationCreate[${this.pluginName}]: operation #${data.operation.id} ` + `卡片由 LarkCliTaskNotifyHook 负责,skip`);
3035
3840
  }
3036
3841
  async onAfterComplete(ctx) {
3037
3842
  const data = ctx.data;
3038
3843
  if (!data?.task) {
3039
- logger8.debug("onAfterComplete: task is missing, skip");
3844
+ logger10.debug("onAfterComplete: task is missing, skip");
3040
3845
  return;
3041
3846
  }
3042
- logger8.debug(`onAfterComplete[${this.pluginName}]: task #${data.task.id} 终态卡片由 ` + `LarkCliTaskNotifyHook 负责,skip`);
3847
+ logger10.debug(`onAfterComplete[${this.pluginName}]: task #${data.task.id} 终态卡片由 ` + `LarkCliTaskNotifyHook 负责,skip`);
3043
3848
  }
3044
3849
  createGroupChat(opts) {
3045
3850
  const args = this.buildCreateGroupChatArgs(opts);
3046
3851
  if (isPluginDebugEnabled()) {
3047
- logger8.debug(`Creating group chat: lark-cli ${args.join(" ")}`);
3852
+ logger10.debug(`Creating group chat: lark-cli ${args.join(" ")}`);
3048
3853
  }
3049
3854
  return new Promise((resolve, reject) => {
3050
3855
  const proc = spawn("lark-cli", args, {
@@ -3119,16 +3924,16 @@ class PerTaskGroupChatPlugin {
3119
3924
  parsed[CTX_FIELD_CHAT_TASK_ID] = task.id;
3120
3925
  const newContext = JSON.stringify(parsed);
3121
3926
  if (!this.taskUpdater) {
3122
- logger8.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。`);
3927
+ 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。`);
3123
3928
  task.context = newContext;
3124
3929
  return;
3125
3930
  }
3126
3931
  try {
3127
3932
  await this.taskUpdater(task.id, newContext);
3128
3933
  task.context = newContext;
3129
- logger8.debug(`attachChatIdToTask[${this.pluginName}]: ✅ persisted chat_id ${chatId} ` + `to task #${task.id} context`);
3934
+ logger10.debug(`attachChatIdToTask[${this.pluginName}]: ✅ persisted chat_id ${chatId} ` + `to task #${task.id} context`);
3130
3935
  } catch (error) {
3131
- logger8.error(`Failed to persist chat_id to task #${task.id}: ${error}`);
3936
+ logger10.error(`Failed to persist chat_id to task #${task.id}: ${error}`);
3132
3937
  task.context = newContext;
3133
3938
  }
3134
3939
  }
@@ -3223,15 +4028,15 @@ class PerTaskGroupChatPlugin {
3223
4028
  }
3224
4029
  resolveBotAppIds(replyChannel) {
3225
4030
  if (this.config.botAppIds.length > 0) {
3226
- logger8.debug(`resolveBotAppIds[${this.pluginName}]: using config.botAppIds=${JSON.stringify(this.config.botAppIds)}`);
4031
+ logger10.debug(`resolveBotAppIds[${this.pluginName}]: using config.botAppIds=${JSON.stringify(this.config.botAppIds)}`);
3227
4032
  return this.config.botAppIds;
3228
4033
  }
3229
4034
  const appId = replyChannel ? replyChannel.appId : undefined;
3230
4035
  if (appId) {
3231
- logger8.debug(`resolveBotAppIds[${this.pluginName}]: using replyChannel.appId=${appId} (auto-detected from event header.app_id)`);
4036
+ logger10.debug(`resolveBotAppIds[${this.pluginName}]: using replyChannel.appId=${appId} (auto-detected from event header.app_id)`);
3232
4037
  return [appId];
3233
4038
  }
3234
- logger8.warn(`resolveBotAppIds[${this.pluginName}]: ⚠️ no botAppIds found, creating group WITHOUT --bots. ` + `Bot will NOT be invited automatically. ` + `可能原因:(1) eventsource 没有收到 im.message.receive_v1 事件(replyChannel.appId 缺失);` + `(2) 配置了 requireReplyChannel=false 模式;` + `(3) buildReplyChannel 没透传 appId(应在 v1.10+ 修复)。` + `修复:手动设置 config.botAppIds: ["cli_xxx"] 或确认 eventsource 处理了 lark event。`);
4039
+ logger10.warn(`resolveBotAppIds[${this.pluginName}]: ⚠️ no botAppIds found, creating group WITHOUT --bots. ` + `Bot will NOT be invited automatically. ` + `可能原因:(1) eventsource 没有收到 im.message.receive_v1 事件(replyChannel.appId 缺失);` + `(2) 配置了 requireReplyChannel=false 模式;` + `(3) buildReplyChannel 没透传 appId(应在 v1.10+ 修复)。` + `修复:手动设置 config.botAppIds: ["cli_xxx"] 或确认 eventsource 处理了 lark event。`);
3235
4040
  return [];
3236
4041
  }
3237
4042
  resolveUserIds(replyChannel) {
@@ -3241,7 +4046,7 @@ class PerTaskGroupChatPlugin {
3241
4046
  if (id && id !== "sender" && id.startsWith("ou_")) {
3242
4047
  ids.add(id);
3243
4048
  } else if (id && id !== "sender") {
3244
- logger8.debug(`resolveUserIds[${this.pluginName}]: skip non-open_id in defaultInviteUserIds: "${id}"`);
4049
+ logger10.debug(`resolveUserIds[${this.pluginName}]: skip non-open_id in defaultInviteUserIds: "${id}"`);
3245
4050
  }
3246
4051
  }
3247
4052
  }
@@ -3264,7 +4069,7 @@ class PerTaskGroupChatPlugin {
3264
4069
  }
3265
4070
  }
3266
4071
  if (isPluginDebugEnabled()) {
3267
- logger8.debug(`resolveUserIds[${this.pluginName}]: ` + `defaultInviteUserIds=${JSON.stringify(this.config.defaultInviteUserIds)} ` + `inviteUserIds=${JSON.stringify(this.config.inviteUserIds)} ` + `replyChannel.userId=${replyChannel?.userId ?? "(none)"} ` + `→ final userIds=${JSON.stringify(Array.from(ids))}`);
4072
+ logger10.debug(`resolveUserIds[${this.pluginName}]: ` + `defaultInviteUserIds=${JSON.stringify(this.config.defaultInviteUserIds)} ` + `inviteUserIds=${JSON.stringify(this.config.inviteUserIds)} ` + `replyChannel.userId=${replyChannel?.userId ?? "(none)"} ` + `→ final userIds=${JSON.stringify(Array.from(ids))}`);
3268
4073
  }
3269
4074
  return Array.from(ids);
3270
4075
  }
@@ -3417,4 +4222,4 @@ __legacyDecorateClassTS([
3417
4222
  function createPerTaskGroupChatPlugin(config = {}) {
3418
4223
  return new PerTaskGroupChatPlugin(config);
3419
4224
  }
3420
- export { createAutoTaskPlugin, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, LAYA_COMPLETION_THRESHOLD, buildCompletionHint, renderCompletionState, buildCompletionQuestion, LayaCompletionPlugin, createLayaCompletionPlugin, registerLayaCompletionTaskHook, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, MemoryLarkCardStateStore, SqliteLarkCardStateStore, getDefaultLarkCardDbPath, createLarkCardStateStore, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
4225
+ export { createAutoTaskPlugin, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, LAYA_COMPLETION_THRESHOLD, buildCompletionHint, renderCompletionState, buildCompletionQuestion, LayaCompletionPlugin, createLayaCompletionPlugin, registerLayaCompletionTaskHook, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, MemoryLarkCardStateStore, SqliteLarkCardStateStore, getDefaultLarkCardDbPath, createLarkCardStateStore, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, MemoryWecomCardStateStore, createMemoryWecomCardStore, SqliteWecomCardStateStore, getDefaultWecomCardDbPath, createWecomCardStateStore, WecomNotifyType, extractWecomFields, buildWecomTaskCard, WecomTaskNotifyHook, createWecomTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };