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

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 (81) 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 +12 -12
  7. package/dist/env/event-source/index.js +13 -13
  8. package/dist/env/index.js +30 -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 +3 -3
  13. package/dist/env/prompt/prompts-index.d.ts +24 -2
  14. package/dist/env/session/index.js +4 -4
  15. package/dist/env/session/storage/index.js +3 -3
  16. package/dist/env/task/delegate/delegate-tool.d.ts +8 -0
  17. package/dist/env/task/delegate/delegate-tool.js +4 -4
  18. package/dist/env/task/delegate/index.js +4 -4
  19. package/dist/env/task/index.js +7 -7
  20. package/dist/env/task/mcp/index.js +2 -2
  21. package/dist/env/task/plugins/index.d.ts +3 -0
  22. package/dist/env/task/plugins/index.js +32 -9
  23. package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +61 -0
  24. package/dist/env/task/plugins/lark-card-patch-notifier.js +6 -2
  25. package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +42 -0
  26. package/dist/env/task/prompt/index.js +1 -1
  27. package/dist/env/task/prompt/task-discipline.d.ts +9 -3
  28. package/dist/env/task/tools/cancel-tool.d.ts +28 -0
  29. package/dist/env/task/tools/index.d.ts +1 -0
  30. package/dist/env/task/tools/index.js +4 -2
  31. package/dist/env/task/tools/task-types.d.ts +22 -0
  32. package/dist/env/tool/built-in/compact-session-tool.d.ts +120 -0
  33. package/dist/env/tool/built-in/index.d.ts +2 -0
  34. package/dist/env/tool/built-in/index.js +6 -2
  35. package/dist/env/tool/index.js +4 -4
  36. package/dist/env/workflow/engine/index.js +6 -6
  37. package/dist/env/workflow/index.js +15 -13
  38. package/dist/env/workflow/plugins/index.js +2 -2
  39. package/dist/env/workflow/tools/ask-user-tool.d.ts +88 -0
  40. package/dist/env/workflow/tools/index.js +11 -9
  41. package/dist/env/workflow/tools/run-workflow.d.ts +2 -2
  42. package/dist/env/workflow/types/index.js +2 -2
  43. package/dist/env/workflow/types/workflow-hil.d.ts +25 -1
  44. package/dist/env/workflow/utils/index.js +5 -5
  45. package/dist/index.js +33 -33
  46. package/dist/shared/@ai-setting/{roy-agent-core-sm0sf6sb.js → roy-agent-core-0qyrk5k0.js} +2 -2
  47. package/dist/shared/@ai-setting/{roy-agent-core-kmbct9yg.js → roy-agent-core-0snm5hc5.js} +1 -1
  48. package/dist/shared/@ai-setting/{roy-agent-core-4jjwewvw.js → roy-agent-core-11shp00p.js} +31 -10
  49. package/dist/shared/@ai-setting/{roy-agent-core-yr1mryb8.js → roy-agent-core-1v4g0d4x.js} +1 -1
  50. package/dist/shared/@ai-setting/{roy-agent-core-rxb00gjm.js → roy-agent-core-264qexac.js} +1 -1
  51. package/dist/shared/@ai-setting/{roy-agent-core-34jz9mgh.js → roy-agent-core-29b6hc39.js} +1 -1
  52. package/dist/shared/@ai-setting/roy-agent-core-2akfd64z.js +374 -0
  53. package/dist/shared/@ai-setting/{roy-agent-core-yhwxpj12.js → roy-agent-core-3e6gg17d.js} +90 -12
  54. package/dist/shared/@ai-setting/{roy-agent-core-skyc30qx.js → roy-agent-core-3fh1f7s7.js} +2 -2
  55. package/dist/shared/@ai-setting/{roy-agent-core-2gzt2q8c.js → roy-agent-core-4gr3k7qg.js} +17 -4
  56. package/dist/shared/@ai-setting/{roy-agent-core-y12kyxwa.js → roy-agent-core-60jbrfye.js} +6 -6
  57. package/dist/shared/@ai-setting/{roy-agent-core-add1zqsh.js → roy-agent-core-6cy43nss.js} +22 -1
  58. package/dist/shared/@ai-setting/{roy-agent-core-vah6g5yx.js → roy-agent-core-6rh8k6ba.js} +5 -5
  59. package/dist/shared/@ai-setting/{roy-agent-core-kpg25s2q.js → roy-agent-core-8ez6mccb.js} +4 -2
  60. package/dist/shared/@ai-setting/{roy-agent-core-sjr1kmfz.js → roy-agent-core-acj8ag08.js} +1 -1
  61. package/dist/shared/@ai-setting/{roy-agent-core-mds8tqd0.js → roy-agent-core-cgtq6427.js} +1 -1
  62. package/dist/shared/@ai-setting/{roy-agent-core-pakhchy7.js → roy-agent-core-ddyk1rc0.js} +1 -1
  63. package/dist/shared/@ai-setting/{roy-agent-core-jgb3bb42.js → roy-agent-core-djpq6yb0.js} +16 -7
  64. package/dist/shared/@ai-setting/{roy-agent-core-attpry6g.js → roy-agent-core-dqxec05w.js} +4 -3
  65. package/dist/shared/@ai-setting/{roy-agent-core-vm8h5zyj.js → roy-agent-core-ed233k5z.js} +1 -1
  66. package/dist/shared/@ai-setting/{roy-agent-core-jrj30vq3.js → roy-agent-core-er070zjq.js} +1 -1
  67. package/dist/shared/@ai-setting/{roy-agent-core-40rv283c.js → roy-agent-core-fghc2m8z.js} +152 -2
  68. package/dist/shared/@ai-setting/{roy-agent-core-7sj9xs9c.js → roy-agent-core-hnyk1by7.js} +110 -2
  69. package/dist/shared/@ai-setting/{roy-agent-core-7qvw65jt.js → roy-agent-core-jg65qnwa.js} +11 -8
  70. package/dist/shared/@ai-setting/{roy-agent-core-e25xkv53.js → roy-agent-core-mkc3zshb.js} +3 -1
  71. package/dist/shared/@ai-setting/{roy-agent-core-hfyfavp3.js → roy-agent-core-mww0mnd9.js} +231 -27
  72. package/dist/shared/@ai-setting/{roy-agent-core-wr7mv7k3.js → roy-agent-core-n9q1encx.js} +1 -1
  73. package/dist/shared/@ai-setting/{roy-agent-core-46rv4d7d.js → roy-agent-core-nmx9934r.js} +92 -2
  74. package/dist/shared/@ai-setting/{roy-agent-core-1w9y7mxj.js → roy-agent-core-p3zv1mx5.js} +1 -1
  75. package/dist/shared/@ai-setting/roy-agent-core-snvh4s2p.js +662 -0
  76. package/dist/shared/@ai-setting/{roy-agent-core-dt104qa1.js → roy-agent-core-v85n42ve.js} +1 -1
  77. package/dist/shared/@ai-setting/{roy-agent-core-gr48c89p.js → roy-agent-core-v87njmq7.js} +3 -3
  78. package/dist/shared/@ai-setting/{roy-agent-core-5gw0qa8m.js → roy-agent-core-y7x2qzwc.js} +553 -92
  79. package/package.json +1 -1
  80. package/dist/shared/@ai-setting/roy-agent-core-1p6syv85.js +0 -1083
  81. 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-3e6gg17d.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") {
1519
+ logger5.debug(`[laya-completion] terminalStatus=${ctx.terminalStatus} — skip (only completed)`);
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
  }
@@ -1715,11 +2133,11 @@ class LarkCliTaskNotifyHook {
1715
2133
  try {
1716
2134
  record = this.cardStateStore?.get(taskId);
1717
2135
  } catch (err) {
1718
- logger6.warn(`onAfterDelete: 读取卡片记录失败 task #${taskId}: ${err.message}`);
2136
+ logger7.warn(`onAfterDelete: 读取卡片记录失败 task #${taskId}: ${err.message}`);
1719
2137
  }
1720
2138
  const messageId = record?.messageId || this.cardMessageIds.get(taskId);
1721
2139
  if (!messageId) {
1722
- logger6.debug(`onAfterDelete: task #${taskId} 无卡片记录,skip(不发新卡)`);
2140
+ logger7.debug(`onAfterDelete: task #${taskId} 无卡片记录,skip(不发新卡)`);
1723
2141
  return;
1724
2142
  }
1725
2143
  this.restoreCardState(taskId);
@@ -1734,17 +2152,18 @@ class LarkCliTaskNotifyHook {
1734
2152
  finalStep: "任务已取消(删除)"
1735
2153
  });
1736
2154
  } catch (err) {
1737
- logger6.warn(`onAfterDelete: patch 卡片失败 task #${taskId} message=${messageId}: ${err.message}`);
2155
+ logger7.warn(`onAfterDelete: patch 卡片失败 task #${taskId} message=${messageId}: ${err.message}`);
1738
2156
  }
1739
2157
  try {
1740
2158
  await this.cardNotifier.unpinMessage(messageId, profile);
1741
2159
  } catch (err) {
1742
- logger6.warn(`onAfterDelete: unpin 失败 task #${taskId} message=${messageId}: ${err.message}`);
2160
+ logger7.warn(`onAfterDelete: unpin 失败 task #${taskId} message=${messageId}: ${err.message}`);
1743
2161
  }
2162
+ this.cancelTrailingPatch(taskId);
1744
2163
  this.persistClosed(taskId);
1745
2164
  this.persistPinned(taskId, false);
1746
2165
  this.cardMessageIds.delete(taskId);
1747
- logger6.debug(`onAfterDelete: task #${taskId} 卡片已收尾 status=deleted message=${messageId}`);
2166
+ logger7.debug(`onAfterDelete: task #${taskId} 卡片已收尾 status=deleted message=${messageId}`);
1748
2167
  }
1749
2168
  async onAfterOperationCreate(ctx) {
1750
2169
  const { operation } = ctx.data;
@@ -1752,18 +2171,16 @@ class LarkCliTaskNotifyHook {
1752
2171
  return;
1753
2172
  if (!this.shouldNotify("operation_created" /* OPERATION_CREATED */))
1754
2173
  return;
1755
- if (this.shouldThrottleOperation(operation))
1756
- return;
1757
2174
  if (!this.isCardModeEnabled())
1758
2175
  return;
1759
2176
  const persisted = this.restoreCardState(operation.taskId);
1760
2177
  const messageId = this.cardMessageIds.get(operation.taskId);
1761
2178
  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`);
2179
+ logger7.debug(`onAfterOperationCreate: no usable card for task #${operation.taskId}, skip`);
1763
2180
  return;
1764
2181
  }
1765
2182
  if (persisted && persisted.status !== "open") {
1766
- logger6.debug(`onAfterOperationCreate: task #${operation.taskId} card status=${persisted.status}, skip`);
2183
+ logger7.debug(`onAfterOperationCreate: task #${operation.taskId} card status=${persisted.status}, skip`);
1767
2184
  return;
1768
2185
  }
1769
2186
  const { milestones, processSteps } = this.deriveTimelineFromOperation(operation);
@@ -1771,6 +2188,17 @@ class LarkCliTaskNotifyHook {
1771
2188
  return;
1772
2189
  const env = getEnvContextOrEmpty();
1773
2190
  const profile = env.replyChannel?.profile;
2191
+ this.cardNotifier.mergeTimelineState?.(messageId, milestones, processSteps);
2192
+ this.refreshTimeline(operation.taskId);
2193
+ const throttled = this.shouldThrottleOperation(operation);
2194
+ if (throttled) {
2195
+ this.scheduleTrailingPatch(operation.taskId, messageId, profile);
2196
+ logger7.debug(`[throttle] OPERATION_CREATED for task #${operation.taskId} ` + `merged into state + ledger; card patch deferred to window end`);
2197
+ return;
2198
+ }
2199
+ await this.patchOperationTimeline(operation.taskId, messageId, profile, milestones, processSteps);
2200
+ }
2201
+ async patchOperationTimeline(taskId, messageId, profile, milestones, processSteps) {
1774
2202
  try {
1775
2203
  await this.cardNotifier.updateTask({
1776
2204
  messageId,
@@ -1780,9 +2208,41 @@ class LarkCliTaskNotifyHook {
1780
2208
  processSteps
1781
2209
  });
1782
2210
  } catch (err) {
1783
- logger6.error(`sendCardOperation failed for task #${operation.taskId}: ${err.message}`);
2211
+ logger7.error(`sendCardOperation failed for task #${taskId}: ${err.message}`);
1784
2212
  }
1785
2213
  }
2214
+ scheduleTrailingPatch(taskId, messageId, profile) {
2215
+ const existing = this.trailingPatchTimers.get(taskId);
2216
+ if (existing) {
2217
+ return;
2218
+ }
2219
+ const throttleMs = this.config.operationThrottleMs ?? 5000;
2220
+ const delay = throttleMs > 0 ? throttleMs : 0;
2221
+ const timer = setTimeout(() => {
2222
+ this.trailingPatchTimers.delete(taskId);
2223
+ const liveMessageId = this.cardMessageIds.get(taskId);
2224
+ if (liveMessageId !== messageId) {
2225
+ logger7.debug(`[throttle] trailing patch skipped for task #${taskId}: ` + `card no longer tracked (finished/closed)`);
2226
+ return;
2227
+ }
2228
+ const rec = this.cardStateStore?.get(taskId);
2229
+ if (rec && rec.status !== "open") {
2230
+ logger7.debug(`[throttle] trailing patch skipped for task #${taskId}: ` + `card status=${rec.status}`);
2231
+ return;
2232
+ }
2233
+ this.patchOperationTimeline(taskId, messageId, profile, [], []);
2234
+ }, delay);
2235
+ if (typeof timer.unref === "function")
2236
+ timer.unref();
2237
+ this.trailingPatchTimers.set(taskId, timer);
2238
+ }
2239
+ cancelTrailingPatch(taskId) {
2240
+ const timer = this.trailingPatchTimers.get(taskId);
2241
+ if (!timer)
2242
+ return;
2243
+ clearTimeout(timer);
2244
+ this.trailingPatchTimers.delete(taskId);
2245
+ }
1786
2246
  restoreCardState(taskId) {
1787
2247
  if (!this.cardStateStore)
1788
2248
  return;
@@ -1795,12 +2255,12 @@ class LarkCliTaskNotifyHook {
1795
2255
  if (rec.status !== "closed" && rec.messageId) {
1796
2256
  this.cardMessageIds.set(taskId, rec.messageId);
1797
2257
  this.hydrateNotifierState(rec);
1798
- logger6.debug(`restoreCardState: task #${taskId} ← 持久层 message ${rec.messageId} ` + `(status=${rec.status}, pinned=${rec.pinned}, timeline=${rec.timeline ? "yes" : "no"})`);
2258
+ logger7.debug(`restoreCardState: task #${taskId} ← 持久层 message ${rec.messageId} ` + `(status=${rec.status}, pinned=${rec.pinned}, timeline=${rec.timeline ? "yes" : "no"})`);
1799
2259
  }
1800
2260
  }
1801
2261
  return rec;
1802
2262
  } catch (err) {
1803
- logger6.warn(`restoreCardState failed for task #${taskId}: ${err.message}`);
2263
+ logger7.warn(`restoreCardState failed for task #${taskId}: ${err.message}`);
1804
2264
  return;
1805
2265
  }
1806
2266
  }
@@ -1811,7 +2271,7 @@ class LarkCliTaskNotifyHook {
1811
2271
  const snapshot = JSON.parse(rec.timeline);
1812
2272
  this.cardNotifier.hydrateState?.(rec.messageId, snapshot);
1813
2273
  } catch (err) {
1814
- logger6.warn(`hydrateNotifierState failed for task #${rec.taskId}: ${err.message}`);
2274
+ logger7.warn(`hydrateNotifierState failed for task #${rec.taskId}: ${err.message}`);
1815
2275
  }
1816
2276
  }
1817
2277
  snapshotTimeline(state) {
@@ -1823,7 +2283,7 @@ class LarkCliTaskNotifyHook {
1823
2283
  return state;
1824
2284
  return { ...state, timeline: JSON.stringify(snap) };
1825
2285
  } catch (err) {
1826
- logger6.warn(`snapshotTimeline failed for task #${state.taskId}: ${err.message}`);
2286
+ logger7.warn(`snapshotTimeline failed for task #${state.taskId}: ${err.message}`);
1827
2287
  return state;
1828
2288
  }
1829
2289
  }
@@ -1834,7 +2294,7 @@ class LarkCliTaskNotifyHook {
1834
2294
  const payload = includeTimeline ? this.snapshotTimeline(state) : state;
1835
2295
  this.cardStateStore.upsert(payload);
1836
2296
  } catch (err) {
1837
- logger6.warn(`persistCardState failed for task #${state.taskId}: ${err.message}`);
2297
+ logger7.warn(`persistCardState failed for task #${state.taskId}: ${err.message}`);
1838
2298
  }
1839
2299
  }
1840
2300
  persistPinned(taskId, pinned) {
@@ -1843,7 +2303,7 @@ class LarkCliTaskNotifyHook {
1843
2303
  try {
1844
2304
  this.cardStateStore.markPinned(taskId, pinned);
1845
2305
  } catch (err) {
1846
- logger6.warn(`persistPinned failed for task #${taskId}: ${err.message}`);
2306
+ logger7.warn(`persistPinned failed for task #${taskId}: ${err.message}`);
1847
2307
  }
1848
2308
  }
1849
2309
  persistPaused(taskId) {
@@ -1852,7 +2312,7 @@ class LarkCliTaskNotifyHook {
1852
2312
  try {
1853
2313
  this.cardStateStore.markPaused(taskId);
1854
2314
  } catch (err) {
1855
- logger6.warn(`persistPaused failed for task #${taskId}: ${err.message}`);
2315
+ logger7.warn(`persistPaused failed for task #${taskId}: ${err.message}`);
1856
2316
  }
1857
2317
  }
1858
2318
  dropStaleMessageId(taskId) {
@@ -1869,7 +2329,7 @@ class LarkCliTaskNotifyHook {
1869
2329
  updatedAt: Date.now()
1870
2330
  });
1871
2331
  } catch (err) {
1872
- logger6.warn(`dropStaleMessageId failed for task #${taskId}: ${err.message}`);
2332
+ logger7.warn(`dropStaleMessageId failed for task #${taskId}: ${err.message}`);
1873
2333
  }
1874
2334
  }
1875
2335
  persistReopened(taskId) {
@@ -1878,7 +2338,7 @@ class LarkCliTaskNotifyHook {
1878
2338
  try {
1879
2339
  this.cardStateStore.markReopened(taskId);
1880
2340
  } catch (err) {
1881
- logger6.warn(`persistReopened failed for task #${taskId}: ${err.message}`);
2341
+ logger7.warn(`persistReopened failed for task #${taskId}: ${err.message}`);
1882
2342
  }
1883
2343
  }
1884
2344
  persistClosed(taskId) {
@@ -1887,7 +2347,7 @@ class LarkCliTaskNotifyHook {
1887
2347
  try {
1888
2348
  this.cardStateStore.markClosed(taskId);
1889
2349
  } catch (err) {
1890
- logger6.warn(`persistClosed failed for task #${taskId}: ${err.message}`);
2350
+ logger7.warn(`persistClosed failed for task #${taskId}: ${err.message}`);
1891
2351
  }
1892
2352
  }
1893
2353
  refreshTimeline(taskId) {
@@ -1899,7 +2359,7 @@ class LarkCliTaskNotifyHook {
1899
2359
  return;
1900
2360
  this.cardStateStore.upsert(this.snapshotTimeline(rec));
1901
2361
  } catch (err) {
1902
- logger6.warn(`refreshTimeline failed for task #${taskId}: ${err.message}`);
2362
+ logger7.warn(`refreshTimeline failed for task #${taskId}: ${err.message}`);
1903
2363
  }
1904
2364
  }
1905
2365
  async repinAfterResume(task, persisted) {
@@ -1915,9 +2375,9 @@ class LarkCliTaskNotifyHook {
1915
2375
  try {
1916
2376
  await this.cardNotifier.pinMessage(persisted.messageId, profile);
1917
2377
  this.persistPinned(task.id, true);
1918
- logger6.debug(`repinAfterResume: task #${task.id} 卡片 ${persisted.messageId} 已重新置顶`);
2378
+ logger7.debug(`repinAfterResume: task #${task.id} 卡片 ${persisted.messageId} 已重新置顶`);
1919
2379
  } catch (err) {
1920
- logger6.warn(`repinAfterResume failed (non-fatal) for task #${task.id}: ${err.message}`);
2380
+ logger7.warn(`repinAfterResume failed (non-fatal) for task #${task.id}: ${err.message}`);
1921
2381
  }
1922
2382
  }
1923
2383
  buildCardState(task, messageId, chatId, profile, pinned) {
@@ -1925,7 +2385,7 @@ class LarkCliTaskNotifyHook {
1925
2385
  try {
1926
2386
  existing = this.cardStateStore?.get(task.id);
1927
2387
  } catch (err) {
1928
- logger6.warn(`buildCardState: read existing failed for task #${task.id}: ${err.message}`);
2388
+ logger7.warn(`buildCardState: read existing failed for task #${task.id}: ${err.message}`);
1929
2389
  }
1930
2390
  const now = Date.now();
1931
2391
  return {
@@ -1959,7 +2419,7 @@ class LarkCliTaskNotifyHook {
1959
2419
  }
1960
2420
  isCardModeEnabled() {
1961
2421
  if (this.config.cardMode === false || !this.cardNotifier) {
1962
- logger6.debug("cardMode=false (legacy opt-out), skip task notification (no text fallback)");
2422
+ logger7.debug("cardMode=false (legacy opt-out), skip task notification (no text fallback)");
1963
2423
  return false;
1964
2424
  }
1965
2425
  return true;
@@ -2029,7 +2489,7 @@ class LarkCliTaskNotifyHook {
2029
2489
  }
2030
2490
  const claim = this.cardMessageIds.get(task.id);
2031
2491
  if (claim) {
2032
- logger6.debug(`sendCardStart: task #${task.id} 已有首卡(${claim})→ 跳过重复发卡`);
2492
+ logger7.debug(`sendCardStart: task #${task.id} 已有首卡(${claim})→ 跳过重复发卡`);
2033
2493
  return;
2034
2494
  }
2035
2495
  this.cardMessageIds.set(task.id, LarkCliTaskNotifyHook.CARD_START_INFLIGHT_SENTINEL);
@@ -2044,7 +2504,7 @@ class LarkCliTaskNotifyHook {
2044
2504
  const profile = target.profile;
2045
2505
  if (!chatId) {
2046
2506
  this.cardMessageIds.delete(task.id);
2047
- logger6.debug(`sendCardStart: no chatId for task #${task.id}, skip`);
2507
+ logger7.debug(`sendCardStart: no chatId for task #${task.id}, skip`);
2048
2508
  return;
2049
2509
  }
2050
2510
  try {
@@ -2062,10 +2522,10 @@ class LarkCliTaskNotifyHook {
2062
2522
  });
2063
2523
  this.cardMessageIds.set(task.id, messageId);
2064
2524
  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}`);
2525
+ logger7.debug(`sendCardStart: task #${task.id} → message ${messageId} (pinned=${pinned}) ` + `chat=${chatId} source=${target.source} reason=${target.reason}`);
2066
2526
  } catch (err) {
2067
2527
  this.cardMessageIds.set(task.id, LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL);
2068
- logger6.error(`sendCardStart failed for task #${task.id}: ${err.message}`);
2528
+ logger7.error(`sendCardStart failed for task #${task.id}: ${err.message}`);
2069
2529
  }
2070
2530
  }
2071
2531
  async sendCardUpdate(task, changes) {
@@ -2075,21 +2535,21 @@ class LarkCliTaskNotifyHook {
2075
2535
  if (persisted?.status === "closed") {
2076
2536
  const stillTerminal = isTerminalTaskStatus(String(task.status));
2077
2537
  if (stillTerminal) {
2078
- logger6.debug(`sendCardUpdate: task #${task.id} 卡片已终结(closed)且任务仍终态 → skip patch`);
2538
+ logger7.debug(`sendCardUpdate: task #${task.id} 卡片已终结(closed)且任务仍终态 → skip patch`);
2079
2539
  return;
2080
2540
  }
2081
- logger6.info(`sendCardUpdate: task #${task.id} 已 reopen(closed 记录 + 当前 status=${task.status})` + `→ 重新建卡`);
2541
+ logger7.info(`sendCardUpdate: task #${task.id} 已 reopen(closed 记录 + 当前 status=${task.status})` + `→ 重新建卡`);
2082
2542
  this.cardMessageIds.delete(task.id);
2083
2543
  this.dropStaleMessageId(task.id);
2084
2544
  }
2085
2545
  if (persisted?.status === "paused" && String(task.status) !== "paused") {
2086
2546
  this.persistReopened(task.id);
2087
- logger6.debug(`sendCardUpdate: task #${task.id} resume(paused → open),复用卡片 ${persisted.messageId}`);
2547
+ logger7.debug(`sendCardUpdate: task #${task.id} resume(paused → open),复用卡片 ${persisted.messageId}`);
2088
2548
  await this.repinAfterResume(task, persisted);
2089
2549
  }
2090
2550
  let messageId = this.cardMessageIds.get(task.id);
2091
2551
  if (!messageId && isTerminalTaskStatus(String(task.status))) {
2092
- logger6.debug(`sendCardUpdate: task #${task.id} 状态=${task.status}(终结/归档)且无卡片 → skip`);
2552
+ logger7.debug(`sendCardUpdate: task #${task.id} 状态=${task.status}(终结/归档)且无卡片 → skip`);
2093
2553
  return;
2094
2554
  }
2095
2555
  if (!messageId) {
@@ -2101,15 +2561,15 @@ class LarkCliTaskNotifyHook {
2101
2561
  if (!chatId) {
2102
2562
  if (!messageId)
2103
2563
  this.cardMessageIds.delete(task.id);
2104
- logger6.debug(`sendCardUpdate: no chatId for task #${task.id}, skip`);
2564
+ logger7.debug(`sendCardUpdate: no chatId for task #${task.id}, skip`);
2105
2565
  return;
2106
2566
  }
2107
2567
  if (messageId === LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL) {
2108
- logger6.debug(`sendCardUpdate: startTask failed previously for task #${task.id}, skip`);
2568
+ logger7.debug(`sendCardUpdate: startTask failed previously for task #${task.id}, skip`);
2109
2569
  return;
2110
2570
  }
2111
2571
  if (messageId === LarkCliTaskNotifyHook.CARD_START_INFLIGHT_SENTINEL) {
2112
- logger6.debug(`sendCardUpdate: task #${task.id} 首卡正在发送中(in-flight)→ 跳过本次 update`);
2572
+ logger7.debug(`sendCardUpdate: task #${task.id} 首卡正在发送中(in-flight)→ 跳过本次 update`);
2113
2573
  return;
2114
2574
  }
2115
2575
  if (!messageId) {
@@ -2124,7 +2584,7 @@ class LarkCliTaskNotifyHook {
2124
2584
  this.persistCardState(this.buildCardState(task, messageId, chatId, profile, res.pinned), true);
2125
2585
  } catch (err) {
2126
2586
  this.cardMessageIds.set(task.id, LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL);
2127
- logger6.error(`sendCardUpdate fallback start failed: ${err.message}`);
2587
+ logger7.error(`sendCardUpdate fallback start failed: ${err.message}`);
2128
2588
  return;
2129
2589
  }
2130
2590
  }
@@ -2151,7 +2611,7 @@ class LarkCliTaskNotifyHook {
2151
2611
  this.persistPinned(task.id, current?.pinned ?? persisted?.pinned ?? true);
2152
2612
  this.refreshTimeline(task.id);
2153
2613
  } catch (err) {
2154
- logger6.error(`sendCardUpdate failed: ${err.message}`);
2614
+ logger7.error(`sendCardUpdate failed: ${err.message}`);
2155
2615
  }
2156
2616
  }
2157
2617
  async sendCardFinish(task) {
@@ -2162,13 +2622,14 @@ class LarkCliTaskNotifyHook {
2162
2622
  const ctx = getEnvContextOrEmpty();
2163
2623
  const profile = this.resolveProfile(task, ctx);
2164
2624
  if (!messageId || messageId === LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL) {
2165
- logger6.debug(`sendCardFinish: no card messageId for task #${task.id}, skip ` + `(记录缺失 → 不发新卡,保持幂等)`);
2625
+ logger7.debug(`sendCardFinish: no card messageId for task #${task.id}, skip ` + `(记录缺失 → 不发新卡,保持幂等)`);
2166
2626
  return;
2167
2627
  }
2168
2628
  const rawStatus = String(task.status);
2169
2629
  const status = mapTaskStatusToCardStatus(rawStatus);
2170
2630
  const isPaused = rawStatus === "paused" || rawStatus === "archived";
2171
2631
  try {
2632
+ this.cancelTrailingPatch(task.id);
2172
2633
  const finish = isPaused ? this.cardNotifier.pauseTask.bind(this.cardNotifier) : this.cardNotifier.finishTask.bind(this.cardNotifier);
2173
2634
  await finish({
2174
2635
  messageId,
@@ -2188,9 +2649,9 @@ class LarkCliTaskNotifyHook {
2188
2649
  this.persistPinned(task.id, false);
2189
2650
  this.cardMessageIds.delete(task.id);
2190
2651
  }
2191
- logger6.debug(`sendCardFinish: task #${task.id} → status=${status} ` + `(raw=${rawStatus}) message=${messageId}`);
2652
+ logger7.debug(`sendCardFinish: task #${task.id} → status=${status} ` + `(raw=${rawStatus}) message=${messageId}`);
2192
2653
  } catch (err) {
2193
- logger6.error(`sendCardFinish failed: ${err.message}`);
2654
+ logger7.error(`sendCardFinish failed: ${err.message}`);
2194
2655
  }
2195
2656
  }
2196
2657
  resolveChatId(task, env) {
@@ -2267,7 +2728,7 @@ class LarkCliTaskNotifyHook {
2267
2728
  const now = Date.now();
2268
2729
  const lastTime = this.lastNotifyTimestamps.get(key);
2269
2730
  if (lastTime && now - lastTime < throttleMs) {
2270
- logger6.debug(`[throttle] skip OPERATION_CREATED patch for task #${operation.taskId} (${now - lastTime}ms ago)`);
2731
+ logger7.debug(`[throttle] skip OPERATION_CREATED patch for task #${operation.taskId} (${now - lastTime}ms ago)`);
2271
2732
  return true;
2272
2733
  }
2273
2734
  this.lastNotifyTimestamps.set(key, now);
@@ -2310,7 +2771,7 @@ init_decorator();
2310
2771
  init_logger();
2311
2772
  init_context();
2312
2773
  import { spawn } from "child_process";
2313
- var logger7 = createLogger("lark-cli:per-task-group");
2774
+ var logger8 = createLogger("lark-cli:per-task-group");
2314
2775
  function isPluginDebugEnabled() {
2315
2776
  return process.env.ROY_TASK_NOTIFY_DEBUG === "1" || process.env.ROY_TRACE_DEBUG === "1";
2316
2777
  }
@@ -2365,7 +2826,7 @@ class PerTaskGroupChatPlugin {
2365
2826
  const renderedName = this.renderTemplate(this.config.nameTemplate, task);
2366
2827
  const existingChatId = this.getTaskChatId(task);
2367
2828
  if (existingChatId) {
2368
- logger7.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 已有专属群 ${existingChatId} → 复用(不重复建群)`);
2829
+ logger8.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 已有专属群 ${existingChatId} → 复用(不重复建群)`);
2369
2830
  return {
2370
2831
  chatId: existingChatId,
2371
2832
  profile: this.parseProfileFromContext(task.context),
@@ -2374,7 +2835,7 @@ class PerTaskGroupChatPlugin {
2374
2835
  }
2375
2836
  const inflight = this.groupChatInflight.get(task.id);
2376
2837
  if (inflight) {
2377
- logger7.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 建群已在进行中 → 复用同一 promise`);
2838
+ logger8.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 建群已在进行中 → 复用同一 promise`);
2378
2839
  return inflight;
2379
2840
  }
2380
2841
  const pending = this.doEnsureGroupChat(task, renderedName, replyChannelOverride);
@@ -2388,7 +2849,7 @@ class PerTaskGroupChatPlugin {
2388
2849
  async doEnsureGroupChat(task, name, replyChannelOverride) {
2389
2850
  const replyChannel = replyChannelOverride ?? getEnvContextOrEmpty().replyChannel;
2390
2851
  if (!replyChannel?.chatId && this.config.requireReplyChannel) {
2391
- logger7.warn(`ensureGroupChat[${this.pluginName}]: no replyChannel.chatId for task #${task.id} ` + `(requireReplyChannel=true) → 无法建群`);
2852
+ logger8.warn(`ensureGroupChat[${this.pluginName}]: no replyChannel.chatId for task #${task.id} ` + `(requireReplyChannel=true) → 无法建群`);
2392
2853
  return;
2393
2854
  }
2394
2855
  const description = this.renderTemplate(this.config.descriptionTemplate, task);
@@ -2406,30 +2867,30 @@ class PerTaskGroupChatPlugin {
2406
2867
  await this.writeChatBinding(task, chatId, profile, "creating");
2407
2868
  await this.attachChatIdToTask(task, chatId, profile);
2408
2869
  await this.writeChatBinding(task, chatId, profile, "ready");
2409
- logger7.info(`ensureGroupChat[${this.pluginName}]: created group ${chatId} for task #${task.id}`);
2870
+ logger8.info(`ensureGroupChat[${this.pluginName}]: created group ${chatId} for task #${task.id}`);
2410
2871
  return { chatId, profile, name };
2411
2872
  } catch (error) {
2412
- logger7.error(`ensureGroupChat[${this.pluginName}]: failed for task #${task.id}: ${error}`);
2873
+ logger8.error(`ensureGroupChat[${this.pluginName}]: failed for task #${task.id}: ${error}`);
2413
2874
  return;
2414
2875
  }
2415
2876
  }
2416
2877
  setTaskUpdater(updater) {
2417
2878
  this.taskUpdater = updater;
2418
- logger7.debug(`TaskUpdater injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2879
+ logger8.debug(`TaskUpdater injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2419
2880
  }
2420
2881
  hasTaskUpdater() {
2421
2882
  return this.taskUpdater !== undefined;
2422
2883
  }
2423
2884
  setTaskGetter(getter) {
2424
2885
  this.taskGetter = getter;
2425
- logger7.debug(`TaskGetter injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2886
+ logger8.debug(`TaskGetter injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2426
2887
  }
2427
2888
  hasTaskGetter() {
2428
2889
  return this.taskGetter !== undefined;
2429
2890
  }
2430
2891
  setSessionComponent(sessionComponent) {
2431
2892
  this.sessionComponent = sessionComponent;
2432
- logger7.debug(`SessionComponent injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2893
+ logger8.debug(`SessionComponent injected for plugin ${this.name} (sourceId=${this.sourceId})`);
2433
2894
  }
2434
2895
  hasSessionComponent() {
2435
2896
  return this.sessionComponent !== undefined;
@@ -2442,7 +2903,7 @@ class PerTaskGroupChatPlugin {
2442
2903
  if (process.env.ROY_HOOK_DEBUG === "1") {
2443
2904
  console.error(`[DEBUG per-task-group-chat.execute] → SKIP (config.enabled=false)`);
2444
2905
  }
2445
- logger7.debug(`execute: plugin disabled (pluginName=${this.pluginName})`);
2906
+ logger8.debug(`execute: plugin disabled (pluginName=${this.pluginName})`);
2446
2907
  return;
2447
2908
  }
2448
2909
  if (this.pluginName && this.sourceId) {
@@ -2455,13 +2916,13 @@ class PerTaskGroupChatPlugin {
2455
2916
  if (process.env.ROY_HOOK_DEBUG === "1") {
2456
2917
  console.error(`[DEBUG per-task-group-chat.execute] → SKIP (plugin NOT enabled for ${key})`);
2457
2918
  }
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`);
2919
+ 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
2920
  return;
2460
2921
  }
2461
2922
  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()})`);
2923
+ logger8.debug(`execute[${this.pluginName}]: ${enabledState} (key=${key}, ` + `hookPoint=${ctx.hookPoint}, taskUpdaterInjected=${this.hasTaskUpdater()})`);
2463
2924
  } else {
2464
- logger7.debug(`execute[${this.pluginName}]: missing pluginName/sourceId metadata ` + `(pluginName=${this.pluginName}, sourceId=${this.sourceId}), ` + `skipping pluginEnabled check. hookPoint=${ctx.hookPoint}`);
2925
+ logger8.debug(`execute[${this.pluginName}]: missing pluginName/sourceId metadata ` + `(pluginName=${this.pluginName}, sourceId=${this.sourceId}), ` + `skipping pluginEnabled check. hookPoint=${ctx.hookPoint}`);
2465
2926
  }
2466
2927
  if (process.env.ROY_HOOK_DEBUG === "1") {
2467
2928
  console.error(`[DEBUG per-task-group-chat.execute] → PASSED pluginEnabled check, will dispatch hookPoint=${ctx.hookPoint}`);
@@ -2480,13 +2941,13 @@ class PerTaskGroupChatPlugin {
2480
2941
  await this.onAfterComplete(ctx);
2481
2942
  break;
2482
2943
  default:
2483
- logger7.debug(`execute: unhandled hookPoint=${ctx.hookPoint}, skip`);
2944
+ logger8.debug(`execute: unhandled hookPoint=${ctx.hookPoint}, skip`);
2484
2945
  }
2485
2946
  }
2486
2947
  async onAfterCreate(ctx) {
2487
2948
  const data = ctx.data;
2488
2949
  if (!data?.task) {
2489
- logger7.debug("onAfterCreate: task is missing, skip");
2950
+ logger8.debug("onAfterCreate: task is missing, skip");
2490
2951
  return;
2491
2952
  }
2492
2953
  const { task } = data;
@@ -2497,38 +2958,38 @@ class PerTaskGroupChatPlugin {
2497
2958
  console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.messageId=${env.messageId}`);
2498
2959
  console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.sourceId=${env.sourceId}`);
2499
2960
  }
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}`);
2961
+ 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
2962
  if (!this.config.autoCreateGroup) {
2502
- logger7.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} autoCreateGroup=false ` + `→ skip group chat creation(按需建群:用户在卡片顶部按钮触发)`);
2963
+ logger8.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} autoCreateGroup=false ` + `→ skip group chat creation(按需建群:用户在卡片顶部按钮触发)`);
2503
2964
  return;
2504
2965
  }
2505
2966
  const replyChannel = this.getReplyChannelFromCtx(ctx);
2506
2967
  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/无)`);
2968
+ 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
2969
  return;
2509
2970
  }
2510
2971
  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}")`);
2972
+ logger8.debug(`onAfterCreate[${this.pluginName}]: no replyChannel but requireReplyChannel=false, ` + `proceeding with profile fallback (fallbackProfile="${this.config.fallbackProfile}")`);
2512
2973
  }
2513
2974
  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`);
2975
+ logger8.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task, ` + `skip group chat creation. 关闭方式:config.disableSubTaskSkip=true`);
2515
2976
  return;
2516
2977
  }
2517
2978
  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`);
2979
+ logger8.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task but ` + `disableSubTaskSkip=true, proceeding to create group chat`);
2519
2980
  }
2520
2981
  try {
2521
2982
  const created = await this.ensureGroupChat(task, replyChannel);
2522
2983
  if (!created?.chatId) {
2523
- logger7.error(`onAfterCreate[${this.pluginName}]: ensureGroupChat 未返回 chatId for task #${task.id}`);
2984
+ logger8.error(`onAfterCreate[${this.pluginName}]: ensureGroupChat 未返回 chatId for task #${task.id}`);
2524
2985
  if (!this.config.fallbackToMain) {
2525
2986
  throw new Error(`per-task group chat unavailable for task #${task.id}`);
2526
2987
  }
2527
2988
  return;
2528
2989
  }
2529
- logger7.info(`onAfterCreate[${this.pluginName}]: task #${task.id} → group ${created.chatId} ` + `(initial card owned by LarkCliTaskNotifyHook)`);
2990
+ logger8.info(`onAfterCreate[${this.pluginName}]: task #${task.id} → group ${created.chatId} ` + `(initial card owned by LarkCliTaskNotifyHook)`);
2530
2991
  } catch (error) {
2531
- logger7.error(`onAfterCreate[${this.pluginName}]: group chat failed for task #${task.id}: ${error}`);
2992
+ logger8.error(`onAfterCreate[${this.pluginName}]: group chat failed for task #${task.id}: ${error}`);
2532
2993
  if (!this.config.fallbackToMain)
2533
2994
  throw error;
2534
2995
  }
@@ -2536,7 +2997,7 @@ class PerTaskGroupChatPlugin {
2536
2997
  async writeChatBinding(task, chatId, profile, status, errorMessage) {
2537
2998
  if (!this.sessionComponent) {
2538
2999
  if (isPluginDebugEnabled()) {
2539
- logger7.debug(`writeChatBinding[${this.pluginName}]: no sessionComponent injected, ` + `skip chat-task binding for chat ${chatId} (status=${status})`);
3000
+ logger8.debug(`writeChatBinding[${this.pluginName}]: no sessionComponent injected, ` + `skip chat-task binding for chat ${chatId} (status=${status})`);
2540
3001
  }
2541
3002
  return;
2542
3003
  }
@@ -2549,37 +3010,37 @@ class PerTaskGroupChatPlugin {
2549
3010
  errorMessage: errorMessage ?? null
2550
3011
  });
2551
3012
  } 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。`);
3013
+ logger8.warn(`writeChatBinding[${this.pluginName}]: failed to write chat-task binding for ` + `chat ${chatId} / task #${task.id} (status=${status}): ${bindErr}. ` + `群消息 handler 将无法自动注入 parentTaskId。`);
2553
3014
  }
2554
3015
  }
2555
3016
  async onAfterUpdate(ctx) {
2556
3017
  const data = ctx.data;
2557
3018
  if (!data?.task) {
2558
- logger7.debug("onAfterUpdate: task is missing, skip");
3019
+ logger8.debug("onAfterUpdate: task is missing, skip");
2559
3020
  return;
2560
3021
  }
2561
- logger7.debug(`onAfterUpdate[${this.pluginName}]: task #${data.task.id} 卡片由 ` + `LarkCliTaskNotifyHook 负责(本插件不再 patch),skip`);
3022
+ logger8.debug(`onAfterUpdate[${this.pluginName}]: task #${data.task.id} 卡片由 ` + `LarkCliTaskNotifyHook 负责(本插件不再 patch),skip`);
2562
3023
  }
2563
3024
  async onAfterOperationCreate(ctx) {
2564
3025
  const data = ctx.data;
2565
3026
  if (!data?.operation) {
2566
- logger7.debug("onAfterOperationCreate: operation is missing, skip");
3027
+ logger8.debug("onAfterOperationCreate: operation is missing, skip");
2567
3028
  return;
2568
3029
  }
2569
- logger7.debug(`onAfterOperationCreate[${this.pluginName}]: operation #${data.operation.id} ` + `卡片由 LarkCliTaskNotifyHook 负责,skip`);
3030
+ logger8.debug(`onAfterOperationCreate[${this.pluginName}]: operation #${data.operation.id} ` + `卡片由 LarkCliTaskNotifyHook 负责,skip`);
2570
3031
  }
2571
3032
  async onAfterComplete(ctx) {
2572
3033
  const data = ctx.data;
2573
3034
  if (!data?.task) {
2574
- logger7.debug("onAfterComplete: task is missing, skip");
3035
+ logger8.debug("onAfterComplete: task is missing, skip");
2575
3036
  return;
2576
3037
  }
2577
- logger7.debug(`onAfterComplete[${this.pluginName}]: task #${data.task.id} 终态卡片由 ` + `LarkCliTaskNotifyHook 负责,skip`);
3038
+ logger8.debug(`onAfterComplete[${this.pluginName}]: task #${data.task.id} 终态卡片由 ` + `LarkCliTaskNotifyHook 负责,skip`);
2578
3039
  }
2579
3040
  createGroupChat(opts) {
2580
3041
  const args = this.buildCreateGroupChatArgs(opts);
2581
3042
  if (isPluginDebugEnabled()) {
2582
- logger7.debug(`Creating group chat: lark-cli ${args.join(" ")}`);
3043
+ logger8.debug(`Creating group chat: lark-cli ${args.join(" ")}`);
2583
3044
  }
2584
3045
  return new Promise((resolve, reject) => {
2585
3046
  const proc = spawn("lark-cli", args, {
@@ -2654,16 +3115,16 @@ class PerTaskGroupChatPlugin {
2654
3115
  parsed[CTX_FIELD_CHAT_TASK_ID] = task.id;
2655
3116
  const newContext = JSON.stringify(parsed);
2656
3117
  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。`);
3118
+ 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
3119
  task.context = newContext;
2659
3120
  return;
2660
3121
  }
2661
3122
  try {
2662
3123
  await this.taskUpdater(task.id, newContext);
2663
3124
  task.context = newContext;
2664
- logger7.debug(`attachChatIdToTask[${this.pluginName}]: ✅ persisted chat_id ${chatId} ` + `to task #${task.id} context`);
3125
+ logger8.debug(`attachChatIdToTask[${this.pluginName}]: ✅ persisted chat_id ${chatId} ` + `to task #${task.id} context`);
2665
3126
  } catch (error) {
2666
- logger7.error(`Failed to persist chat_id to task #${task.id}: ${error}`);
3127
+ logger8.error(`Failed to persist chat_id to task #${task.id}: ${error}`);
2667
3128
  task.context = newContext;
2668
3129
  }
2669
3130
  }
@@ -2758,15 +3219,15 @@ class PerTaskGroupChatPlugin {
2758
3219
  }
2759
3220
  resolveBotAppIds(replyChannel) {
2760
3221
  if (this.config.botAppIds.length > 0) {
2761
- logger7.debug(`resolveBotAppIds[${this.pluginName}]: using config.botAppIds=${JSON.stringify(this.config.botAppIds)}`);
3222
+ logger8.debug(`resolveBotAppIds[${this.pluginName}]: using config.botAppIds=${JSON.stringify(this.config.botAppIds)}`);
2762
3223
  return this.config.botAppIds;
2763
3224
  }
2764
3225
  const appId = replyChannel ? replyChannel.appId : undefined;
2765
3226
  if (appId) {
2766
- logger7.debug(`resolveBotAppIds[${this.pluginName}]: using replyChannel.appId=${appId} (auto-detected from event header.app_id)`);
3227
+ logger8.debug(`resolveBotAppIds[${this.pluginName}]: using replyChannel.appId=${appId} (auto-detected from event header.app_id)`);
2767
3228
  return [appId];
2768
3229
  }
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。`);
3230
+ 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
3231
  return [];
2771
3232
  }
2772
3233
  resolveUserIds(replyChannel) {
@@ -2776,7 +3237,7 @@ class PerTaskGroupChatPlugin {
2776
3237
  if (id && id !== "sender" && id.startsWith("ou_")) {
2777
3238
  ids.add(id);
2778
3239
  } else if (id && id !== "sender") {
2779
- logger7.debug(`resolveUserIds[${this.pluginName}]: skip non-open_id in defaultInviteUserIds: "${id}"`);
3240
+ logger8.debug(`resolveUserIds[${this.pluginName}]: skip non-open_id in defaultInviteUserIds: "${id}"`);
2780
3241
  }
2781
3242
  }
2782
3243
  }
@@ -2799,7 +3260,7 @@ class PerTaskGroupChatPlugin {
2799
3260
  }
2800
3261
  }
2801
3262
  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))}`);
3263
+ 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
3264
  }
2804
3265
  return Array.from(ids);
2805
3266
  }
@@ -2952,4 +3413,4 @@ __legacyDecorateClassTS([
2952
3413
  function createPerTaskGroupChatPlugin(config = {}) {
2953
3414
  return new PerTaskGroupChatPlugin(config);
2954
3415
  }
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 };
3416
+ 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 };