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

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 (91) hide show
  1. package/dist/env/agent/agent-component.js +6 -6
  2. package/dist/env/agent/agent-registry.d.ts +4 -1
  3. package/dist/env/agent/index.js +6 -6
  4. package/dist/env/error-reply.d.ts +46 -0
  5. package/dist/env/error-reply.js +3 -1
  6. package/dist/env/event-source/event-source-handlers.js +13 -12
  7. package/dist/env/event-source/index.js +14 -13
  8. package/dist/env/index.js +31 -30
  9. package/dist/env/llm/index.js +2 -2
  10. package/dist/env/llm/orphan-toolcall.d.ts +107 -0
  11. package/dist/env/plugin/index.js +3 -3
  12. package/dist/env/prompt/index.js +4 -3
  13. package/dist/env/prompt/prompts-index.d.ts +27 -3
  14. package/dist/env/session/index.js +4 -4
  15. package/dist/env/session/storage/index.js +3 -3
  16. package/dist/env/task/backend/index.js +9 -1
  17. package/dist/env/task/delegate/delegate-tool.d.ts +8 -0
  18. package/dist/env/task/delegate/delegate-tool.js +5 -4
  19. package/dist/env/task/delegate/index.js +5 -4
  20. package/dist/env/task/hooks/contexts.d.ts +12 -1
  21. package/dist/env/task/hooks/task-hook-points.d.ts +8 -3
  22. package/dist/env/task/index.d.ts +1 -0
  23. package/dist/env/task/index.js +17 -7
  24. package/dist/env/task/mcp/index.js +3 -2
  25. package/dist/env/task/plugins/index.d.ts +3 -0
  26. package/dist/env/task/plugins/index.js +33 -9
  27. package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +61 -0
  28. package/dist/env/task/plugins/lark-card-patch-notifier.js +6 -2
  29. package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +42 -0
  30. package/dist/env/task/prompt/index.js +2 -1
  31. package/dist/env/task/prompt/task-discipline.d.ts +9 -3
  32. package/dist/env/task/task-component.d.ts +79 -0
  33. package/dist/env/task/tools/cancel-tool.d.ts +36 -0
  34. package/dist/env/task/tools/completion-copy.d.ts +92 -0
  35. package/dist/env/task/tools/index.d.ts +2 -0
  36. package/dist/env/task/tools/index.js +7 -2
  37. package/dist/env/task/tools/pre-complete-tool.d.ts +32 -0
  38. package/dist/env/task/tools/task-types.d.ts +71 -17
  39. package/dist/env/task/types.d.ts +13 -1
  40. package/dist/env/tool/built-in/compact-session-tool.d.ts +120 -0
  41. package/dist/env/tool/built-in/index.d.ts +2 -0
  42. package/dist/env/tool/built-in/index.js +6 -2
  43. package/dist/env/tool/index.js +4 -4
  44. package/dist/env/workflow/engine/index.js +6 -6
  45. package/dist/env/workflow/index.js +15 -13
  46. package/dist/env/workflow/plugins/index.js +2 -2
  47. package/dist/env/workflow/tools/ask-user-tool.d.ts +88 -0
  48. package/dist/env/workflow/tools/index.js +11 -9
  49. package/dist/env/workflow/tools/run-workflow.d.ts +2 -2
  50. package/dist/env/workflow/types/index.js +2 -2
  51. package/dist/env/workflow/types/workflow-hil.d.ts +25 -1
  52. package/dist/env/workflow/utils/index.js +5 -5
  53. package/dist/index.d.ts +1 -1
  54. package/dist/index.js +43 -33
  55. package/dist/shared/@ai-setting/{roy-agent-core-sm0sf6sb.js → roy-agent-core-0qyrk5k0.js} +2 -2
  56. package/dist/shared/@ai-setting/{roy-agent-core-kmbct9yg.js → roy-agent-core-0snm5hc5.js} +1 -1
  57. package/dist/shared/@ai-setting/{roy-agent-core-yr1mryb8.js → roy-agent-core-1v4g0d4x.js} +1 -1
  58. package/dist/shared/@ai-setting/{roy-agent-core-rxb00gjm.js → roy-agent-core-264qexac.js} +1 -1
  59. package/dist/shared/@ai-setting/roy-agent-core-2akfd64z.js +374 -0
  60. package/dist/shared/@ai-setting/{roy-agent-core-skyc30qx.js → roy-agent-core-3fh1f7s7.js} +2 -2
  61. package/dist/shared/@ai-setting/roy-agent-core-4rtg69cn.js +26 -0
  62. package/dist/shared/@ai-setting/{roy-agent-core-add1zqsh.js → roy-agent-core-6cy43nss.js} +22 -1
  63. package/dist/shared/@ai-setting/{roy-agent-core-vah6g5yx.js → roy-agent-core-6rh8k6ba.js} +5 -5
  64. package/dist/shared/@ai-setting/{roy-agent-core-kpg25s2q.js → roy-agent-core-8ez6mccb.js} +4 -2
  65. package/dist/shared/@ai-setting/{roy-agent-core-yhwxpj12.js → roy-agent-core-9vxh6f8h.js} +233 -16
  66. package/dist/shared/@ai-setting/{roy-agent-core-7qvw65jt.js → roy-agent-core-aa2j7bk9.js} +11 -8
  67. package/dist/shared/@ai-setting/{roy-agent-core-sjr1kmfz.js → roy-agent-core-acj8ag08.js} +1 -1
  68. package/dist/shared/@ai-setting/{roy-agent-core-mds8tqd0.js → roy-agent-core-cgtq6427.js} +1 -1
  69. package/dist/shared/@ai-setting/{roy-agent-core-34jz9mgh.js → roy-agent-core-cmprygrp.js} +1 -1
  70. package/dist/shared/@ai-setting/{roy-agent-core-wr7mv7k3.js → roy-agent-core-cxy143m4.js} +1 -1
  71. package/dist/shared/@ai-setting/{roy-agent-core-pakhchy7.js → roy-agent-core-ddyk1rc0.js} +1 -1
  72. package/dist/shared/@ai-setting/{roy-agent-core-attpry6g.js → roy-agent-core-dqxec05w.js} +4 -3
  73. package/dist/shared/@ai-setting/{roy-agent-core-vm8h5zyj.js → roy-agent-core-ed233k5z.js} +1 -1
  74. package/dist/shared/@ai-setting/{roy-agent-core-jrj30vq3.js → roy-agent-core-er070zjq.js} +1 -1
  75. package/dist/shared/@ai-setting/{roy-agent-core-40rv283c.js → roy-agent-core-fghc2m8z.js} +152 -2
  76. package/dist/shared/@ai-setting/{roy-agent-core-5gw0qa8m.js → roy-agent-core-ghnagzrh.js} +557 -92
  77. package/dist/shared/@ai-setting/{roy-agent-core-4jjwewvw.js → roy-agent-core-m46fe3x4.js} +41 -30
  78. package/dist/shared/@ai-setting/{roy-agent-core-e25xkv53.js → roy-agent-core-mkc3zshb.js} +3 -1
  79. package/dist/shared/@ai-setting/{roy-agent-core-hfyfavp3.js → roy-agent-core-mww0mnd9.js} +231 -27
  80. package/dist/shared/@ai-setting/{roy-agent-core-46rv4d7d.js → roy-agent-core-nmx9934r.js} +92 -2
  81. package/dist/shared/@ai-setting/{roy-agent-core-1w9y7mxj.js → roy-agent-core-p3zv1mx5.js} +1 -1
  82. package/dist/shared/@ai-setting/{roy-agent-core-jgb3bb42.js → roy-agent-core-q5r75vev.js} +17 -8
  83. package/dist/shared/@ai-setting/{roy-agent-core-y12kyxwa.js → roy-agent-core-t3rrb959.js} +33 -9
  84. package/dist/shared/@ai-setting/{roy-agent-core-dt104qa1.js → roy-agent-core-v85n42ve.js} +1 -1
  85. package/dist/shared/@ai-setting/{roy-agent-core-gr48c89p.js → roy-agent-core-v87njmq7.js} +3 -3
  86. package/dist/shared/@ai-setting/{roy-agent-core-7sj9xs9c.js → roy-agent-core-vffccgf6.js} +252 -25
  87. package/dist/shared/@ai-setting/{roy-agent-core-2gzt2q8c.js → roy-agent-core-z43aj03k.js} +134 -29
  88. package/dist/shared/@ai-setting/roy-agent-core-zycadbpx.js +662 -0
  89. package/package.json +1 -1
  90. package/dist/shared/@ai-setting/roy-agent-core-1p6syv85.js +0 -1083
  91. package/dist/shared/@ai-setting/roy-agent-core-58804s3j.js +0 -155
@@ -3,7 +3,7 @@ import {
3
3
  } from "./roy-agent-core-na18zwj0.js";
4
4
  import {
5
5
  WORKFLOW_EXTRACT_PROMPT
6
- } from "./roy-agent-core-yhwxpj12.js";
6
+ } from "./roy-agent-core-9vxh6f8h.js";
7
7
  import {
8
8
  createCompatibleDatabase,
9
9
  init_sqlite_task_store
@@ -24,10 +24,10 @@ import {
24
24
  import {
25
25
  LarkCardPatchNotifier,
26
26
  humanizeValue
27
- } from "./roy-agent-core-46rv4d7d.js";
27
+ } from "./roy-agent-core-nmx9934r.js";
28
28
  import {
29
29
  validateWorkflowDefinition
30
- } from "./roy-agent-core-yr1mryb8.js";
30
+ } from "./roy-agent-core-1v4g0d4x.js";
31
31
  import {
32
32
  enqueueToAddFakeUserMessage
33
33
  } from "./roy-agent-core-f77k3gqt.js";
@@ -1254,6 +1254,423 @@ function createAutoTaskPlugin(config) {
1254
1254
  execute
1255
1255
  };
1256
1256
  }
1257
+ // src/env/task/plugins/laya-completion-plugin.ts
1258
+ init_logger();
1259
+ init_decorator();
1260
+ var logger5 = createLogger("plugin:laya-completion");
1261
+ var LAYA_COMPLETION_THRESHOLD = 0.92;
1262
+ var CHOICE_PASS = "PASS_COMPLETED";
1263
+ var CHOICE_FAIL = "FAIL_INCOMPLETE";
1264
+ var MAX_OPERATIONS_IN_STATE = 20;
1265
+ var OP_TITLE_MAX = 60;
1266
+ var OP_DESC_MAX = 120;
1267
+ var GOALS_MAX = 400;
1268
+ function buildCompletionHint(task, confidence) {
1269
+ return [
1270
+ "[TASK_COMPLETION_HINT] 决策模型检测到「目标 vs 交付物」可能不匹配:任务已标记完成,但操作记录链显示目标或交付物可能未达成。",
1271
+ "",
1272
+ `任务:#${task.id} [${task.type}] ${task.title}`,
1273
+ `状态:${task.status} | 判定置信度:${confidence.toFixed(4)}`,
1274
+ "",
1275
+ "⚠️ 你必须核实本提示:先用 task_get(task_id=" + task.id + ") 复核任务的目标(description / goals_and_expected_deliverables)与交付物证据(context.deliverable_storage_path、操作记录链)。",
1276
+ "核实后二选一执行:",
1277
+ "(a) 确认未完成 → task_update(task_id=" + task.id + ", status=todo) 重开任务继续推进;",
1278
+ "(b) 确认已完成 → task_operation_create(task_id=" + task.id + ", milestone_type=decision, ...) 落一条操作记录说明已完成的理由。",
1279
+ "不允许无视本提示。"
1280
+ ].join(`
1281
+ `);
1282
+ }
1283
+ function renderOperations(ops) {
1284
+ if (ops.length === 0)
1285
+ return "(无操作记录)";
1286
+ const sorted = [...ops].sort((a, b) => new Date(a.timestamp ?? 0).getTime() - new Date(b.timestamp ?? 0).getTime());
1287
+ let windowed;
1288
+ if (sorted.length <= MAX_OPERATIONS_IN_STATE) {
1289
+ windowed = sorted;
1290
+ } else {
1291
+ const tail = sorted.slice(-(MAX_OPERATIONS_IN_STATE - 1));
1292
+ const head = sorted.slice(0, 1);
1293
+ const seen = new Set(tail.map((o) => o.id));
1294
+ windowed = seen.has(head[0].id) ? tail : [...head, ...tail];
1295
+ }
1296
+ return windowed.map((op, i) => {
1297
+ const title = (op.milestoneTitle || "").slice(0, OP_TITLE_MAX);
1298
+ const desc = (op.milestoneDescription || "").replace(/\n/g, " ").slice(0, OP_DESC_MAX);
1299
+ const proc = (op.processDescription || "").replace(/\n/g, " ").slice(0, OP_DESC_MAX);
1300
+ const parts = [`#${i + 1} [${op.milestoneType}] ${title}`];
1301
+ if (desc)
1302
+ parts.push(` ${desc}`);
1303
+ if (proc)
1304
+ parts.push(` 过程:${proc}`);
1305
+ return parts.join(`
1306
+ `);
1307
+ }).join(`
1308
+ `);
1309
+ }
1310
+ function readDeliverablePath(task) {
1311
+ try {
1312
+ const parsed = JSON.parse(task.context || "{}");
1313
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1314
+ const p = parsed.deliverable_storage_path;
1315
+ return typeof p === "string" ? p : "";
1316
+ }
1317
+ } catch {}
1318
+ return "";
1319
+ }
1320
+ function looksLikeFilePath(token) {
1321
+ if (token.startsWith("//"))
1322
+ return false;
1323
+ if ((token.match(/\//g) ?? []).length >= 2)
1324
+ return true;
1325
+ return /\.[A-Za-z0-9]+$/.test(token);
1326
+ }
1327
+ function firstPathToken(text) {
1328
+ const m = text.match(/(?:^|[\s((【「::,,])(\/[A-Za-z0-9._~\-/@%+]{2,})/);
1329
+ if (!m)
1330
+ return null;
1331
+ return looksLikeFilePath(m[1]) ? m[1] : null;
1332
+ }
1333
+ var CHECKLIST_MAX = 1200;
1334
+ var DELIVERABLE_SPLIT = /[,,;;、\n]/;
1335
+ var DELIVERABLE_PLUS_SPLIT = /\s\+\s/;
1336
+ function stripDeliverableToken(t) {
1337
+ const seg = t.trim();
1338
+ if (!seg)
1339
+ return null;
1340
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(seg))
1341
+ return null;
1342
+ const m = seg.match(/(?:^|[\s((【「::,,])(\/[A-Za-z0-9._~\-/@%+]{2,})/);
1343
+ if (!m)
1344
+ return null;
1345
+ const path = m[1].replace(/[。,、;:)】」"'”’.,;:!?)\]}]+$/u, "");
1346
+ return path.startsWith("/") ? path : null;
1347
+ }
1348
+ function isUrlSegment(seg) {
1349
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(seg.trim());
1350
+ }
1351
+ function splitDeliverableSegments(deliverable) {
1352
+ return deliverable.split(DELIVERABLE_PLUS_SPLIT).flatMap((s) => s.split(DELIVERABLE_SPLIT)).flatMap((s) => splitUrlSegments(s)).flatMap((s) => splitSpaceSeparatedPaths(s)).map((s) => s.trim()).filter(Boolean);
1353
+ }
1354
+ function splitUrlSegments(seg) {
1355
+ const re = /([a-z][a-z0-9+.-]*:\/\/\S+)/gi;
1356
+ const parts = [];
1357
+ let last = 0;
1358
+ let m;
1359
+ while ((m = re.exec(seg)) !== null) {
1360
+ const before = seg.slice(last, m.index).trim();
1361
+ if (before)
1362
+ parts.push(before);
1363
+ const url = m[1];
1364
+ if (/^file:\/\//i.test(url)) {
1365
+ const local = url.replace(/^file:\/\//i, "");
1366
+ if (local)
1367
+ parts.push(local);
1368
+ } else {
1369
+ parts.push(url);
1370
+ }
1371
+ last = m.index + url.length;
1372
+ }
1373
+ const rest = seg.slice(last).trim();
1374
+ if (rest)
1375
+ parts.push(rest);
1376
+ return parts.length > 0 ? parts : [seg];
1377
+ }
1378
+ function spaceSeparatedPathStarts(seg) {
1379
+ const re = /(?:^|[\s((【「::,,])(\/[A-Za-z0-9._~\-/@%+]{2,})/g;
1380
+ const starts = [];
1381
+ let m;
1382
+ while ((m = re.exec(seg)) !== null) {
1383
+ const pathStart = m[0].startsWith("/") ? m.index : m.index + 1;
1384
+ if (m[1].startsWith("//"))
1385
+ continue;
1386
+ starts.push(pathStart);
1387
+ }
1388
+ return starts;
1389
+ }
1390
+ function splitSpaceSeparatedPaths(seg) {
1391
+ const starts = spaceSeparatedPathStarts(seg);
1392
+ if (starts.length < 2)
1393
+ return [seg];
1394
+ const parts = [seg.slice(0, starts[1])];
1395
+ for (let i = 1;i < starts.length; i++) {
1396
+ const end = i + 1 < starts.length ? starts[i + 1] : seg.length;
1397
+ parts.push(seg.slice(starts[i], end));
1398
+ }
1399
+ return parts;
1400
+ }
1401
+ function isPathLikeDeliverable(deliverable) {
1402
+ if (!deliverable.includes("/"))
1403
+ return false;
1404
+ const segs = splitDeliverableSegments(deliverable);
1405
+ if (segs.length === 0)
1406
+ return false;
1407
+ const paths = segs.map(stripDeliverableToken).filter((p) => p !== null);
1408
+ if (paths.length === 0)
1409
+ return false;
1410
+ return segs.every((s, i) => stripDeliverableToken(s) !== null || isUrlSegment(s));
1411
+ }
1412
+ function renderCriteriaChecklist(goalsText, deliverable) {
1413
+ if (!deliverable || deliverable === "(未声明)")
1414
+ return null;
1415
+ if (!isPathLikeDeliverable(deliverable))
1416
+ return null;
1417
+ const items = goalsText.split(`
1418
+ `).map((l) => l.trim()).filter((l) => /^\d+\s*[.、))]/.test(l));
1419
+ if (items.length === 0)
1420
+ return null;
1421
+ const tokens = items.map(firstPathToken);
1422
+ if (tokens.some((t) => t === null))
1423
+ return null;
1424
+ const declared = splitDeliverableSegments(deliverable).map(stripDeliverableToken).filter((t) => t !== null);
1425
+ const declaredSet = new Set(declared);
1426
+ const lines = items.map((item, i) => {
1427
+ const token = tokens[i];
1428
+ const done = declaredSet.has(token);
1429
+ const origNum = item.match(/^(\d+)\s*[.、))]/)?.[1] ?? String(i + 1);
1430
+ return done ? `${origNum}. ${token} 已产出` : `${origNum}. ${token} 未产出`;
1431
+ });
1432
+ const anyUndone = tokens.some((t) => !declaredSet.has(t));
1433
+ const raw = `【逐条对照】${lines.join(";")},目标${anyUndone ? "未完成" : "全部完成"}`;
1434
+ if (raw.length > CHECKLIST_MAX) {
1435
+ const suffix = `目标${anyUndone ? "未完成" : "全部完成"}`;
1436
+ const head = raw.slice(0, CHECKLIST_MAX - suffix.length - 2);
1437
+ const lastSep = head.lastIndexOf(";");
1438
+ const cut = lastSep > 0 ? head.slice(0, lastSep) : head;
1439
+ return `${cut};…,${suffix}`;
1440
+ }
1441
+ return raw;
1442
+ }
1443
+ function renderCompletionState(task, ops) {
1444
+ const goals = (task.goals_and_expected_deliverables || "(无)").replace(/\n/g, " ").slice(0, GOALS_MAX);
1445
+ const deliverable = readDeliverablePath(task) || "(未声明)";
1446
+ const checklist = renderCriteriaChecklist(task.goals_and_expected_deliverables || "", deliverable);
1447
+ return `【任务标题】${task.title}
1448
+ ` + `【任务目标】${goals}
1449
+ ` + `【交付物声明】${deliverable}
1450
+ ` + (checklist ? `${checklist}
1451
+ ` : "") + `【操作记录】
1452
+ ` + renderOperations(ops);
1453
+ }
1454
+ function buildCompletionQuestion() {
1455
+ return {
1456
+ completion_check: {
1457
+ type: "choice",
1458
+ instructions: "根据任务目标、交付物声明与操作记录链,判断该任务是否真正达成目标且产出交付物。" + `若证据充分支持已完成,选 ${CHOICE_PASS};若目标或交付物疑似未达成,选 ${CHOICE_FAIL}。`,
1459
+ criteria: {
1460
+ [CHOICE_PASS]: "目标已达成且交付物已产出",
1461
+ [CHOICE_FAIL]: "目标或交付物疑似未达成"
1462
+ }
1463
+ }
1464
+ };
1465
+ }
1466
+
1467
+ class LayaCompletionPlugin {
1468
+ name = "laya-completion";
1469
+ version = "1.0.0";
1470
+ description = "Task #4760 — task:after.complete 完成判定(目标/交付物 match,只提示不拦截)";
1471
+ options;
1472
+ port = null;
1473
+ portResolved = false;
1474
+ constructor(options = {}) {
1475
+ this.options = options;
1476
+ if (options.decisionPort) {
1477
+ this.port = options.decisionPort;
1478
+ this.portResolved = true;
1479
+ }
1480
+ }
1481
+ isDisabled() {
1482
+ return this.port?.isDisabled() ?? false;
1483
+ }
1484
+ async resolvePort() {
1485
+ if (this.portResolved)
1486
+ return this.port;
1487
+ this.portResolved = true;
1488
+ try {
1489
+ const auth = await loadLayaAuthForPurpose(this.options.authPath ?? `${process.env.HOME ?? "~"}/.roy-agent/auth.json`, "goal");
1490
+ if (!auth)
1491
+ return null;
1492
+ this.port = new LayaDecisionPort({
1493
+ baseURL: auth.baseURL,
1494
+ apiKey: auth.apiKey,
1495
+ model: auth.model,
1496
+ timeoutMs: this.options.timeoutMs,
1497
+ maxStrikes: this.options.maxStrikes
1498
+ });
1499
+ } catch (err) {
1500
+ logger5.warn(`[laya-completion] loadLayaAuth failed (fail-open): ${err?.message}`);
1501
+ this.port = null;
1502
+ }
1503
+ return this.port;
1504
+ }
1505
+ async execute(ctx, taskComponent) {
1506
+ try {
1507
+ const data = normalizeHookCtx2(ctx);
1508
+ await this.run(data, taskComponent);
1509
+ } catch (err) {
1510
+ logger5.warn(`[laya-completion] unexpected failure, ignored: ${err?.message}`);
1511
+ }
1512
+ }
1513
+ async run(ctx, taskComponent) {
1514
+ if (!taskComponent) {
1515
+ logger5.warn("[laya-completion] taskComponent not injected — skip");
1516
+ return;
1517
+ }
1518
+ if (ctx.terminalStatus !== "completed" && ctx.terminalStatus !== "to_validate") {
1519
+ logger5.debug(`[laya-completion] terminalStatus=${ctx.terminalStatus} — skip (only completed / to_validate)`);
1520
+ return;
1521
+ }
1522
+ const task = ctx.task;
1523
+ if (!task?.id)
1524
+ return;
1525
+ let ops = [];
1526
+ try {
1527
+ ops = await taskComponent.listOperations({
1528
+ taskId: task.id,
1529
+ limit: 1e6,
1530
+ offset: 0
1531
+ });
1532
+ } catch (err) {
1533
+ logger5.warn(`[laya-completion] listOperations failed (fail-open): ${err?.message}`);
1534
+ return;
1535
+ }
1536
+ logger5.info(`[laya-completion] judging task #${task.id}: chain=${ops?.length ?? 0} ops, terminalStatus=${ctx.terminalStatus}`);
1537
+ if (!ops || ops.length === 0) {
1538
+ logger5.debug("[laya-completion] empty operation chain — pass through");
1539
+ return;
1540
+ }
1541
+ const port = await this.resolvePort();
1542
+ if (!port) {
1543
+ logger5.debug("[laya-completion] no decision port configured — skip");
1544
+ return;
1545
+ }
1546
+ if (port.isDisabled()) {
1547
+ logger5.debug("[laya-completion] port self-disabled (circuit open) — skip");
1548
+ return;
1549
+ }
1550
+ const req = {
1551
+ state: renderCompletionState(task, ops),
1552
+ questions: buildCompletionQuestion()
1553
+ };
1554
+ let answer;
1555
+ try {
1556
+ const res = await port.decide(req);
1557
+ answer = res?.answers?.["completion_check"];
1558
+ } catch (err) {
1559
+ logger5.warn(`[laya-completion] decide failed (fail-open): ${err?.message}`);
1560
+ return;
1561
+ }
1562
+ if (!answer || typeof answer.choice !== "string") {
1563
+ logger5.debug("[laya-completion] malformed answer — skip");
1564
+ return;
1565
+ }
1566
+ const confidence = pickGateConfidence(answer);
1567
+ const threshold = this.options.completionThreshold ?? LAYA_COMPLETION_THRESHOLD;
1568
+ if (confidence < threshold) {
1569
+ logger5.debug(`[laya-completion] confidence ${confidence} < ${threshold} — pass through`);
1570
+ return;
1571
+ }
1572
+ if (answer.choice !== CHOICE_FAIL) {
1573
+ logger5.debug(`[laya-completion] choice=${answer.choice} (not FAIL_INCOMPLETE) — no hint`);
1574
+ return;
1575
+ }
1576
+ await this.writeHint(task, confidence, taskComponent);
1577
+ }
1578
+ async writeHint(task, confidence, taskComponent) {
1579
+ const hint = buildCompletionHint(task, confidence);
1580
+ try {
1581
+ let parsed = {};
1582
+ try {
1583
+ const p = JSON.parse(task.context || "{}");
1584
+ if (p && typeof p === "object" && !Array.isArray(p)) {
1585
+ parsed = p;
1586
+ }
1587
+ } catch {}
1588
+ parsed.completion_hint = hint;
1589
+ await taskComponent.updateTaskContext(task.id, JSON.stringify(parsed));
1590
+ logger5.info(`[laya-completion] hint written to task #${task.id} context (conf=${confidence.toFixed(4)})`);
1591
+ } catch (err) {
1592
+ logger5.warn(`[laya-completion] updateTask hint failed (ignored): ${err?.message}`);
1593
+ }
1594
+ try {
1595
+ await taskComponent.createOperation({
1596
+ taskId: task.id,
1597
+ sessionId: "laya-completion",
1598
+ milestoneType: "decision",
1599
+ milestoneTitle: "[TASK_COMPLETION_HINT] 目标/交付物疑似未达成",
1600
+ milestoneDescription: `决策模型判定 FAIL_INCOMPLETE(conf=${confidence.toFixed(4)})。hint 已写入 task.context.completion_hint,等待 agent 核实。`
1601
+ });
1602
+ } catch (err) {
1603
+ logger5.warn(`[laya-completion] createOperation hint failed (ignored): ${err?.message}`);
1604
+ }
1605
+ }
1606
+ }
1607
+ __legacyDecorateClassTS([
1608
+ TracedAs("task.laya.completion.onAfterComplete", {
1609
+ recordParams: false,
1610
+ recordResult: false,
1611
+ log: false,
1612
+ paramFilter: (args) => {
1613
+ const raw = args[0] ?? {};
1614
+ const ctx = raw?.data ?? raw;
1615
+ return {
1616
+ taskId: ctx?.task?.id,
1617
+ terminalStatus: ctx?.terminalStatus,
1618
+ hasTaskComponent: Boolean(args[1])
1619
+ };
1620
+ },
1621
+ resultFilter: (_res) => ({ ok: true })
1622
+ })
1623
+ ], LayaCompletionPlugin.prototype, "execute", null);
1624
+ function normalizeHookCtx2(ctx) {
1625
+ const maybe = ctx;
1626
+ return maybe && typeof maybe === "object" && maybe.data ? maybe.data : ctx;
1627
+ }
1628
+ function createLayaCompletionPlugin(options) {
1629
+ const inner = options && Object.keys(options).length > 0 ? new LayaCompletionPlugin(options) : getSharedCompletionPlugin();
1630
+ return {
1631
+ name: "laya-completion",
1632
+ priority: 20,
1633
+ execute: async (ctx) => {
1634
+ await inner.execute(ctx, inner.__taskComponent);
1635
+ },
1636
+ plugin: inner
1637
+ };
1638
+ }
1639
+ var sharedCompletionPlugin = null;
1640
+ function getSharedCompletionPlugin() {
1641
+ if (!sharedCompletionPlugin) {
1642
+ sharedCompletionPlugin = new LayaCompletionPlugin;
1643
+ }
1644
+ return sharedCompletionPlugin;
1645
+ }
1646
+ function registerLayaCompletionTaskHook(taskComponent, options) {
1647
+ if (!taskComponent) {
1648
+ logger5.warn("[registerLayaCompletionTaskHook] TaskComponent not available — skip (fail-open)");
1649
+ return false;
1650
+ }
1651
+ try {
1652
+ const hook = createLayaCompletionPlugin(options);
1653
+ hook.plugin.__taskComponent = taskComponent;
1654
+ taskComponent.unregisterHook(TaskHookPoints.AFTER_COMPLETE, hook.name);
1655
+ taskComponent.registerHook(TaskHookPoints.AFTER_COMPLETE, {
1656
+ name: hook.name,
1657
+ priority: hook.priority,
1658
+ execute: async (ctx) => {
1659
+ try {
1660
+ const data = ctx && typeof ctx === "object" && "data" in ctx ? ctx.data : ctx;
1661
+ await hook.plugin.execute(data, taskComponent);
1662
+ } catch (err) {
1663
+ logger5.warn(`[registerLayaCompletionTaskHook] hook execution failed (ignored): ${err?.message}`);
1664
+ }
1665
+ }
1666
+ });
1667
+ logger5.info(`[registerLayaCompletionTaskHook] registered hook "${hook.name}" at ${TaskHookPoints.AFTER_COMPLETE} (priority=${hook.priority})`);
1668
+ return true;
1669
+ } catch (err) {
1670
+ logger5.warn(`[registerLayaCompletionTaskHook] registration failed (ignored): ${err?.message}`);
1671
+ return false;
1672
+ }
1673
+ }
1257
1674
  // src/env/task/plugins/lark-cli-task-notify-plugin.ts
1258
1675
  init_decorator();
1259
1676
  init_logger();
@@ -1263,7 +1680,7 @@ init_context();
1263
1680
  init_logger();
1264
1681
  init_sqlite_task_store();
1265
1682
  import { join } from "path";
1266
- var logger5 = createLogger("lark-cli:card-state");
1683
+ var logger6 = createLogger("lark-cli:card-state");
1267
1684
 
1268
1685
  class MemoryLarkCardStateStore {
1269
1686
  records = new Map;
@@ -1427,7 +1844,7 @@ function createLarkCardStateStore(dbPath) {
1427
1844
  try {
1428
1845
  return new SqliteLarkCardStateStore(path);
1429
1846
  } catch (err) {
1430
- logger5.warn(`LarkCardStateStore: sqlite unavailable at ${path} (${err.message}) ` + `→ falling back to in-memory (卡片仍可发,但重启后无法 patch 旧卡)`);
1847
+ logger6.warn(`LarkCardStateStore: sqlite unavailable at ${path} (${err.message}) ` + `→ falling back to in-memory (卡片仍可发,但重启后无法 patch 旧卡)`);
1431
1848
  return new MemoryLarkCardStateStore;
1432
1849
  }
1433
1850
  }
@@ -1509,7 +1926,7 @@ function decideCardChatTarget(input) {
1509
1926
  }
1510
1927
 
1511
1928
  // src/env/task/plugins/lark-cli-task-notify-plugin.ts
1512
- var logger6 = createLogger("lark-cli:task-notify");
1929
+ var logger7 = createLogger("lark-cli:task-notify");
1513
1930
  var CTX_FIELD_CHAT_ID = "larkPerTaskChatId";
1514
1931
  var CTX_FIELD_CHAT_PROFILE = "larkPerTaskChatProfile";
1515
1932
  function parseLarkPerTaskChatId(ctxStr) {
@@ -1572,6 +1989,7 @@ class LarkCliTaskNotifyHook {
1572
1989
  cardStateStore;
1573
1990
  restoredTaskIds = new Set;
1574
1991
  lastNotifyTimestamps = new Map;
1992
+ trailingPatchTimers = new Map;
1575
1993
  cardRoutingPorts;
1576
1994
  static CARD_START_FAILED_SENTINEL = "__card_start_failed__";
1577
1995
  static CARD_START_INFLIGHT_SENTINEL = "__card_start_inflight__";
@@ -1599,7 +2017,7 @@ class LarkCliTaskNotifyHook {
1599
2017
  }
1600
2018
  setCardRoutingPorts(ports) {
1601
2019
  this.cardRoutingPorts = ports;
1602
- logger6.debug(`CardRoutingPorts injected: getTask=${typeof ports.getTask}`);
2020
+ logger7.debug(`CardRoutingPorts injected: getTask=${typeof ports.getTask}`);
1603
2021
  }
1604
2022
  hasCardRoutingPorts() {
1605
2023
  return this.cardRoutingPorts !== undefined;
@@ -1612,7 +2030,7 @@ class LarkCliTaskNotifyHook {
1612
2030
  if (this.pluginName && this.sourceId) {
1613
2031
  const key = `${this.sourceId}:${this.pluginName}`;
1614
2032
  if (env.pluginEnabled && env.pluginEnabled[key] !== true) {
1615
- logger6.debug(`Plugin not enabled for source: ${key}`);
2033
+ logger7.debug(`Plugin not enabled for source: ${key}`);
1616
2034
  return;
1617
2035
  }
1618
2036
  }
@@ -1698,6 +2116,10 @@ class LarkCliTaskNotifyHook {
1698
2116
  return;
1699
2117
  if (!this.isCardModeEnabled())
1700
2118
  return;
2119
+ const terminalStatus = data.terminalStatus;
2120
+ if (terminalStatus === "to_validate" || task.status === "to_validate") {
2121
+ return;
2122
+ }
1701
2123
  await this.sendCardFinish(task);
1702
2124
  }
1703
2125
  async onAfterDelete(ctx) {
@@ -1715,11 +2137,11 @@ class LarkCliTaskNotifyHook {
1715
2137
  try {
1716
2138
  record = this.cardStateStore?.get(taskId);
1717
2139
  } catch (err) {
1718
- logger6.warn(`onAfterDelete: 读取卡片记录失败 task #${taskId}: ${err.message}`);
2140
+ logger7.warn(`onAfterDelete: 读取卡片记录失败 task #${taskId}: ${err.message}`);
1719
2141
  }
1720
2142
  const messageId = record?.messageId || this.cardMessageIds.get(taskId);
1721
2143
  if (!messageId) {
1722
- logger6.debug(`onAfterDelete: task #${taskId} 无卡片记录,skip(不发新卡)`);
2144
+ logger7.debug(`onAfterDelete: task #${taskId} 无卡片记录,skip(不发新卡)`);
1723
2145
  return;
1724
2146
  }
1725
2147
  this.restoreCardState(taskId);
@@ -1734,17 +2156,18 @@ class LarkCliTaskNotifyHook {
1734
2156
  finalStep: "任务已取消(删除)"
1735
2157
  });
1736
2158
  } catch (err) {
1737
- logger6.warn(`onAfterDelete: patch 卡片失败 task #${taskId} message=${messageId}: ${err.message}`);
2159
+ logger7.warn(`onAfterDelete: patch 卡片失败 task #${taskId} message=${messageId}: ${err.message}`);
1738
2160
  }
1739
2161
  try {
1740
2162
  await this.cardNotifier.unpinMessage(messageId, profile);
1741
2163
  } catch (err) {
1742
- logger6.warn(`onAfterDelete: unpin 失败 task #${taskId} message=${messageId}: ${err.message}`);
2164
+ logger7.warn(`onAfterDelete: unpin 失败 task #${taskId} message=${messageId}: ${err.message}`);
1743
2165
  }
2166
+ this.cancelTrailingPatch(taskId);
1744
2167
  this.persistClosed(taskId);
1745
2168
  this.persistPinned(taskId, false);
1746
2169
  this.cardMessageIds.delete(taskId);
1747
- logger6.debug(`onAfterDelete: task #${taskId} 卡片已收尾 status=deleted message=${messageId}`);
2170
+ logger7.debug(`onAfterDelete: task #${taskId} 卡片已收尾 status=deleted message=${messageId}`);
1748
2171
  }
1749
2172
  async onAfterOperationCreate(ctx) {
1750
2173
  const { operation } = ctx.data;
@@ -1752,18 +2175,16 @@ class LarkCliTaskNotifyHook {
1752
2175
  return;
1753
2176
  if (!this.shouldNotify("operation_created" /* OPERATION_CREATED */))
1754
2177
  return;
1755
- if (this.shouldThrottleOperation(operation))
1756
- return;
1757
2178
  if (!this.isCardModeEnabled())
1758
2179
  return;
1759
2180
  const persisted = this.restoreCardState(operation.taskId);
1760
2181
  const messageId = this.cardMessageIds.get(operation.taskId);
1761
2182
  if (!messageId || messageId === LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL || messageId === LarkCliTaskNotifyHook.CARD_START_INFLIGHT_SENTINEL) {
1762
- logger6.debug(`onAfterOperationCreate: no usable card for task #${operation.taskId}, skip`);
2183
+ logger7.debug(`onAfterOperationCreate: no usable card for task #${operation.taskId}, skip`);
1763
2184
  return;
1764
2185
  }
1765
2186
  if (persisted && persisted.status !== "open") {
1766
- logger6.debug(`onAfterOperationCreate: task #${operation.taskId} card status=${persisted.status}, skip`);
2187
+ logger7.debug(`onAfterOperationCreate: task #${operation.taskId} card status=${persisted.status}, skip`);
1767
2188
  return;
1768
2189
  }
1769
2190
  const { milestones, processSteps } = this.deriveTimelineFromOperation(operation);
@@ -1771,6 +2192,17 @@ class LarkCliTaskNotifyHook {
1771
2192
  return;
1772
2193
  const env = getEnvContextOrEmpty();
1773
2194
  const profile = env.replyChannel?.profile;
2195
+ this.cardNotifier.mergeTimelineState?.(messageId, milestones, processSteps);
2196
+ this.refreshTimeline(operation.taskId);
2197
+ const throttled = this.shouldThrottleOperation(operation);
2198
+ if (throttled) {
2199
+ this.scheduleTrailingPatch(operation.taskId, messageId, profile);
2200
+ logger7.debug(`[throttle] OPERATION_CREATED for task #${operation.taskId} ` + `merged into state + ledger; card patch deferred to window end`);
2201
+ return;
2202
+ }
2203
+ await this.patchOperationTimeline(operation.taskId, messageId, profile, milestones, processSteps);
2204
+ }
2205
+ async patchOperationTimeline(taskId, messageId, profile, milestones, processSteps) {
1774
2206
  try {
1775
2207
  await this.cardNotifier.updateTask({
1776
2208
  messageId,
@@ -1780,9 +2212,41 @@ class LarkCliTaskNotifyHook {
1780
2212
  processSteps
1781
2213
  });
1782
2214
  } catch (err) {
1783
- logger6.error(`sendCardOperation failed for task #${operation.taskId}: ${err.message}`);
2215
+ logger7.error(`sendCardOperation failed for task #${taskId}: ${err.message}`);
1784
2216
  }
1785
2217
  }
2218
+ scheduleTrailingPatch(taskId, messageId, profile) {
2219
+ const existing = this.trailingPatchTimers.get(taskId);
2220
+ if (existing) {
2221
+ return;
2222
+ }
2223
+ const throttleMs = this.config.operationThrottleMs ?? 5000;
2224
+ const delay = throttleMs > 0 ? throttleMs : 0;
2225
+ const timer = setTimeout(() => {
2226
+ this.trailingPatchTimers.delete(taskId);
2227
+ const liveMessageId = this.cardMessageIds.get(taskId);
2228
+ if (liveMessageId !== messageId) {
2229
+ logger7.debug(`[throttle] trailing patch skipped for task #${taskId}: ` + `card no longer tracked (finished/closed)`);
2230
+ return;
2231
+ }
2232
+ const rec = this.cardStateStore?.get(taskId);
2233
+ if (rec && rec.status !== "open") {
2234
+ logger7.debug(`[throttle] trailing patch skipped for task #${taskId}: ` + `card status=${rec.status}`);
2235
+ return;
2236
+ }
2237
+ this.patchOperationTimeline(taskId, messageId, profile, [], []);
2238
+ }, delay);
2239
+ if (typeof timer.unref === "function")
2240
+ timer.unref();
2241
+ this.trailingPatchTimers.set(taskId, timer);
2242
+ }
2243
+ cancelTrailingPatch(taskId) {
2244
+ const timer = this.trailingPatchTimers.get(taskId);
2245
+ if (!timer)
2246
+ return;
2247
+ clearTimeout(timer);
2248
+ this.trailingPatchTimers.delete(taskId);
2249
+ }
1786
2250
  restoreCardState(taskId) {
1787
2251
  if (!this.cardStateStore)
1788
2252
  return;
@@ -1795,12 +2259,12 @@ class LarkCliTaskNotifyHook {
1795
2259
  if (rec.status !== "closed" && rec.messageId) {
1796
2260
  this.cardMessageIds.set(taskId, rec.messageId);
1797
2261
  this.hydrateNotifierState(rec);
1798
- logger6.debug(`restoreCardState: task #${taskId} ← 持久层 message ${rec.messageId} ` + `(status=${rec.status}, pinned=${rec.pinned}, timeline=${rec.timeline ? "yes" : "no"})`);
2262
+ logger7.debug(`restoreCardState: task #${taskId} ← 持久层 message ${rec.messageId} ` + `(status=${rec.status}, pinned=${rec.pinned}, timeline=${rec.timeline ? "yes" : "no"})`);
1799
2263
  }
1800
2264
  }
1801
2265
  return rec;
1802
2266
  } catch (err) {
1803
- logger6.warn(`restoreCardState failed for task #${taskId}: ${err.message}`);
2267
+ logger7.warn(`restoreCardState failed for task #${taskId}: ${err.message}`);
1804
2268
  return;
1805
2269
  }
1806
2270
  }
@@ -1811,7 +2275,7 @@ class LarkCliTaskNotifyHook {
1811
2275
  const snapshot = JSON.parse(rec.timeline);
1812
2276
  this.cardNotifier.hydrateState?.(rec.messageId, snapshot);
1813
2277
  } catch (err) {
1814
- logger6.warn(`hydrateNotifierState failed for task #${rec.taskId}: ${err.message}`);
2278
+ logger7.warn(`hydrateNotifierState failed for task #${rec.taskId}: ${err.message}`);
1815
2279
  }
1816
2280
  }
1817
2281
  snapshotTimeline(state) {
@@ -1823,7 +2287,7 @@ class LarkCliTaskNotifyHook {
1823
2287
  return state;
1824
2288
  return { ...state, timeline: JSON.stringify(snap) };
1825
2289
  } catch (err) {
1826
- logger6.warn(`snapshotTimeline failed for task #${state.taskId}: ${err.message}`);
2290
+ logger7.warn(`snapshotTimeline failed for task #${state.taskId}: ${err.message}`);
1827
2291
  return state;
1828
2292
  }
1829
2293
  }
@@ -1834,7 +2298,7 @@ class LarkCliTaskNotifyHook {
1834
2298
  const payload = includeTimeline ? this.snapshotTimeline(state) : state;
1835
2299
  this.cardStateStore.upsert(payload);
1836
2300
  } catch (err) {
1837
- logger6.warn(`persistCardState failed for task #${state.taskId}: ${err.message}`);
2301
+ logger7.warn(`persistCardState failed for task #${state.taskId}: ${err.message}`);
1838
2302
  }
1839
2303
  }
1840
2304
  persistPinned(taskId, pinned) {
@@ -1843,7 +2307,7 @@ class LarkCliTaskNotifyHook {
1843
2307
  try {
1844
2308
  this.cardStateStore.markPinned(taskId, pinned);
1845
2309
  } catch (err) {
1846
- logger6.warn(`persistPinned failed for task #${taskId}: ${err.message}`);
2310
+ logger7.warn(`persistPinned failed for task #${taskId}: ${err.message}`);
1847
2311
  }
1848
2312
  }
1849
2313
  persistPaused(taskId) {
@@ -1852,7 +2316,7 @@ class LarkCliTaskNotifyHook {
1852
2316
  try {
1853
2317
  this.cardStateStore.markPaused(taskId);
1854
2318
  } catch (err) {
1855
- logger6.warn(`persistPaused failed for task #${taskId}: ${err.message}`);
2319
+ logger7.warn(`persistPaused failed for task #${taskId}: ${err.message}`);
1856
2320
  }
1857
2321
  }
1858
2322
  dropStaleMessageId(taskId) {
@@ -1869,7 +2333,7 @@ class LarkCliTaskNotifyHook {
1869
2333
  updatedAt: Date.now()
1870
2334
  });
1871
2335
  } catch (err) {
1872
- logger6.warn(`dropStaleMessageId failed for task #${taskId}: ${err.message}`);
2336
+ logger7.warn(`dropStaleMessageId failed for task #${taskId}: ${err.message}`);
1873
2337
  }
1874
2338
  }
1875
2339
  persistReopened(taskId) {
@@ -1878,7 +2342,7 @@ class LarkCliTaskNotifyHook {
1878
2342
  try {
1879
2343
  this.cardStateStore.markReopened(taskId);
1880
2344
  } catch (err) {
1881
- logger6.warn(`persistReopened failed for task #${taskId}: ${err.message}`);
2345
+ logger7.warn(`persistReopened failed for task #${taskId}: ${err.message}`);
1882
2346
  }
1883
2347
  }
1884
2348
  persistClosed(taskId) {
@@ -1887,7 +2351,7 @@ class LarkCliTaskNotifyHook {
1887
2351
  try {
1888
2352
  this.cardStateStore.markClosed(taskId);
1889
2353
  } catch (err) {
1890
- logger6.warn(`persistClosed failed for task #${taskId}: ${err.message}`);
2354
+ logger7.warn(`persistClosed failed for task #${taskId}: ${err.message}`);
1891
2355
  }
1892
2356
  }
1893
2357
  refreshTimeline(taskId) {
@@ -1899,7 +2363,7 @@ class LarkCliTaskNotifyHook {
1899
2363
  return;
1900
2364
  this.cardStateStore.upsert(this.snapshotTimeline(rec));
1901
2365
  } catch (err) {
1902
- logger6.warn(`refreshTimeline failed for task #${taskId}: ${err.message}`);
2366
+ logger7.warn(`refreshTimeline failed for task #${taskId}: ${err.message}`);
1903
2367
  }
1904
2368
  }
1905
2369
  async repinAfterResume(task, persisted) {
@@ -1915,9 +2379,9 @@ class LarkCliTaskNotifyHook {
1915
2379
  try {
1916
2380
  await this.cardNotifier.pinMessage(persisted.messageId, profile);
1917
2381
  this.persistPinned(task.id, true);
1918
- logger6.debug(`repinAfterResume: task #${task.id} 卡片 ${persisted.messageId} 已重新置顶`);
2382
+ logger7.debug(`repinAfterResume: task #${task.id} 卡片 ${persisted.messageId} 已重新置顶`);
1919
2383
  } catch (err) {
1920
- logger6.warn(`repinAfterResume failed (non-fatal) for task #${task.id}: ${err.message}`);
2384
+ logger7.warn(`repinAfterResume failed (non-fatal) for task #${task.id}: ${err.message}`);
1921
2385
  }
1922
2386
  }
1923
2387
  buildCardState(task, messageId, chatId, profile, pinned) {
@@ -1925,7 +2389,7 @@ class LarkCliTaskNotifyHook {
1925
2389
  try {
1926
2390
  existing = this.cardStateStore?.get(task.id);
1927
2391
  } catch (err) {
1928
- logger6.warn(`buildCardState: read existing failed for task #${task.id}: ${err.message}`);
2392
+ logger7.warn(`buildCardState: read existing failed for task #${task.id}: ${err.message}`);
1929
2393
  }
1930
2394
  const now = Date.now();
1931
2395
  return {
@@ -1959,7 +2423,7 @@ class LarkCliTaskNotifyHook {
1959
2423
  }
1960
2424
  isCardModeEnabled() {
1961
2425
  if (this.config.cardMode === false || !this.cardNotifier) {
1962
- logger6.debug("cardMode=false (legacy opt-out), skip task notification (no text fallback)");
2426
+ logger7.debug("cardMode=false (legacy opt-out), skip task notification (no text fallback)");
1963
2427
  return false;
1964
2428
  }
1965
2429
  return true;
@@ -2029,7 +2493,7 @@ class LarkCliTaskNotifyHook {
2029
2493
  }
2030
2494
  const claim = this.cardMessageIds.get(task.id);
2031
2495
  if (claim) {
2032
- logger6.debug(`sendCardStart: task #${task.id} 已有首卡(${claim})→ 跳过重复发卡`);
2496
+ logger7.debug(`sendCardStart: task #${task.id} 已有首卡(${claim})→ 跳过重复发卡`);
2033
2497
  return;
2034
2498
  }
2035
2499
  this.cardMessageIds.set(task.id, LarkCliTaskNotifyHook.CARD_START_INFLIGHT_SENTINEL);
@@ -2044,7 +2508,7 @@ class LarkCliTaskNotifyHook {
2044
2508
  const profile = target.profile;
2045
2509
  if (!chatId) {
2046
2510
  this.cardMessageIds.delete(task.id);
2047
- logger6.debug(`sendCardStart: no chatId for task #${task.id}, skip`);
2511
+ logger7.debug(`sendCardStart: no chatId for task #${task.id}, skip`);
2048
2512
  return;
2049
2513
  }
2050
2514
  try {
@@ -2062,10 +2526,10 @@ class LarkCliTaskNotifyHook {
2062
2526
  });
2063
2527
  this.cardMessageIds.set(task.id, messageId);
2064
2528
  this.persistCardState(this.buildCardState(task, messageId, chatId, profile, pinned), true);
2065
- logger6.debug(`sendCardStart: task #${task.id} → message ${messageId} (pinned=${pinned}) ` + `chat=${chatId} source=${target.source} reason=${target.reason}`);
2529
+ logger7.debug(`sendCardStart: task #${task.id} → message ${messageId} (pinned=${pinned}) ` + `chat=${chatId} source=${target.source} reason=${target.reason}`);
2066
2530
  } catch (err) {
2067
2531
  this.cardMessageIds.set(task.id, LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL);
2068
- logger6.error(`sendCardStart failed for task #${task.id}: ${err.message}`);
2532
+ logger7.error(`sendCardStart failed for task #${task.id}: ${err.message}`);
2069
2533
  }
2070
2534
  }
2071
2535
  async sendCardUpdate(task, changes) {
@@ -2075,21 +2539,21 @@ class LarkCliTaskNotifyHook {
2075
2539
  if (persisted?.status === "closed") {
2076
2540
  const stillTerminal = isTerminalTaskStatus(String(task.status));
2077
2541
  if (stillTerminal) {
2078
- logger6.debug(`sendCardUpdate: task #${task.id} 卡片已终结(closed)且任务仍终态 → skip patch`);
2542
+ logger7.debug(`sendCardUpdate: task #${task.id} 卡片已终结(closed)且任务仍终态 → skip patch`);
2079
2543
  return;
2080
2544
  }
2081
- logger6.info(`sendCardUpdate: task #${task.id} 已 reopen(closed 记录 + 当前 status=${task.status})` + `→ 重新建卡`);
2545
+ logger7.info(`sendCardUpdate: task #${task.id} 已 reopen(closed 记录 + 当前 status=${task.status})` + `→ 重新建卡`);
2082
2546
  this.cardMessageIds.delete(task.id);
2083
2547
  this.dropStaleMessageId(task.id);
2084
2548
  }
2085
2549
  if (persisted?.status === "paused" && String(task.status) !== "paused") {
2086
2550
  this.persistReopened(task.id);
2087
- logger6.debug(`sendCardUpdate: task #${task.id} resume(paused → open),复用卡片 ${persisted.messageId}`);
2551
+ logger7.debug(`sendCardUpdate: task #${task.id} resume(paused → open),复用卡片 ${persisted.messageId}`);
2088
2552
  await this.repinAfterResume(task, persisted);
2089
2553
  }
2090
2554
  let messageId = this.cardMessageIds.get(task.id);
2091
2555
  if (!messageId && isTerminalTaskStatus(String(task.status))) {
2092
- logger6.debug(`sendCardUpdate: task #${task.id} 状态=${task.status}(终结/归档)且无卡片 → skip`);
2556
+ logger7.debug(`sendCardUpdate: task #${task.id} 状态=${task.status}(终结/归档)且无卡片 → skip`);
2093
2557
  return;
2094
2558
  }
2095
2559
  if (!messageId) {
@@ -2101,15 +2565,15 @@ class LarkCliTaskNotifyHook {
2101
2565
  if (!chatId) {
2102
2566
  if (!messageId)
2103
2567
  this.cardMessageIds.delete(task.id);
2104
- logger6.debug(`sendCardUpdate: no chatId for task #${task.id}, skip`);
2568
+ logger7.debug(`sendCardUpdate: no chatId for task #${task.id}, skip`);
2105
2569
  return;
2106
2570
  }
2107
2571
  if (messageId === LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL) {
2108
- logger6.debug(`sendCardUpdate: startTask failed previously for task #${task.id}, skip`);
2572
+ logger7.debug(`sendCardUpdate: startTask failed previously for task #${task.id}, skip`);
2109
2573
  return;
2110
2574
  }
2111
2575
  if (messageId === LarkCliTaskNotifyHook.CARD_START_INFLIGHT_SENTINEL) {
2112
- logger6.debug(`sendCardUpdate: task #${task.id} 首卡正在发送中(in-flight)→ 跳过本次 update`);
2576
+ logger7.debug(`sendCardUpdate: task #${task.id} 首卡正在发送中(in-flight)→ 跳过本次 update`);
2113
2577
  return;
2114
2578
  }
2115
2579
  if (!messageId) {
@@ -2124,7 +2588,7 @@ class LarkCliTaskNotifyHook {
2124
2588
  this.persistCardState(this.buildCardState(task, messageId, chatId, profile, res.pinned), true);
2125
2589
  } catch (err) {
2126
2590
  this.cardMessageIds.set(task.id, LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL);
2127
- logger6.error(`sendCardUpdate fallback start failed: ${err.message}`);
2591
+ logger7.error(`sendCardUpdate fallback start failed: ${err.message}`);
2128
2592
  return;
2129
2593
  }
2130
2594
  }
@@ -2151,7 +2615,7 @@ class LarkCliTaskNotifyHook {
2151
2615
  this.persistPinned(task.id, current?.pinned ?? persisted?.pinned ?? true);
2152
2616
  this.refreshTimeline(task.id);
2153
2617
  } catch (err) {
2154
- logger6.error(`sendCardUpdate failed: ${err.message}`);
2618
+ logger7.error(`sendCardUpdate failed: ${err.message}`);
2155
2619
  }
2156
2620
  }
2157
2621
  async sendCardFinish(task) {
@@ -2162,13 +2626,14 @@ class LarkCliTaskNotifyHook {
2162
2626
  const ctx = getEnvContextOrEmpty();
2163
2627
  const profile = this.resolveProfile(task, ctx);
2164
2628
  if (!messageId || messageId === LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL) {
2165
- logger6.debug(`sendCardFinish: no card messageId for task #${task.id}, skip ` + `(记录缺失 → 不发新卡,保持幂等)`);
2629
+ logger7.debug(`sendCardFinish: no card messageId for task #${task.id}, skip ` + `(记录缺失 → 不发新卡,保持幂等)`);
2166
2630
  return;
2167
2631
  }
2168
2632
  const rawStatus = String(task.status);
2169
2633
  const status = mapTaskStatusToCardStatus(rawStatus);
2170
2634
  const isPaused = rawStatus === "paused" || rawStatus === "archived";
2171
2635
  try {
2636
+ this.cancelTrailingPatch(task.id);
2172
2637
  const finish = isPaused ? this.cardNotifier.pauseTask.bind(this.cardNotifier) : this.cardNotifier.finishTask.bind(this.cardNotifier);
2173
2638
  await finish({
2174
2639
  messageId,
@@ -2188,9 +2653,9 @@ class LarkCliTaskNotifyHook {
2188
2653
  this.persistPinned(task.id, false);
2189
2654
  this.cardMessageIds.delete(task.id);
2190
2655
  }
2191
- logger6.debug(`sendCardFinish: task #${task.id} → status=${status} ` + `(raw=${rawStatus}) message=${messageId}`);
2656
+ logger7.debug(`sendCardFinish: task #${task.id} → status=${status} ` + `(raw=${rawStatus}) message=${messageId}`);
2192
2657
  } catch (err) {
2193
- logger6.error(`sendCardFinish failed: ${err.message}`);
2658
+ logger7.error(`sendCardFinish failed: ${err.message}`);
2194
2659
  }
2195
2660
  }
2196
2661
  resolveChatId(task, env) {
@@ -2267,7 +2732,7 @@ class LarkCliTaskNotifyHook {
2267
2732
  const now = Date.now();
2268
2733
  const lastTime = this.lastNotifyTimestamps.get(key);
2269
2734
  if (lastTime && now - lastTime < throttleMs) {
2270
- logger6.debug(`[throttle] skip OPERATION_CREATED patch for task #${operation.taskId} (${now - lastTime}ms ago)`);
2735
+ logger7.debug(`[throttle] skip OPERATION_CREATED patch for task #${operation.taskId} (${now - lastTime}ms ago)`);
2271
2736
  return true;
2272
2737
  }
2273
2738
  this.lastNotifyTimestamps.set(key, now);
@@ -2310,7 +2775,7 @@ init_decorator();
2310
2775
  init_logger();
2311
2776
  init_context();
2312
2777
  import { spawn } from "child_process";
2313
- var logger7 = createLogger("lark-cli:per-task-group");
2778
+ var logger8 = createLogger("lark-cli:per-task-group");
2314
2779
  function isPluginDebugEnabled() {
2315
2780
  return process.env.ROY_TASK_NOTIFY_DEBUG === "1" || process.env.ROY_TRACE_DEBUG === "1";
2316
2781
  }
@@ -2365,7 +2830,7 @@ class PerTaskGroupChatPlugin {
2365
2830
  const renderedName = this.renderTemplate(this.config.nameTemplate, task);
2366
2831
  const existingChatId = this.getTaskChatId(task);
2367
2832
  if (existingChatId) {
2368
- logger7.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 已有专属群 ${existingChatId} → 复用(不重复建群)`);
2833
+ logger8.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 已有专属群 ${existingChatId} → 复用(不重复建群)`);
2369
2834
  return {
2370
2835
  chatId: existingChatId,
2371
2836
  profile: this.parseProfileFromContext(task.context),
@@ -2374,7 +2839,7 @@ class PerTaskGroupChatPlugin {
2374
2839
  }
2375
2840
  const inflight = this.groupChatInflight.get(task.id);
2376
2841
  if (inflight) {
2377
- logger7.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 建群已在进行中 → 复用同一 promise`);
2842
+ logger8.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 建群已在进行中 → 复用同一 promise`);
2378
2843
  return inflight;
2379
2844
  }
2380
2845
  const pending = this.doEnsureGroupChat(task, renderedName, replyChannelOverride);
@@ -2388,7 +2853,7 @@ class PerTaskGroupChatPlugin {
2388
2853
  async doEnsureGroupChat(task, name, replyChannelOverride) {
2389
2854
  const replyChannel = replyChannelOverride ?? getEnvContextOrEmpty().replyChannel;
2390
2855
  if (!replyChannel?.chatId && this.config.requireReplyChannel) {
2391
- logger7.warn(`ensureGroupChat[${this.pluginName}]: no replyChannel.chatId for task #${task.id} ` + `(requireReplyChannel=true) → 无法建群`);
2856
+ logger8.warn(`ensureGroupChat[${this.pluginName}]: no replyChannel.chatId for task #${task.id} ` + `(requireReplyChannel=true) → 无法建群`);
2392
2857
  return;
2393
2858
  }
2394
2859
  const description = this.renderTemplate(this.config.descriptionTemplate, task);
@@ -2406,30 +2871,30 @@ class PerTaskGroupChatPlugin {
2406
2871
  await this.writeChatBinding(task, chatId, profile, "creating");
2407
2872
  await this.attachChatIdToTask(task, chatId, profile);
2408
2873
  await this.writeChatBinding(task, chatId, profile, "ready");
2409
- logger7.info(`ensureGroupChat[${this.pluginName}]: created group ${chatId} for task #${task.id}`);
2874
+ logger8.info(`ensureGroupChat[${this.pluginName}]: created group ${chatId} for task #${task.id}`);
2410
2875
  return { chatId, profile, name };
2411
2876
  } catch (error) {
2412
- logger7.error(`ensureGroupChat[${this.pluginName}]: failed for task #${task.id}: ${error}`);
2877
+ logger8.error(`ensureGroupChat[${this.pluginName}]: failed for task #${task.id}: ${error}`);
2413
2878
  return;
2414
2879
  }
2415
2880
  }
2416
2881
  setTaskUpdater(updater) {
2417
2882
  this.taskUpdater = updater;
2418
- logger7.debug(`TaskUpdater injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2883
+ logger8.debug(`TaskUpdater injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2419
2884
  }
2420
2885
  hasTaskUpdater() {
2421
2886
  return this.taskUpdater !== undefined;
2422
2887
  }
2423
2888
  setTaskGetter(getter) {
2424
2889
  this.taskGetter = getter;
2425
- logger7.debug(`TaskGetter injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2890
+ logger8.debug(`TaskGetter injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2426
2891
  }
2427
2892
  hasTaskGetter() {
2428
2893
  return this.taskGetter !== undefined;
2429
2894
  }
2430
2895
  setSessionComponent(sessionComponent) {
2431
2896
  this.sessionComponent = sessionComponent;
2432
- logger7.debug(`SessionComponent injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2897
+ logger8.debug(`SessionComponent injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2433
2898
  }
2434
2899
  hasSessionComponent() {
2435
2900
  return this.sessionComponent !== undefined;
@@ -2442,7 +2907,7 @@ class PerTaskGroupChatPlugin {
2442
2907
  if (process.env.ROY_HOOK_DEBUG === "1") {
2443
2908
  console.error(`[DEBUG per-task-group-chat.execute] → SKIP (config.enabled=false)`);
2444
2909
  }
2445
- logger7.debug(`execute: plugin disabled (pluginName=${this.pluginName})`);
2910
+ logger8.debug(`execute: plugin disabled (pluginName=${this.pluginName})`);
2446
2911
  return;
2447
2912
  }
2448
2913
  if (this.pluginName && this.sourceId) {
@@ -2455,13 +2920,13 @@ class PerTaskGroupChatPlugin {
2455
2920
  if (process.env.ROY_HOOK_DEBUG === "1") {
2456
2921
  console.error(`[DEBUG per-task-group-chat.execute] → SKIP (plugin NOT enabled for ${key})`);
2457
2922
  }
2458
- logger7.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`);
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`);
2459
2924
  return;
2460
2925
  }
2461
2926
  const enabledState = env.pluginEnabled ? env.pluginEnabled[key] === true ? "✅ enabled" : "❌ NOT enabled" : "⚠️ env.pluginEnabled 未设置 (env 没创建 / 插件未注册到 eventsource)";
2462
- logger7.debug(`execute[${this.pluginName}]: ${enabledState} (key=${key}, ` + `hookPoint=${ctx.hookPoint}, taskUpdaterInjected=${this.hasTaskUpdater()})`);
2927
+ logger8.debug(`execute[${this.pluginName}]: ${enabledState} (key=${key}, ` + `hookPoint=${ctx.hookPoint}, taskUpdaterInjected=${this.hasTaskUpdater()})`);
2463
2928
  } else {
2464
- logger7.debug(`execute[${this.pluginName}]: missing pluginName/sourceId metadata ` + `(pluginName=${this.pluginName}, sourceId=${this.sourceId}), ` + `skipping pluginEnabled check. hookPoint=${ctx.hookPoint}`);
2929
+ logger8.debug(`execute[${this.pluginName}]: missing pluginName/sourceId metadata ` + `(pluginName=${this.pluginName}, sourceId=${this.sourceId}), ` + `skipping pluginEnabled check. hookPoint=${ctx.hookPoint}`);
2465
2930
  }
2466
2931
  if (process.env.ROY_HOOK_DEBUG === "1") {
2467
2932
  console.error(`[DEBUG per-task-group-chat.execute] → PASSED pluginEnabled check, will dispatch hookPoint=${ctx.hookPoint}`);
@@ -2480,13 +2945,13 @@ class PerTaskGroupChatPlugin {
2480
2945
  await this.onAfterComplete(ctx);
2481
2946
  break;
2482
2947
  default:
2483
- logger7.debug(`execute: unhandled hookPoint=${ctx.hookPoint}, skip`);
2948
+ logger8.debug(`execute: unhandled hookPoint=${ctx.hookPoint}, skip`);
2484
2949
  }
2485
2950
  }
2486
2951
  async onAfterCreate(ctx) {
2487
2952
  const data = ctx.data;
2488
2953
  if (!data?.task) {
2489
- logger7.debug("onAfterCreate: task is missing, skip");
2954
+ logger8.debug("onAfterCreate: task is missing, skip");
2490
2955
  return;
2491
2956
  }
2492
2957
  const { task } = data;
@@ -2497,38 +2962,38 @@ class PerTaskGroupChatPlugin {
2497
2962
  console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.messageId=${env.messageId}`);
2498
2963
  console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.sourceId=${env.sourceId}`);
2499
2964
  }
2500
- logger7.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}`);
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}`);
2501
2966
  if (!this.config.autoCreateGroup) {
2502
- logger7.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} autoCreateGroup=false ` + `→ skip group chat creation(按需建群:用户在卡片顶部按钮触发)`);
2967
+ logger8.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} autoCreateGroup=false ` + `→ skip group chat creation(按需建群:用户在卡片顶部按钮触发)`);
2503
2968
  return;
2504
2969
  }
2505
2970
  const replyChannel = this.getReplyChannelFromCtx(ctx);
2506
2971
  if (!replyChannel?.chatId && this.config.requireReplyChannel) {
2507
- logger7.debug(`onAfterCreate[${this.pluginName}]: no replyChannel.chatId for task #${task.id}, skip. ` + `修复:eventsource 收到 im.message.receive_v1 事件时 replyChannel 应有值;` + `或在 plugin config 设 requireReplyChannel=false 强制创建群聊(profile 降级到 fallbackProfile/无)`);
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/无)`);
2508
2973
  return;
2509
2974
  }
2510
2975
  if (!replyChannel?.chatId && !this.config.requireReplyChannel) {
2511
- logger7.debug(`onAfterCreate[${this.pluginName}]: no replyChannel but requireReplyChannel=false, ` + `proceeding with profile fallback (fallbackProfile="${this.config.fallbackProfile}")`);
2976
+ logger8.debug(`onAfterCreate[${this.pluginName}]: no replyChannel but requireReplyChannel=false, ` + `proceeding with profile fallback (fallbackProfile="${this.config.fallbackProfile}")`);
2512
2977
  }
2513
2978
  if (this.isSubTask(task) && !this.config.disableSubTaskSkip) {
2514
- logger7.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task, ` + `skip group chat creation. 关闭方式:config.disableSubTaskSkip=true`);
2979
+ logger8.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task, ` + `skip group chat creation. 关闭方式:config.disableSubTaskSkip=true`);
2515
2980
  return;
2516
2981
  }
2517
2982
  if (this.isSubTask(task) && this.config.disableSubTaskSkip) {
2518
- logger7.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task but ` + `disableSubTaskSkip=true, proceeding to create group chat`);
2983
+ logger8.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task but ` + `disableSubTaskSkip=true, proceeding to create group chat`);
2519
2984
  }
2520
2985
  try {
2521
2986
  const created = await this.ensureGroupChat(task, replyChannel);
2522
2987
  if (!created?.chatId) {
2523
- logger7.error(`onAfterCreate[${this.pluginName}]: ensureGroupChat 未返回 chatId for task #${task.id}`);
2988
+ logger8.error(`onAfterCreate[${this.pluginName}]: ensureGroupChat 未返回 chatId for task #${task.id}`);
2524
2989
  if (!this.config.fallbackToMain) {
2525
2990
  throw new Error(`per-task group chat unavailable for task #${task.id}`);
2526
2991
  }
2527
2992
  return;
2528
2993
  }
2529
- logger7.info(`onAfterCreate[${this.pluginName}]: task #${task.id} → group ${created.chatId} ` + `(initial card owned by LarkCliTaskNotifyHook)`);
2994
+ logger8.info(`onAfterCreate[${this.pluginName}]: task #${task.id} → group ${created.chatId} ` + `(initial card owned by LarkCliTaskNotifyHook)`);
2530
2995
  } catch (error) {
2531
- logger7.error(`onAfterCreate[${this.pluginName}]: group chat failed for task #${task.id}: ${error}`);
2996
+ logger8.error(`onAfterCreate[${this.pluginName}]: group chat failed for task #${task.id}: ${error}`);
2532
2997
  if (!this.config.fallbackToMain)
2533
2998
  throw error;
2534
2999
  }
@@ -2536,7 +3001,7 @@ class PerTaskGroupChatPlugin {
2536
3001
  async writeChatBinding(task, chatId, profile, status, errorMessage) {
2537
3002
  if (!this.sessionComponent) {
2538
3003
  if (isPluginDebugEnabled()) {
2539
- logger7.debug(`writeChatBinding[${this.pluginName}]: no sessionComponent injected, ` + `skip chat-task binding for chat ${chatId} (status=${status})`);
3004
+ logger8.debug(`writeChatBinding[${this.pluginName}]: no sessionComponent injected, ` + `skip chat-task binding for chat ${chatId} (status=${status})`);
2540
3005
  }
2541
3006
  return;
2542
3007
  }
@@ -2549,37 +3014,37 @@ class PerTaskGroupChatPlugin {
2549
3014
  errorMessage: errorMessage ?? null
2550
3015
  });
2551
3016
  } catch (bindErr) {
2552
- logger7.warn(`writeChatBinding[${this.pluginName}]: failed to write chat-task binding for ` + `chat ${chatId} / task #${task.id} (status=${status}): ${bindErr}. ` + `群消息 handler 将无法自动注入 parentTaskId。`);
3017
+ logger8.warn(`writeChatBinding[${this.pluginName}]: failed to write chat-task binding for ` + `chat ${chatId} / task #${task.id} (status=${status}): ${bindErr}. ` + `群消息 handler 将无法自动注入 parentTaskId。`);
2553
3018
  }
2554
3019
  }
2555
3020
  async onAfterUpdate(ctx) {
2556
3021
  const data = ctx.data;
2557
3022
  if (!data?.task) {
2558
- logger7.debug("onAfterUpdate: task is missing, skip");
3023
+ logger8.debug("onAfterUpdate: task is missing, skip");
2559
3024
  return;
2560
3025
  }
2561
- logger7.debug(`onAfterUpdate[${this.pluginName}]: task #${data.task.id} 卡片由 ` + `LarkCliTaskNotifyHook 负责(本插件不再 patch),skip`);
3026
+ logger8.debug(`onAfterUpdate[${this.pluginName}]: task #${data.task.id} 卡片由 ` + `LarkCliTaskNotifyHook 负责(本插件不再 patch),skip`);
2562
3027
  }
2563
3028
  async onAfterOperationCreate(ctx) {
2564
3029
  const data = ctx.data;
2565
3030
  if (!data?.operation) {
2566
- logger7.debug("onAfterOperationCreate: operation is missing, skip");
3031
+ logger8.debug("onAfterOperationCreate: operation is missing, skip");
2567
3032
  return;
2568
3033
  }
2569
- logger7.debug(`onAfterOperationCreate[${this.pluginName}]: operation #${data.operation.id} ` + `卡片由 LarkCliTaskNotifyHook 负责,skip`);
3034
+ logger8.debug(`onAfterOperationCreate[${this.pluginName}]: operation #${data.operation.id} ` + `卡片由 LarkCliTaskNotifyHook 负责,skip`);
2570
3035
  }
2571
3036
  async onAfterComplete(ctx) {
2572
3037
  const data = ctx.data;
2573
3038
  if (!data?.task) {
2574
- logger7.debug("onAfterComplete: task is missing, skip");
3039
+ logger8.debug("onAfterComplete: task is missing, skip");
2575
3040
  return;
2576
3041
  }
2577
- logger7.debug(`onAfterComplete[${this.pluginName}]: task #${data.task.id} 终态卡片由 ` + `LarkCliTaskNotifyHook 负责,skip`);
3042
+ logger8.debug(`onAfterComplete[${this.pluginName}]: task #${data.task.id} 终态卡片由 ` + `LarkCliTaskNotifyHook 负责,skip`);
2578
3043
  }
2579
3044
  createGroupChat(opts) {
2580
3045
  const args = this.buildCreateGroupChatArgs(opts);
2581
3046
  if (isPluginDebugEnabled()) {
2582
- logger7.debug(`Creating group chat: lark-cli ${args.join(" ")}`);
3047
+ logger8.debug(`Creating group chat: lark-cli ${args.join(" ")}`);
2583
3048
  }
2584
3049
  return new Promise((resolve, reject) => {
2585
3050
  const proc = spawn("lark-cli", args, {
@@ -2654,16 +3119,16 @@ class PerTaskGroupChatPlugin {
2654
3119
  parsed[CTX_FIELD_CHAT_TASK_ID] = task.id;
2655
3120
  const newContext = JSON.stringify(parsed);
2656
3121
  if (!this.taskUpdater) {
2657
- logger7.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。`);
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。`);
2658
3123
  task.context = newContext;
2659
3124
  return;
2660
3125
  }
2661
3126
  try {
2662
3127
  await this.taskUpdater(task.id, newContext);
2663
3128
  task.context = newContext;
2664
- logger7.debug(`attachChatIdToTask[${this.pluginName}]: ✅ persisted chat_id ${chatId} ` + `to task #${task.id} context`);
3129
+ logger8.debug(`attachChatIdToTask[${this.pluginName}]: ✅ persisted chat_id ${chatId} ` + `to task #${task.id} context`);
2665
3130
  } catch (error) {
2666
- logger7.error(`Failed to persist chat_id to task #${task.id}: ${error}`);
3131
+ logger8.error(`Failed to persist chat_id to task #${task.id}: ${error}`);
2667
3132
  task.context = newContext;
2668
3133
  }
2669
3134
  }
@@ -2758,15 +3223,15 @@ class PerTaskGroupChatPlugin {
2758
3223
  }
2759
3224
  resolveBotAppIds(replyChannel) {
2760
3225
  if (this.config.botAppIds.length > 0) {
2761
- logger7.debug(`resolveBotAppIds[${this.pluginName}]: using config.botAppIds=${JSON.stringify(this.config.botAppIds)}`);
3226
+ logger8.debug(`resolveBotAppIds[${this.pluginName}]: using config.botAppIds=${JSON.stringify(this.config.botAppIds)}`);
2762
3227
  return this.config.botAppIds;
2763
3228
  }
2764
3229
  const appId = replyChannel ? replyChannel.appId : undefined;
2765
3230
  if (appId) {
2766
- logger7.debug(`resolveBotAppIds[${this.pluginName}]: using replyChannel.appId=${appId} (auto-detected from event header.app_id)`);
3231
+ logger8.debug(`resolveBotAppIds[${this.pluginName}]: using replyChannel.appId=${appId} (auto-detected from event header.app_id)`);
2767
3232
  return [appId];
2768
3233
  }
2769
- logger7.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。`);
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。`);
2770
3235
  return [];
2771
3236
  }
2772
3237
  resolveUserIds(replyChannel) {
@@ -2776,7 +3241,7 @@ class PerTaskGroupChatPlugin {
2776
3241
  if (id && id !== "sender" && id.startsWith("ou_")) {
2777
3242
  ids.add(id);
2778
3243
  } else if (id && id !== "sender") {
2779
- logger7.debug(`resolveUserIds[${this.pluginName}]: skip non-open_id in defaultInviteUserIds: "${id}"`);
3244
+ logger8.debug(`resolveUserIds[${this.pluginName}]: skip non-open_id in defaultInviteUserIds: "${id}"`);
2780
3245
  }
2781
3246
  }
2782
3247
  }
@@ -2799,7 +3264,7 @@ class PerTaskGroupChatPlugin {
2799
3264
  }
2800
3265
  }
2801
3266
  if (isPluginDebugEnabled()) {
2802
- logger7.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))}`);
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))}`);
2803
3268
  }
2804
3269
  return Array.from(ids);
2805
3270
  }
@@ -2952,4 +3417,4 @@ __legacyDecorateClassTS([
2952
3417
  function createPerTaskGroupChatPlugin(config = {}) {
2953
3418
  return new PerTaskGroupChatPlugin(config);
2954
3419
  }
2955
- export { createAutoTaskPlugin, pickGateConfidence, LayaDecisionPort, loadLayaAuthForPurpose, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, MemoryLarkCardStateStore, SqliteLarkCardStateStore, getDefaultLarkCardDbPath, createLarkCardStateStore, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
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 };