@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.
- package/dist/env/agent/agent-component.js +6 -6
- package/dist/env/agent/agent-registry.d.ts +4 -1
- package/dist/env/agent/index.js +6 -6
- package/dist/env/error-reply.d.ts +46 -0
- package/dist/env/error-reply.js +3 -1
- package/dist/env/event-source/event-source-handlers.js +13 -12
- package/dist/env/event-source/index.js +14 -13
- package/dist/env/index.js +31 -30
- package/dist/env/llm/index.js +2 -2
- package/dist/env/llm/orphan-toolcall.d.ts +107 -0
- package/dist/env/plugin/index.js +3 -3
- package/dist/env/prompt/index.js +4 -3
- package/dist/env/prompt/prompts-index.d.ts +27 -3
- package/dist/env/session/index.js +4 -4
- package/dist/env/session/storage/index.js +3 -3
- package/dist/env/task/backend/index.js +9 -1
- package/dist/env/task/delegate/delegate-tool.d.ts +8 -0
- package/dist/env/task/delegate/delegate-tool.js +5 -4
- package/dist/env/task/delegate/index.js +5 -4
- package/dist/env/task/hooks/contexts.d.ts +12 -1
- package/dist/env/task/hooks/task-hook-points.d.ts +8 -3
- package/dist/env/task/index.d.ts +1 -0
- package/dist/env/task/index.js +17 -7
- package/dist/env/task/mcp/index.js +3 -2
- package/dist/env/task/plugins/index.d.ts +3 -0
- package/dist/env/task/plugins/index.js +33 -9
- package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +61 -0
- package/dist/env/task/plugins/lark-card-patch-notifier.js +6 -2
- package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +42 -0
- package/dist/env/task/prompt/index.js +2 -1
- package/dist/env/task/prompt/task-discipline.d.ts +9 -3
- package/dist/env/task/task-component.d.ts +79 -0
- package/dist/env/task/tools/cancel-tool.d.ts +36 -0
- package/dist/env/task/tools/completion-copy.d.ts +92 -0
- package/dist/env/task/tools/index.d.ts +2 -0
- package/dist/env/task/tools/index.js +7 -2
- package/dist/env/task/tools/pre-complete-tool.d.ts +32 -0
- package/dist/env/task/tools/task-types.d.ts +71 -17
- package/dist/env/task/types.d.ts +13 -1
- package/dist/env/tool/built-in/compact-session-tool.d.ts +120 -0
- package/dist/env/tool/built-in/index.d.ts +2 -0
- package/dist/env/tool/built-in/index.js +6 -2
- package/dist/env/tool/index.js +4 -4
- package/dist/env/workflow/engine/index.js +6 -6
- package/dist/env/workflow/index.js +15 -13
- package/dist/env/workflow/plugins/index.js +2 -2
- package/dist/env/workflow/tools/ask-user-tool.d.ts +88 -0
- package/dist/env/workflow/tools/index.js +11 -9
- package/dist/env/workflow/tools/run-workflow.d.ts +2 -2
- package/dist/env/workflow/types/index.js +2 -2
- package/dist/env/workflow/types/workflow-hil.d.ts +25 -1
- package/dist/env/workflow/utils/index.js +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +43 -33
- package/dist/shared/@ai-setting/{roy-agent-core-sm0sf6sb.js → roy-agent-core-0qyrk5k0.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-kmbct9yg.js → roy-agent-core-0snm5hc5.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-yr1mryb8.js → roy-agent-core-1v4g0d4x.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-rxb00gjm.js → roy-agent-core-264qexac.js} +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-2akfd64z.js +374 -0
- package/dist/shared/@ai-setting/{roy-agent-core-skyc30qx.js → roy-agent-core-3fh1f7s7.js} +2 -2
- package/dist/shared/@ai-setting/roy-agent-core-4rtg69cn.js +26 -0
- package/dist/shared/@ai-setting/{roy-agent-core-add1zqsh.js → roy-agent-core-6cy43nss.js} +22 -1
- package/dist/shared/@ai-setting/{roy-agent-core-vah6g5yx.js → roy-agent-core-6rh8k6ba.js} +5 -5
- package/dist/shared/@ai-setting/{roy-agent-core-kpg25s2q.js → roy-agent-core-8ez6mccb.js} +4 -2
- package/dist/shared/@ai-setting/{roy-agent-core-yhwxpj12.js → roy-agent-core-9vxh6f8h.js} +233 -16
- package/dist/shared/@ai-setting/{roy-agent-core-7qvw65jt.js → roy-agent-core-aa2j7bk9.js} +11 -8
- package/dist/shared/@ai-setting/{roy-agent-core-sjr1kmfz.js → roy-agent-core-acj8ag08.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-mds8tqd0.js → roy-agent-core-cgtq6427.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-34jz9mgh.js → roy-agent-core-cmprygrp.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-wr7mv7k3.js → roy-agent-core-cxy143m4.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-pakhchy7.js → roy-agent-core-ddyk1rc0.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-attpry6g.js → roy-agent-core-dqxec05w.js} +4 -3
- package/dist/shared/@ai-setting/{roy-agent-core-vm8h5zyj.js → roy-agent-core-ed233k5z.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-jrj30vq3.js → roy-agent-core-er070zjq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-40rv283c.js → roy-agent-core-fghc2m8z.js} +152 -2
- package/dist/shared/@ai-setting/{roy-agent-core-5gw0qa8m.js → roy-agent-core-ghnagzrh.js} +557 -92
- package/dist/shared/@ai-setting/{roy-agent-core-4jjwewvw.js → roy-agent-core-m46fe3x4.js} +41 -30
- package/dist/shared/@ai-setting/{roy-agent-core-e25xkv53.js → roy-agent-core-mkc3zshb.js} +3 -1
- package/dist/shared/@ai-setting/{roy-agent-core-hfyfavp3.js → roy-agent-core-mww0mnd9.js} +231 -27
- package/dist/shared/@ai-setting/{roy-agent-core-46rv4d7d.js → roy-agent-core-nmx9934r.js} +92 -2
- package/dist/shared/@ai-setting/{roy-agent-core-1w9y7mxj.js → roy-agent-core-p3zv1mx5.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-jgb3bb42.js → roy-agent-core-q5r75vev.js} +17 -8
- package/dist/shared/@ai-setting/{roy-agent-core-y12kyxwa.js → roy-agent-core-t3rrb959.js} +33 -9
- package/dist/shared/@ai-setting/{roy-agent-core-dt104qa1.js → roy-agent-core-v85n42ve.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-gr48c89p.js → roy-agent-core-v87njmq7.js} +3 -3
- package/dist/shared/@ai-setting/{roy-agent-core-7sj9xs9c.js → roy-agent-core-vffccgf6.js} +252 -25
- package/dist/shared/@ai-setting/{roy-agent-core-2gzt2q8c.js → roy-agent-core-z43aj03k.js} +134 -29
- package/dist/shared/@ai-setting/roy-agent-core-zycadbpx.js +662 -0
- package/package.json +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-1p6syv85.js +0 -1083
- 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-
|
|
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-
|
|
27
|
+
} from "./roy-agent-core-nmx9934r.js";
|
|
28
28
|
import {
|
|
29
29
|
validateWorkflowDefinition
|
|
30
|
-
} from "./roy-agent-core-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2382
|
+
logger7.debug(`repinAfterResume: task #${task.id} 卡片 ${persisted.messageId} 已重新置顶`);
|
|
1919
2383
|
} catch (err) {
|
|
1920
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2542
|
+
logger7.debug(`sendCardUpdate: task #${task.id} 卡片已终结(closed)且任务仍终态 → skip patch`);
|
|
2079
2543
|
return;
|
|
2080
2544
|
}
|
|
2081
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2656
|
+
logger7.debug(`sendCardFinish: task #${task.id} → status=${status} ` + `(raw=${rawStatus}) message=${messageId}`);
|
|
2192
2657
|
} catch (err) {
|
|
2193
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2874
|
+
logger8.info(`ensureGroupChat[${this.pluginName}]: created group ${chatId} for task #${task.id}`);
|
|
2410
2875
|
return { chatId, profile, name };
|
|
2411
2876
|
} catch (error) {
|
|
2412
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2927
|
+
logger8.debug(`execute[${this.pluginName}]: ${enabledState} (key=${key}, ` + `hookPoint=${ctx.hookPoint}, taskUpdaterInjected=${this.hasTaskUpdater()})`);
|
|
2463
2928
|
} else {
|
|
2464
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2994
|
+
logger8.info(`onAfterCreate[${this.pluginName}]: task #${task.id} → group ${created.chatId} ` + `(initial card owned by LarkCliTaskNotifyHook)`);
|
|
2530
2995
|
} catch (error) {
|
|
2531
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3023
|
+
logger8.debug("onAfterUpdate: task is missing, skip");
|
|
2559
3024
|
return;
|
|
2560
3025
|
}
|
|
2561
|
-
|
|
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
|
-
|
|
3031
|
+
logger8.debug("onAfterOperationCreate: operation is missing, skip");
|
|
2567
3032
|
return;
|
|
2568
3033
|
}
|
|
2569
|
-
|
|
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
|
-
|
|
3039
|
+
logger8.debug("onAfterComplete: task is missing, skip");
|
|
2575
3040
|
return;
|
|
2576
3041
|
}
|
|
2577
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3129
|
+
logger8.debug(`attachChatIdToTask[${this.pluginName}]: ✅ persisted chat_id ${chatId} ` + `to task #${task.id} context`);
|
|
2665
3130
|
} catch (error) {
|
|
2666
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3231
|
+
logger8.debug(`resolveBotAppIds[${this.pluginName}]: using replyChannel.appId=${appId} (auto-detected from event header.app_id)`);
|
|
2767
3232
|
return [appId];
|
|
2768
3233
|
}
|
|
2769
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 };
|