@ai-setting/roy-agent-core 1.7.64 → 1.7.65
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/environment.d.ts +27 -0
- package/dist/env/event-source/event-source-handlers.js +3 -2
- package/dist/env/event-source/index.js +4 -3
- package/dist/env/index.js +7 -6
- package/dist/env/task/delegate/delegate-tool.d.ts +2 -2
- package/dist/env/task/hooks/contexts.d.ts +34 -0
- package/dist/env/task/hooks/verdict.d.ts +170 -0
- package/dist/env/task/index.js +3 -2
- package/dist/env/task/mcp/index.js +2 -1
- package/dist/env/task/plugins/__e2e__/laya-chain-validate.d.ts +15 -0
- package/dist/env/task/plugins/__e2e__/laya-decision-branches.d.ts +18 -0
- package/dist/env/task/plugins/__e2e__/laya-decision-e2e-live.d.ts +27 -0
- package/dist/env/task/plugins/__e2e__/laya-e2e-verify.d.ts +17 -0
- package/dist/env/task/plugins/decision-hint-plugin.d.ts +144 -0
- package/dist/env/task/plugins/decision-port.d.ts +119 -0
- package/dist/env/task/plugins/decision-renderers.d.ts +33 -0
- package/dist/env/task/plugins/index.d.ts +1 -0
- package/dist/env/task/plugins/index.js +8 -1
- package/dist/env/task/plugins/laya-decision-plugin.d.ts +136 -0
- package/dist/env/task/plugins/laya-port.d.ts +143 -0
- package/dist/env/task/plugins/laya-render.d.ts +100 -0
- package/dist/env/task/tools/index.js +2 -1
- package/dist/index.js +7 -6
- package/dist/shared/@ai-setting/{roy-agent-core-1h5bbqcg.js → roy-agent-core-18am1h07.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-vncwfzw5.js → roy-agent-core-6z412atw.js} +39 -2
- package/dist/shared/@ai-setting/{roy-agent-core-vd9zak15.js → roy-agent-core-e42v9bcq.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-ry2eztft.js → roy-agent-core-f6c0nadn.js} +958 -94
- package/dist/shared/@ai-setting/{roy-agent-core-e4hehcdr.js → roy-agent-core-hmdcz3g9.js} +3 -2
- package/dist/shared/@ai-setting/{roy-agent-core-xw4wsj38.js → roy-agent-core-t0ktaevq.js} +48 -2
- package/dist/shared/@ai-setting/roy-agent-core-tr3ep81v.js +397 -0
- package/package.json +1 -1
|
@@ -11,6 +11,16 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
WORKFLOW_EXTRACT_PROMPT
|
|
13
13
|
} from "./roy-agent-core-6masakz4.js";
|
|
14
|
+
import {
|
|
15
|
+
assertNoLeakage,
|
|
16
|
+
buildHintText,
|
|
17
|
+
deriveActionFromStatus,
|
|
18
|
+
formatRelativeAge,
|
|
19
|
+
parseSimilarTaskIds,
|
|
20
|
+
renderCandidateKey,
|
|
21
|
+
renderCandidateValue,
|
|
22
|
+
writeDecisionHint
|
|
23
|
+
} from "./roy-agent-core-tr3ep81v.js";
|
|
14
24
|
import {
|
|
15
25
|
LarkCardPatchNotifier,
|
|
16
26
|
humanizeValue
|
|
@@ -322,42 +332,896 @@ ${prompt}`;
|
|
|
322
332
|
__require("fs").mkdirSync(dataDir, { recursive: true });
|
|
323
333
|
}
|
|
324
334
|
}
|
|
335
|
+
// src/env/task/plugins/laya-render.ts
|
|
336
|
+
var LAYA_CANDIDATE_TITLE_MAX = 50;
|
|
337
|
+
var LAYA_CANDIDATE_DESC_MAX = 45;
|
|
338
|
+
var LAYA_CANDIDATE_TAGS_MAX = 4;
|
|
339
|
+
var LAYA_STATE_DESC_MAX = 600;
|
|
340
|
+
var LAYA_NEW_TASK_DESC = "新建为独立任务(与所有现有候选均无明确关联)";
|
|
341
|
+
var LAYA_STATUS_LETTER = {
|
|
342
|
+
active: "A",
|
|
343
|
+
todo: "T",
|
|
344
|
+
completed: "C",
|
|
345
|
+
paused: "P",
|
|
346
|
+
cancelled: "X",
|
|
347
|
+
archived: "R"
|
|
348
|
+
};
|
|
349
|
+
function layaCandidateKey(c) {
|
|
350
|
+
return `#${c.id} ${truncate(c.title, LAYA_CANDIDATE_TITLE_MAX)}`;
|
|
351
|
+
}
|
|
352
|
+
function layaCandidateValue(c) {
|
|
353
|
+
const letter = LAYA_STATUS_LETTER[c.status] ?? (c.status.charAt(0) || "T").toUpperCase();
|
|
354
|
+
const desc = truncate(c.description.replace(/\n/g, " "), LAYA_CANDIDATE_DESC_MAX);
|
|
355
|
+
const tags = (c.tags ?? []).slice(0, LAYA_CANDIDATE_TAGS_MAX);
|
|
356
|
+
const tagsPart = tags.length > 0 ? ` [tags: ${tags.join(",")}]` : "";
|
|
357
|
+
return `${letter}|${c.ago}|${desc}${tagsPart}`;
|
|
358
|
+
}
|
|
359
|
+
function renderLayaState(input) {
|
|
360
|
+
const desc = (input.description || "").slice(0, LAYA_STATE_DESC_MAX) || "(无)";
|
|
361
|
+
const lines = [];
|
|
362
|
+
for (const c of input.candidates) {
|
|
363
|
+
lines.push(`${layaCandidateKey(c)}
|
|
364
|
+
${layaCandidateValue(c)}`);
|
|
365
|
+
}
|
|
366
|
+
const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
|
|
367
|
+
const tagsLine = tags.length > 0 ? `【请求标签】${tags.join(",")}
|
|
368
|
+
` : "";
|
|
369
|
+
return `【新任务标题】${input.title}
|
|
370
|
+
` + `【新任务描述】${desc}
|
|
371
|
+
` + tagsLine + `【候选任务】
|
|
372
|
+
` + lines.join(`
|
|
373
|
+
`);
|
|
374
|
+
}
|
|
375
|
+
function renderLayaCriteria(candidates) {
|
|
376
|
+
const crit = {};
|
|
377
|
+
for (const c of candidates) {
|
|
378
|
+
crit[layaCandidateKey(c)] = layaCandidateValue(c);
|
|
379
|
+
}
|
|
380
|
+
crit["NEW_TASK"] = LAYA_NEW_TASK_DESC;
|
|
381
|
+
return crit;
|
|
382
|
+
}
|
|
383
|
+
function matchChoiceToCandidate(choice, candidates) {
|
|
384
|
+
if (typeof choice !== "string" || choice.length === 0)
|
|
385
|
+
return;
|
|
386
|
+
for (const c of candidates) {
|
|
387
|
+
if (layaCandidateKey(c) === choice)
|
|
388
|
+
return c.id;
|
|
389
|
+
}
|
|
390
|
+
if (choice.startsWith("#")) {
|
|
391
|
+
const bare = Number(choice.slice(1));
|
|
392
|
+
if (Number.isInteger(bare) && candidates.some((c) => c.id === bare))
|
|
393
|
+
return bare;
|
|
394
|
+
}
|
|
395
|
+
const m = /^#(\d+)\s/.exec(choice);
|
|
396
|
+
if (m) {
|
|
397
|
+
const bare = Number(m[1]);
|
|
398
|
+
if (Number.isInteger(bare) && candidates.some((c) => c.id === bare))
|
|
399
|
+
return bare;
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
function truncate(text, max) {
|
|
404
|
+
if (!text)
|
|
405
|
+
return "";
|
|
406
|
+
if (text.length <= max)
|
|
407
|
+
return text;
|
|
408
|
+
return text.slice(0, max);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/env/task/plugins/decision-renderers.ts
|
|
412
|
+
var SYSTEMONE_STAGE_A_INSTRUCTIONS = "判断本次『新任务请求』应该新建还是复用某个已有任务。若应复用,选该任务的 #id;若应新建,选 NEW_TASK。输出 choice/confidence/probabilities。";
|
|
413
|
+
var LAYA_STAGE_A_INSTRUCTIONS = "优先复用:若候选与请求属于同一工作流/同一系列/目标重叠,必须选该候选的 #id;仅当完全无关时才选 NEW_TASK。";
|
|
414
|
+
var systemoneRenderer = {
|
|
415
|
+
buildStageA(input) {
|
|
416
|
+
const criteria = {};
|
|
417
|
+
const similarTasks = input.candidates.map((t) => {
|
|
418
|
+
const key = renderCandidateKey(t);
|
|
419
|
+
criteria[key] = renderCandidateValue({
|
|
420
|
+
id: t.id,
|
|
421
|
+
status: t.status,
|
|
422
|
+
ago: formatRelativeAge(t.updatedAt, input.now),
|
|
423
|
+
description: t.description ?? "",
|
|
424
|
+
tags: t.tags ?? []
|
|
425
|
+
});
|
|
426
|
+
return {
|
|
427
|
+
id: t.id,
|
|
428
|
+
status: t.status,
|
|
429
|
+
ago: formatRelativeAge(t.updatedAt, input.now),
|
|
430
|
+
title: t.title,
|
|
431
|
+
snippet: (t.description ?? "").slice(0, 120),
|
|
432
|
+
tags: t.tags ?? []
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
criteria["NEW_TASK"] = "新建为独立任务(与所有现有候选均无明确关联)";
|
|
436
|
+
return {
|
|
437
|
+
state: {
|
|
438
|
+
task_to_route: {
|
|
439
|
+
title: input.title,
|
|
440
|
+
description: input.description,
|
|
441
|
+
goals: input.goals,
|
|
442
|
+
tags: input.tags,
|
|
443
|
+
project_path: input.projectPath
|
|
444
|
+
},
|
|
445
|
+
similar_tasks: similarTasks
|
|
446
|
+
},
|
|
447
|
+
questions: {
|
|
448
|
+
reuse_or_create: {
|
|
449
|
+
type: "choice",
|
|
450
|
+
instructions: SYSTEMONE_STAGE_A_INSTRUCTIONS,
|
|
451
|
+
criteria
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
},
|
|
456
|
+
matchChoice(choice, candidates) {
|
|
457
|
+
const keyById = new Map(candidates.map((t) => [renderCandidateKey(t), t.id]));
|
|
458
|
+
let targetTaskId = keyById.get(choice);
|
|
459
|
+
if (targetTaskId === undefined && choice.startsWith("#")) {
|
|
460
|
+
const bareId = Number(choice.slice(1));
|
|
461
|
+
if (Number.isInteger(bareId) && candidates.some((t) => t.id === bareId)) {
|
|
462
|
+
targetTaskId = bareId;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return targetTaskId;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
var layaRenderer = {
|
|
469
|
+
buildStageA(input) {
|
|
470
|
+
const candidates = input.candidates.map((t) => ({
|
|
471
|
+
id: t.id,
|
|
472
|
+
title: t.title,
|
|
473
|
+
status: t.status,
|
|
474
|
+
ago: formatRelativeAge(t.updatedAt, input.now),
|
|
475
|
+
description: t.description ?? "",
|
|
476
|
+
tags: t.tags ?? []
|
|
477
|
+
}));
|
|
478
|
+
const reqTags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
|
|
479
|
+
return {
|
|
480
|
+
state: renderLayaState({
|
|
481
|
+
title: input.title,
|
|
482
|
+
description: input.description,
|
|
483
|
+
tags: reqTags,
|
|
484
|
+
candidates
|
|
485
|
+
}),
|
|
486
|
+
questions: {
|
|
487
|
+
reuse_or_create: {
|
|
488
|
+
type: "choice",
|
|
489
|
+
instructions: reqTags.length > 0 ? `${LAYA_STAGE_A_INSTRUCTIONS}
|
|
490
|
+
请求标签:${reqTags.join(",")}` : LAYA_STAGE_A_INSTRUCTIONS,
|
|
491
|
+
criteria: renderLayaCriteria(candidates)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
},
|
|
496
|
+
matchChoice(choice, candidates) {
|
|
497
|
+
const layaCands = candidates.map((t) => ({
|
|
498
|
+
id: t.id,
|
|
499
|
+
title: t.title,
|
|
500
|
+
status: t.status,
|
|
501
|
+
ago: "",
|
|
502
|
+
description: t.description ?? "",
|
|
503
|
+
tags: t.tags ?? []
|
|
504
|
+
}));
|
|
505
|
+
const direct = matchChoiceToCandidate(choice, layaCands);
|
|
506
|
+
if (direct !== undefined)
|
|
507
|
+
return direct;
|
|
508
|
+
const m = /^#(\d+)/.exec(choice);
|
|
509
|
+
if (m) {
|
|
510
|
+
const bare = Number(m[1]);
|
|
511
|
+
if (Number.isInteger(bare) && candidates.some((t) => t.id === bare))
|
|
512
|
+
return bare;
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// src/env/task/plugins/decision-hint-plugin.ts
|
|
519
|
+
init_logger();
|
|
520
|
+
init_decorator();
|
|
521
|
+
var logger2 = createLogger("plugin:decision-hint");
|
|
522
|
+
function normalizeHookCtx(ctx) {
|
|
523
|
+
const maybe = ctx;
|
|
524
|
+
return maybe && typeof maybe === "object" && maybe.data ? maybe.data : ctx;
|
|
525
|
+
}
|
|
526
|
+
var MAX_STAGE_A_CANDIDATES = 20;
|
|
527
|
+
var MAX_LONGTERM_ROOTS = 6;
|
|
528
|
+
var MAX_KEYWORD_ROOTS = 10;
|
|
529
|
+
var FALLBACK_TOP_N = 5;
|
|
530
|
+
var REUSE_ADOPT_THRESHOLD = 0.6;
|
|
531
|
+
var SOFT_HINT_THRESHOLD = 0.4;
|
|
532
|
+
var MAX_ROOT_ANCESTOR_DEPTH = 20;
|
|
533
|
+
|
|
534
|
+
class DecisionHintPlugin {
|
|
535
|
+
name = "decision-hint";
|
|
536
|
+
version = "1.0.0";
|
|
537
|
+
description = "Task #4467 — task:before.create 的 decision 检测:判定新建/复用 + root/子挂载,只提示不拦截";
|
|
538
|
+
decisionPort;
|
|
539
|
+
envParentTaskIdProvider;
|
|
540
|
+
renderer;
|
|
541
|
+
reuseThreshold;
|
|
542
|
+
softHintThreshold;
|
|
543
|
+
singleCandidateShortcut;
|
|
544
|
+
constructor(options) {
|
|
545
|
+
this.decisionPort = options.decisionPort;
|
|
546
|
+
this.envParentTaskIdProvider = options.envParentTaskIdProvider ?? (() => {
|
|
547
|
+
return;
|
|
548
|
+
});
|
|
549
|
+
this.renderer = options.renderer ?? systemoneRenderer;
|
|
550
|
+
this.reuseThreshold = options.reuseThreshold ?? REUSE_ADOPT_THRESHOLD;
|
|
551
|
+
this.softHintThreshold = options.softHintThreshold ?? SOFT_HINT_THRESHOLD;
|
|
552
|
+
this.singleCandidateShortcut = options.singleCandidateShortcut ?? true;
|
|
553
|
+
}
|
|
554
|
+
async execute(ctx) {
|
|
555
|
+
try {
|
|
556
|
+
await this.run(normalizeHookCtx(ctx));
|
|
557
|
+
} catch (err) {
|
|
558
|
+
logger2.warn(`[decision-hint] degraded to pass-through: ${err?.message}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async run(ctx) {
|
|
562
|
+
const taskComponent = ctx.taskComponent;
|
|
563
|
+
if (!taskComponent) {
|
|
564
|
+
logger2.warn("[decision-hint] taskComponent not injected — skip");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const explicitIds = parseSimilarTaskIds(ctx.options.context);
|
|
568
|
+
let candidates = [];
|
|
569
|
+
if (explicitIds.length > 0) {
|
|
570
|
+
for (const id of explicitIds) {
|
|
571
|
+
const t = await taskComponent.getTask(id);
|
|
572
|
+
if (t)
|
|
573
|
+
candidates.push(t);
|
|
574
|
+
}
|
|
575
|
+
} else {
|
|
576
|
+
candidates = await this.fallbackCandidates(taskComponent, ctx.options.tags);
|
|
577
|
+
}
|
|
578
|
+
if (candidates.length === 0) {
|
|
579
|
+
logger2.debug("[decision-hint] 0 candidates — pass through");
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (candidates.length === 1 && this.singleCandidateShortcut) {
|
|
583
|
+
const hint = buildHintText(this.reuseHintInput(candidates[0], 1));
|
|
584
|
+
writeDecisionHint(ctx.options, hint);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const trimmed = candidates.slice(0, MAX_STAGE_A_CANDIDATES);
|
|
588
|
+
const stageA = await this.runStageA(ctx, trimmed);
|
|
589
|
+
if (!stageA)
|
|
590
|
+
return;
|
|
591
|
+
if (stageA.kind === "reuse") {
|
|
592
|
+
const target = trimmed.find((t) => t.id === stageA.targetTaskId);
|
|
593
|
+
if (!target)
|
|
594
|
+
return;
|
|
595
|
+
const hint = buildHintText(this.reuseHintInput(target, stageA.confidence));
|
|
596
|
+
writeDecisionHint(ctx.options, hint);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (stageA.kind === "silent") {
|
|
600
|
+
logger2.debug("[decision-hint] Stage A silent — skip Stage B (no hint)");
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const stageB = await this.runStageB(ctx, taskComponent);
|
|
604
|
+
if (!stageB)
|
|
605
|
+
return;
|
|
606
|
+
if (stageB.kind === "remount") {
|
|
607
|
+
const hint = buildHintText({
|
|
608
|
+
scenario: "remount",
|
|
609
|
+
newTaskId: stageB.newTaskId,
|
|
610
|
+
recommendedRootId: stageB.recommendedRootId,
|
|
611
|
+
recommendedRootTitle: stageB.recommendedRootTitle,
|
|
612
|
+
currentParentId: stageB.currentParentId,
|
|
613
|
+
currentRootAncestorId: stageB.currentRootAncestorId
|
|
614
|
+
});
|
|
615
|
+
writeDecisionHint(ctx.options, hint);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
async fallbackCandidates(taskComponent, tags) {
|
|
619
|
+
const cleanTags = (tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
|
|
620
|
+
if (cleanTags.length === 0)
|
|
621
|
+
return [];
|
|
622
|
+
try {
|
|
623
|
+
const res = await taskComponent.searchTasksByKeywords(cleanTags, {
|
|
624
|
+
limit: FALLBACK_TOP_N,
|
|
625
|
+
include_archived: true
|
|
626
|
+
});
|
|
627
|
+
return (res.tasks ?? []).filter((t) => {
|
|
628
|
+
const titleHit = cleanTags.some((k) => t.title.toLowerCase().includes(k.toLowerCase()));
|
|
629
|
+
const tagHit = (t.tags ?? []).some((tag) => cleanTags.some((k) => tag.toLowerCase().includes(k.toLowerCase())));
|
|
630
|
+
return titleHit || tagHit;
|
|
631
|
+
}).slice(0, FALLBACK_TOP_N);
|
|
632
|
+
} catch (err) {
|
|
633
|
+
logger2.warn(`[decision-hint] fallback retrieval failed: ${err?.message}`);
|
|
634
|
+
return [];
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async runStageA(ctx, candidates) {
|
|
638
|
+
const now = Date.now();
|
|
639
|
+
const request = this.buildStageARequest(ctx, candidates, now);
|
|
640
|
+
let response;
|
|
641
|
+
try {
|
|
642
|
+
response = await this.decisionPort.decide(request);
|
|
643
|
+
} catch (err) {
|
|
644
|
+
logger2.warn(`[decision-hint] Stage A decision failed: ${err?.message}`);
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
const answer = response?.answers?.["reuse_or_create"];
|
|
648
|
+
if (!answer || typeof answer.choice !== "string") {
|
|
649
|
+
logger2.warn("[decision-hint] Stage A answer malformed — pass through");
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
const confidence = pickGateConfidence(answer);
|
|
653
|
+
if (confidence < this.softHintThreshold) {
|
|
654
|
+
return { kind: "silent" };
|
|
655
|
+
}
|
|
656
|
+
if (answer.choice === "NEW_TASK") {
|
|
657
|
+
return { kind: "create" };
|
|
658
|
+
}
|
|
659
|
+
const targetTaskId = this.renderer.matchChoice(answer.choice, candidates);
|
|
660
|
+
if (targetTaskId === undefined) {
|
|
661
|
+
logger2.warn(`[decision-hint] Stage A choice not in candidate set: ${answer.choice}`);
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
return { kind: "reuse", targetTaskId, confidence };
|
|
665
|
+
}
|
|
666
|
+
buildStageARequest(ctx, candidates, now) {
|
|
667
|
+
return this.renderer.buildStageA({
|
|
668
|
+
title: ctx.options.title,
|
|
669
|
+
description: ctx.options.description ?? "",
|
|
670
|
+
goals: ctx.options.goals_and_expected_deliverables ?? "",
|
|
671
|
+
tags: ctx.options.tags ?? [],
|
|
672
|
+
projectPath: ctx.options.project_path ?? "",
|
|
673
|
+
candidates,
|
|
674
|
+
now
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
async runStageB(ctx, taskComponent) {
|
|
678
|
+
const now = Date.now();
|
|
679
|
+
const roots = await this.collectRootCandidates(taskComponent, ctx);
|
|
680
|
+
if (roots.length === 0)
|
|
681
|
+
return null;
|
|
682
|
+
const criteria = {};
|
|
683
|
+
const rootCandidates = roots.map((t) => {
|
|
684
|
+
const key = renderCandidateKey(t);
|
|
685
|
+
criteria[key] = renderCandidateValue({
|
|
686
|
+
id: t.id,
|
|
687
|
+
status: t.status,
|
|
688
|
+
ago: formatRelativeAge(t.updatedAt, now),
|
|
689
|
+
description: t.description ?? "",
|
|
690
|
+
tags: t.tags ?? []
|
|
691
|
+
});
|
|
692
|
+
return {
|
|
693
|
+
id: t.id,
|
|
694
|
+
type: t.type,
|
|
695
|
+
ago: formatRelativeAge(t.updatedAt, now),
|
|
696
|
+
title: t.title,
|
|
697
|
+
snippet: truncateSnippet(t.description ?? ""),
|
|
698
|
+
tags: t.tags ?? []
|
|
699
|
+
};
|
|
700
|
+
});
|
|
701
|
+
criteria["NEW_ROOT"] = "新建为顶级根任务(与所有现有根任务均无明确关联)";
|
|
702
|
+
const request = {
|
|
703
|
+
state: {
|
|
704
|
+
task_to_route: {
|
|
705
|
+
title: ctx.options.title,
|
|
706
|
+
description: ctx.options.description ?? "",
|
|
707
|
+
goals: ctx.options.goals_and_expected_deliverables ?? "",
|
|
708
|
+
tags: ctx.options.tags ?? [],
|
|
709
|
+
project_path: ctx.options.project_path ?? "",
|
|
710
|
+
has_explicit_parent: ctx.options.parent_task_id != null,
|
|
711
|
+
env_parent_task_id: this.envParentTaskIdProvider() ?? null,
|
|
712
|
+
available_roots_count: roots.length,
|
|
713
|
+
pool_size_before_prune: roots.length,
|
|
714
|
+
pool_size_after_prune: roots.length,
|
|
715
|
+
candidates_truncated: false,
|
|
716
|
+
now: new Date(now).toISOString()
|
|
717
|
+
},
|
|
718
|
+
root_candidates: rootCandidates
|
|
719
|
+
},
|
|
720
|
+
questions: {
|
|
721
|
+
root_choice: {
|
|
722
|
+
type: "choice",
|
|
723
|
+
instructions: "判断本次『新任务』应该新建为独立根任务,还是挂载到某个已有根任务下。若新任务和所有现有根任务都没明确关联,选 NEW_ROOT。输出 choice/confidence/probabilities。",
|
|
724
|
+
criteria
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
let response;
|
|
729
|
+
try {
|
|
730
|
+
response = await this.decisionPort.decide(request);
|
|
731
|
+
} catch (err) {
|
|
732
|
+
logger2.warn(`[decision-hint] Stage B decision failed: ${err?.message}`);
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
const answer = response?.answers?.["root_choice"];
|
|
736
|
+
if (!answer || typeof answer.choice !== "string")
|
|
737
|
+
return null;
|
|
738
|
+
const confidence = pickGateConfidence(answer);
|
|
739
|
+
if (confidence < this.softHintThreshold)
|
|
740
|
+
return null;
|
|
741
|
+
const keyById = new Map(roots.map((t) => [renderCandidateKey(t), t]));
|
|
742
|
+
let recommended = keyById.get(answer.choice);
|
|
743
|
+
if (!recommended && answer.choice.startsWith("#")) {
|
|
744
|
+
const bareId = Number(answer.choice.slice(1));
|
|
745
|
+
if (Number.isInteger(bareId))
|
|
746
|
+
recommended = roots.find((t) => t.id === bareId);
|
|
747
|
+
}
|
|
748
|
+
if (!recommended)
|
|
749
|
+
return null;
|
|
750
|
+
const currentParentId = ctx.options.parent_task_id ?? undefined;
|
|
751
|
+
if (currentParentId === undefined)
|
|
752
|
+
return null;
|
|
753
|
+
const currentRootAncestorId = await this.resolveRootAncestor(taskComponent, currentParentId);
|
|
754
|
+
if (currentRootAncestorId === recommended.id) {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
kind: "remount",
|
|
759
|
+
newTaskId: 0,
|
|
760
|
+
recommendedRootId: recommended.id,
|
|
761
|
+
recommendedRootTitle: recommended.title,
|
|
762
|
+
currentParentId,
|
|
763
|
+
currentRootAncestorId
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
async collectRootCandidates(taskComponent, ctx) {
|
|
767
|
+
try {
|
|
768
|
+
const all = (await taskComponent.listTasks({ depth: 0, include_archived: true }))?.tasks ?? [];
|
|
769
|
+
const longterm = all.filter((t) => t.type === "longterm").slice(0, MAX_LONGTERM_ROOTS);
|
|
770
|
+
const keywords = (ctx.options.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
|
|
771
|
+
let matched = [];
|
|
772
|
+
if (keywords.length > 0) {
|
|
773
|
+
matched = all.filter((t) => {
|
|
774
|
+
const titleHit = keywords.some((k) => t.title.toLowerCase().includes(k.toLowerCase()));
|
|
775
|
+
const tagHit = (t.tags ?? []).some((tag) => keywords.some((k) => tag.toLowerCase().includes(k.toLowerCase())));
|
|
776
|
+
return titleHit || tagHit;
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
const seen = new Set;
|
|
780
|
+
const out = [];
|
|
781
|
+
for (const t of [...longterm, ...matched]) {
|
|
782
|
+
if (seen.has(t.id))
|
|
783
|
+
continue;
|
|
784
|
+
seen.add(t.id);
|
|
785
|
+
out.push(t);
|
|
786
|
+
}
|
|
787
|
+
return out.slice(0, MAX_LONGTERM_ROOTS + MAX_KEYWORD_ROOTS);
|
|
788
|
+
} catch (err) {
|
|
789
|
+
logger2.warn(`[decision-hint] root collection failed: ${err?.message}`);
|
|
790
|
+
return [];
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
async resolveRootAncestor(taskComponent, taskId) {
|
|
794
|
+
const visited = new Set([taskId]);
|
|
795
|
+
let current = taskId;
|
|
796
|
+
for (let depth = 0;depth < MAX_ROOT_ANCESTOR_DEPTH; depth++) {
|
|
797
|
+
const t = await taskComponent.getTask(current);
|
|
798
|
+
const parentId = t?.parent_task_id;
|
|
799
|
+
if (parentId === undefined || parentId === null)
|
|
800
|
+
return current;
|
|
801
|
+
if (visited.has(parentId)) {
|
|
802
|
+
logger2.warn(`[decision-hint] root ancestor cycle detected at #${parentId}`);
|
|
803
|
+
return current;
|
|
804
|
+
}
|
|
805
|
+
visited.add(parentId);
|
|
806
|
+
current = parentId;
|
|
807
|
+
}
|
|
808
|
+
return current;
|
|
809
|
+
}
|
|
810
|
+
reuseHintInput(target, confidence) {
|
|
811
|
+
const action = deriveActionFromStatus(target.status);
|
|
812
|
+
const scenario = action === "reopen" ? "reopen" : confidence >= this.reuseThreshold ? "reuse" : "soft_reuse";
|
|
813
|
+
return {
|
|
814
|
+
scenario,
|
|
815
|
+
newTaskId: 0,
|
|
816
|
+
targetTaskId: target.id,
|
|
817
|
+
targetTask: { title: target.title, status: target.status, type: target.type },
|
|
818
|
+
confidence
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
__legacyDecorateClassTS([
|
|
823
|
+
TracedAs("task.decision.hint.onBeforeCreate", {
|
|
824
|
+
recordParams: true,
|
|
825
|
+
recordResult: true,
|
|
826
|
+
log: false,
|
|
827
|
+
paramFilter: (args) => {
|
|
828
|
+
const raw = args[0] ?? {};
|
|
829
|
+
const ctx = raw?.data ?? raw;
|
|
830
|
+
const opts = ctx?.options ?? {};
|
|
831
|
+
const decisionHint = typeof opts.context === "string" ? opts.context : "";
|
|
832
|
+
return {
|
|
833
|
+
hasTaskComponent: Boolean(ctx?.taskComponent),
|
|
834
|
+
titleLen: typeof opts.title === "string" ? opts.title.length : 0,
|
|
835
|
+
title: typeof opts.title === "string" ? opts.title.slice(0, 120) : null,
|
|
836
|
+
tagsCount: Array.isArray(opts.tags) ? opts.tags.length : 0,
|
|
837
|
+
hasParentTaskId: opts.parent_task_id != null,
|
|
838
|
+
contextLen: decisionHint.length,
|
|
839
|
+
similarTaskIdsCount: /similar_task_ids/.test(decisionHint) ? "present" : "absent",
|
|
840
|
+
hookCtxShape: raw?.data ? "HookContext" : "bare"
|
|
841
|
+
};
|
|
842
|
+
},
|
|
843
|
+
resultFilter: (_res) => ({ ok: true })
|
|
844
|
+
})
|
|
845
|
+
], DecisionHintPlugin.prototype, "execute", null);
|
|
846
|
+
__legacyDecorateClassTS([
|
|
847
|
+
TracedAs("task.decision.hint.fallbackCandidates", {
|
|
848
|
+
recordParams: true,
|
|
849
|
+
recordResult: true,
|
|
850
|
+
log: false,
|
|
851
|
+
paramFilter: (args) => ({
|
|
852
|
+
tags: args[1] ?? []
|
|
853
|
+
}),
|
|
854
|
+
resultFilter: (res) => ({
|
|
855
|
+
count: Array.isArray(res) ? res.length : -1,
|
|
856
|
+
ids: Array.isArray(res) ? res.map((t) => t.id).join(",") : ""
|
|
857
|
+
})
|
|
858
|
+
})
|
|
859
|
+
], DecisionHintPlugin.prototype, "fallbackCandidates", null);
|
|
860
|
+
__legacyDecorateClassTS([
|
|
861
|
+
TracedAs("task.decision.hint.stageA", {
|
|
862
|
+
recordParams: true,
|
|
863
|
+
recordResult: true,
|
|
864
|
+
log: false,
|
|
865
|
+
paramFilter: (args) => {
|
|
866
|
+
const ctx = args[0] ?? {};
|
|
867
|
+
const candidates = args[1] ?? [];
|
|
868
|
+
return {
|
|
869
|
+
candidateCount: candidates.length,
|
|
870
|
+
candidateIds: candidates.map((c) => c.id).join(","),
|
|
871
|
+
title: (ctx?.options?.title ?? "").slice(0, 120),
|
|
872
|
+
tagsCount: Array.isArray(ctx?.options?.tags) ? ctx.options.tags.length : 0
|
|
873
|
+
};
|
|
874
|
+
},
|
|
875
|
+
resultFilter: (res) => ({
|
|
876
|
+
kind: res?.kind ?? "null",
|
|
877
|
+
targetTaskId: res?.targetTaskId ?? null,
|
|
878
|
+
confidence: res?.confidence ?? null
|
|
879
|
+
})
|
|
880
|
+
})
|
|
881
|
+
], DecisionHintPlugin.prototype, "runStageA", null);
|
|
882
|
+
__legacyDecorateClassTS([
|
|
883
|
+
TracedAs("task.decision.hint.stageB", {
|
|
884
|
+
recordParams: true,
|
|
885
|
+
recordResult: true,
|
|
886
|
+
log: false,
|
|
887
|
+
paramFilter: (args) => {
|
|
888
|
+
const ctx = args[0] ?? {};
|
|
889
|
+
return {
|
|
890
|
+
title: (ctx?.options?.title ?? "").slice(0, 120),
|
|
891
|
+
hasExplicitParent: ctx?.options?.parent_task_id != null,
|
|
892
|
+
explicitParentId: ctx?.options?.parent_task_id ?? null,
|
|
893
|
+
projectPath: ctx?.options?.project_path ?? null
|
|
894
|
+
};
|
|
895
|
+
},
|
|
896
|
+
resultFilter: (res) => ({
|
|
897
|
+
kind: res?.kind ?? "null",
|
|
898
|
+
recommendedRootId: res?.recommendedRootId ?? null,
|
|
899
|
+
currentParentId: res?.currentParentId ?? null,
|
|
900
|
+
currentRootAncestorId: res?.currentRootAncestorId ?? null
|
|
901
|
+
})
|
|
902
|
+
})
|
|
903
|
+
], DecisionHintPlugin.prototype, "runStageB", null);
|
|
904
|
+
function clampConfidence(value) {
|
|
905
|
+
if (typeof value !== "number" || Number.isNaN(value))
|
|
906
|
+
return 0;
|
|
907
|
+
if (value > 1)
|
|
908
|
+
return 1;
|
|
909
|
+
if (value < 0)
|
|
910
|
+
return 0;
|
|
911
|
+
return value;
|
|
912
|
+
}
|
|
913
|
+
function pickGateConfidence(answer) {
|
|
914
|
+
if (!answer)
|
|
915
|
+
return 0;
|
|
916
|
+
const jev = typeof answer.x_jev_confidence === "number" ? answer.x_jev_confidence : undefined;
|
|
917
|
+
const answerConf = typeof answer.answer_confidence === "number" ? answer.answer_confidence : undefined;
|
|
918
|
+
const entropy = typeof answer.confidence === "number" ? answer.confidence : undefined;
|
|
919
|
+
const picked = jev ?? answerConf ?? entropy;
|
|
920
|
+
return clampConfidence(picked);
|
|
921
|
+
}
|
|
922
|
+
function truncateSnippet(text) {
|
|
923
|
+
if (text.length <= 45)
|
|
924
|
+
return text;
|
|
925
|
+
return text.slice(0, 45) + "…";
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// src/env/task/plugins/laya-port.ts
|
|
929
|
+
var DEFAULT_TIMEOUT_MS = 2000;
|
|
930
|
+
var DEFAULT_MAX_STRIKES = 3;
|
|
931
|
+
var DEFAULT_MODEL = "task-decision";
|
|
932
|
+
var SYSTEMONE_PATH = "/v1/systemone";
|
|
933
|
+
|
|
934
|
+
class LayaDecisionPort {
|
|
935
|
+
baseURL;
|
|
936
|
+
apiKey;
|
|
937
|
+
model;
|
|
938
|
+
timeoutMs;
|
|
939
|
+
maxStrikes;
|
|
940
|
+
fetchImpl;
|
|
941
|
+
confidencePref;
|
|
942
|
+
timeoutStrikes = 0;
|
|
943
|
+
disabled = false;
|
|
944
|
+
constructor(options) {
|
|
945
|
+
this.baseURL = options.baseURL.replace(/\/+$/, "");
|
|
946
|
+
this.apiKey = options.apiKey;
|
|
947
|
+
this.model = options.model ?? DEFAULT_MODEL;
|
|
948
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
949
|
+
this.maxStrikes = options.maxStrikes ?? DEFAULT_MAX_STRIKES;
|
|
950
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
951
|
+
this.confidencePref = options.confidencePref ?? "auto";
|
|
952
|
+
}
|
|
953
|
+
isDisabled() {
|
|
954
|
+
return this.disabled || this.timeoutStrikes >= this.maxStrikes;
|
|
955
|
+
}
|
|
956
|
+
get endpoint() {
|
|
957
|
+
if (this.baseURL.endsWith(SYSTEMONE_PATH))
|
|
958
|
+
return this.baseURL;
|
|
959
|
+
return `${this.baseURL}${SYSTEMONE_PATH}`;
|
|
960
|
+
}
|
|
961
|
+
async decide(req) {
|
|
962
|
+
if (this.isDisabled()) {
|
|
963
|
+
throw new Error("[LayaDecisionPort] port self-disabled (3 consecutive timeouts)");
|
|
964
|
+
}
|
|
965
|
+
assertNoLeakage(req.state, req.questions);
|
|
966
|
+
const controller = new AbortController;
|
|
967
|
+
let timedOut = false;
|
|
968
|
+
const timer = setTimeout(() => {
|
|
969
|
+
timedOut = true;
|
|
970
|
+
controller.abort();
|
|
971
|
+
}, this.timeoutMs);
|
|
972
|
+
try {
|
|
973
|
+
const fetchPromise = this.fetchImpl(this.endpoint, {
|
|
974
|
+
method: "POST",
|
|
975
|
+
headers: {
|
|
976
|
+
"Content-Type": "application/json",
|
|
977
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
978
|
+
},
|
|
979
|
+
body: JSON.stringify({
|
|
980
|
+
model: this.model,
|
|
981
|
+
state: req.state,
|
|
982
|
+
questions: req.questions
|
|
983
|
+
}),
|
|
984
|
+
signal: controller.signal
|
|
985
|
+
});
|
|
986
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
987
|
+
const onTimeout = () => {
|
|
988
|
+
const err = new Error(`[LayaDecisionPort] request timed out after ${this.timeoutMs}ms`);
|
|
989
|
+
err.name = "TimeoutError";
|
|
990
|
+
reject(err);
|
|
991
|
+
};
|
|
992
|
+
if (timedOut)
|
|
993
|
+
onTimeout();
|
|
994
|
+
else {
|
|
995
|
+
const t = setTimeout(onTimeout, this.timeoutMs);
|
|
996
|
+
t.unref?.();
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
const res = await Promise.race([fetchPromise, timeoutPromise]);
|
|
1000
|
+
if (!res.ok) {
|
|
1001
|
+
throw new Error(`[LayaDecisionPort] HTTP ${res.status}`);
|
|
1002
|
+
}
|
|
1003
|
+
const body = await res.json();
|
|
1004
|
+
this.timeoutStrikes = 0;
|
|
1005
|
+
return this.normalize(body);
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
if (timedOut || isTimeoutError(err)) {
|
|
1008
|
+
this.timeoutStrikes += 1;
|
|
1009
|
+
if (this.timeoutStrikes >= this.maxStrikes) {
|
|
1010
|
+
this.disabled = true;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
throw err;
|
|
1014
|
+
} finally {
|
|
1015
|
+
clearTimeout(timer);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
normalize(body) {
|
|
1019
|
+
const usage = body?.usage;
|
|
1020
|
+
const dropped = usage?.state_tokens_dropped ?? 0;
|
|
1021
|
+
const truncated = Boolean(usage?.truncated) || dropped > 0;
|
|
1022
|
+
const answers = {};
|
|
1023
|
+
for (const [qid, a] of Object.entries(body?.answers ?? {})) {
|
|
1024
|
+
answers[qid] = {
|
|
1025
|
+
...a,
|
|
1026
|
+
truncated: truncated || Boolean(a?.truncated),
|
|
1027
|
+
confidence: this.pickConfidence(a)
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
return { ...body, answers };
|
|
1031
|
+
}
|
|
1032
|
+
pickConfidence(a) {
|
|
1033
|
+
if (!a)
|
|
1034
|
+
return 0;
|
|
1035
|
+
const jev = numberOrUndefined(a.x_jev_confidence);
|
|
1036
|
+
const answerConf = numberOrUndefined(a.answer_confidence);
|
|
1037
|
+
const entropy = numberOrUndefined(a.confidence);
|
|
1038
|
+
switch (this.confidencePref) {
|
|
1039
|
+
case "jev":
|
|
1040
|
+
return jev ?? 0;
|
|
1041
|
+
case "entropy":
|
|
1042
|
+
return entropy ?? 0;
|
|
1043
|
+
case "auto":
|
|
1044
|
+
default:
|
|
1045
|
+
return jev ?? answerConf ?? entropy ?? 0;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function numberOrUndefined(v) {
|
|
1050
|
+
return typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
1051
|
+
}
|
|
1052
|
+
function isTimeoutError(err) {
|
|
1053
|
+
if (err instanceof Error) {
|
|
1054
|
+
if (err.name === "AbortError" || err.name === "TimeoutError")
|
|
1055
|
+
return true;
|
|
1056
|
+
if (/timeout|timed out|abort/i.test(err.message))
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
async function loadLayaAuth(authPath = `${process.env.HOME ?? "~"}/.roy-agent/auth.json`) {
|
|
1062
|
+
try {
|
|
1063
|
+
const fs = await import("fs/promises");
|
|
1064
|
+
const raw = await fs.readFile(authPath, "utf-8");
|
|
1065
|
+
const parsed = JSON.parse(raw);
|
|
1066
|
+
const p = parsed.providers?.["laya-decision"];
|
|
1067
|
+
if (!p?.baseURL || !p?.apiKey)
|
|
1068
|
+
return null;
|
|
1069
|
+
return {
|
|
1070
|
+
baseURL: p.baseURL,
|
|
1071
|
+
apiKey: p.apiKey,
|
|
1072
|
+
model: p.model ?? DEFAULT_MODEL
|
|
1073
|
+
};
|
|
1074
|
+
} catch {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/env/task/plugins/laya-decision-plugin.ts
|
|
1080
|
+
init_logger();
|
|
1081
|
+
var logger3 = createLogger("plugin:laya-decision");
|
|
1082
|
+
var LAYA_REUSE_THRESHOLD = 0.92;
|
|
1083
|
+
|
|
1084
|
+
class LayaDecisionPlugin {
|
|
1085
|
+
name = "laya-decision";
|
|
1086
|
+
version = "1.0.0";
|
|
1087
|
+
description = "Task #4620 — task:before.create 决策检测(本地 Laya 服务载体,只提示不拦截)";
|
|
1088
|
+
options;
|
|
1089
|
+
port = null;
|
|
1090
|
+
portResolved = false;
|
|
1091
|
+
inner = null;
|
|
1092
|
+
constructor(options = {}) {
|
|
1093
|
+
this.options = options;
|
|
1094
|
+
if (options.decisionPort) {
|
|
1095
|
+
this.port = options.decisionPort;
|
|
1096
|
+
this.portResolved = true;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
isDisabled() {
|
|
1100
|
+
return this.port?.isDisabled() ?? false;
|
|
1101
|
+
}
|
|
1102
|
+
async execute(ctx) {
|
|
1103
|
+
try {
|
|
1104
|
+
const inner = await this.ensureInner();
|
|
1105
|
+
if (!inner) {
|
|
1106
|
+
logger3.debug("[laya-decision] no decision port configured — skip");
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
await inner.execute(ctx);
|
|
1110
|
+
} catch (err) {
|
|
1111
|
+
logger3.warn(`[laya-decision] unexpected failure, ignored: ${err?.message}`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
async ensureInner() {
|
|
1115
|
+
if (this.inner)
|
|
1116
|
+
return this.inner;
|
|
1117
|
+
const port = await this.resolvePort();
|
|
1118
|
+
if (!port)
|
|
1119
|
+
return null;
|
|
1120
|
+
this.inner = new DecisionHintPlugin({
|
|
1121
|
+
decisionPort: port,
|
|
1122
|
+
envParentTaskIdProvider: this.options.envParentTaskIdProvider,
|
|
1123
|
+
renderer: layaRenderer,
|
|
1124
|
+
reuseThreshold: this.options.reuseThreshold ?? LAYA_REUSE_THRESHOLD,
|
|
1125
|
+
softHintThreshold: this.options.softHintThreshold ?? LAYA_REUSE_THRESHOLD,
|
|
1126
|
+
singleCandidateShortcut: this.options.singleCandidateShortcut ?? false
|
|
1127
|
+
});
|
|
1128
|
+
return this.inner;
|
|
1129
|
+
}
|
|
1130
|
+
async resolvePort() {
|
|
1131
|
+
if (this.portResolved)
|
|
1132
|
+
return this.port;
|
|
1133
|
+
this.portResolved = true;
|
|
1134
|
+
if (this.options.decisionPort) {
|
|
1135
|
+
this.port = this.options.decisionPort;
|
|
1136
|
+
return this.port;
|
|
1137
|
+
}
|
|
1138
|
+
let cfg = null;
|
|
1139
|
+
try {
|
|
1140
|
+
cfg = await loadLayaAuth(this.options.authPath);
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
logger3.warn(`[laya-decision] auth load failed: ${err?.message}`);
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
if (!cfg) {
|
|
1146
|
+
logger3.info("[laya-decision] providers['laya-decision'] not configured in auth.json — skip");
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
try {
|
|
1150
|
+
this.port = new LayaDecisionPort({
|
|
1151
|
+
baseURL: cfg.baseURL,
|
|
1152
|
+
apiKey: cfg.apiKey,
|
|
1153
|
+
model: cfg.model,
|
|
1154
|
+
timeoutMs: this.options.timeoutMs,
|
|
1155
|
+
maxStrikes: this.options.maxStrikes,
|
|
1156
|
+
fetchImpl: this.options.fetchImpl,
|
|
1157
|
+
confidencePref: this.options.confidencePref
|
|
1158
|
+
});
|
|
1159
|
+
logger3.info(`[laya-decision] port ready: ${cfg.baseURL} model=${cfg.model} ` + `timeout=${this.options.timeoutMs ?? 2000}ms strikes=${this.options.maxStrikes ?? 3}`);
|
|
1160
|
+
return this.port;
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
logger3.warn(`[laya-decision] port construction failed: ${err?.message}`);
|
|
1163
|
+
return null;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
var sharedPlugin = null;
|
|
1168
|
+
function getSharedLayaDecisionPlugin(options) {
|
|
1169
|
+
if (options && Object.keys(options).length > 0) {
|
|
1170
|
+
return new LayaDecisionPlugin(options);
|
|
1171
|
+
}
|
|
1172
|
+
if (!sharedPlugin) {
|
|
1173
|
+
sharedPlugin = new LayaDecisionPlugin;
|
|
1174
|
+
}
|
|
1175
|
+
return sharedPlugin;
|
|
1176
|
+
}
|
|
1177
|
+
function createLayaDecisionPlugin(options) {
|
|
1178
|
+
const inner = getSharedLayaDecisionPlugin(options);
|
|
1179
|
+
return {
|
|
1180
|
+
name: "laya-decision",
|
|
1181
|
+
priority: 20,
|
|
1182
|
+
execute: async (ctx) => {
|
|
1183
|
+
const data = ctx && typeof ctx === "object" && "data" in ctx ? ctx.data : ctx;
|
|
1184
|
+
await inner.execute(data);
|
|
1185
|
+
},
|
|
1186
|
+
plugin: inner
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
325
1189
|
// src/env/task/plugins/auto-task-plugin.ts
|
|
326
1190
|
init_logger();
|
|
327
1191
|
init_decorator();
|
|
328
|
-
var
|
|
1192
|
+
var logger4 = createLogger("AutoTaskPlugin");
|
|
329
1193
|
function createAutoTaskPlugin(config) {
|
|
330
1194
|
const threshold = config?.threshold ?? 20;
|
|
331
1195
|
const agentName = config?.agentName ?? "default";
|
|
332
1196
|
let lastProcessedTraceId = "";
|
|
333
1197
|
async function executeImpl(ctx) {
|
|
334
1198
|
try {
|
|
335
|
-
|
|
1199
|
+
logger4.info(`[AutoTaskPlugin:ENTER] agentName=${ctx.agent.name} iteration=${ctx.iteration} threshold=${threshold}`);
|
|
336
1200
|
if (ctx.agent.name !== agentName)
|
|
337
1201
|
return { continue: true };
|
|
338
1202
|
if (ctx.iteration !== threshold)
|
|
339
1203
|
return { continue: true };
|
|
340
1204
|
const traceId = ctx.context?.traceId ?? ctx.context?.sessionId ?? "unknown";
|
|
341
1205
|
if (traceId === lastProcessedTraceId) {
|
|
342
|
-
|
|
1206
|
+
logger4.debug(`[AutoTaskPlugin] skip - traceId=${traceId.slice(0, 20)}... already processed`);
|
|
343
1207
|
return { continue: true };
|
|
344
1208
|
}
|
|
345
1209
|
lastProcessedTraceId = traceId;
|
|
346
|
-
|
|
1210
|
+
logger4.debug(`[AutoTaskPlugin] new query detected (traceId=${traceId.slice(0, 20)}...)`);
|
|
347
1211
|
const reminderContent = `⚠️ **系统提醒**: 你已经调用了 ${ctx.iteration} 次工具,达到 iteration 阈值(默认 ${threshold})。
|
|
348
1212
|
` + `当前请求较为复杂,建议通过 \`delegate_task\` 工具将整个任务委托给 \`roy\` sub-agent 处理。` + `注意,需要让roy为这个任务调用task create工具来创建任务进行跟踪,这个是必走程序哈。`;
|
|
349
1213
|
if (!ctx.toolResult) {
|
|
350
|
-
|
|
1214
|
+
logger4.warn(`[AutoTaskPlugin] toolResult missing, skip meta-protocol (iteration=${ctx.iteration})`);
|
|
351
1215
|
return { continue: true };
|
|
352
1216
|
}
|
|
353
1217
|
enqueueToAddFakeUserMessage(ctx.toolResult, {
|
|
354
1218
|
role: "user",
|
|
355
1219
|
content: reminderContent
|
|
356
1220
|
}, "delegate");
|
|
357
|
-
|
|
1221
|
+
logger4.info(`[AutoTaskPlugin] user reminder meta-protocol set (session=${(ctx.context?.sessionId ?? "unknown").slice(0, 12)}..., iteration=${ctx.iteration})`);
|
|
358
1222
|
return { continue: true };
|
|
359
1223
|
} catch (err) {
|
|
360
|
-
|
|
1224
|
+
logger4.error(`[AutoTaskPlugin] hook failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
361
1225
|
return { continue: true };
|
|
362
1226
|
}
|
|
363
1227
|
}
|
|
@@ -385,7 +1249,7 @@ init_context();
|
|
|
385
1249
|
init_logger();
|
|
386
1250
|
init_sqlite_task_store();
|
|
387
1251
|
import { join } from "path";
|
|
388
|
-
var
|
|
1252
|
+
var logger5 = createLogger("lark-cli:card-state");
|
|
389
1253
|
|
|
390
1254
|
class MemoryLarkCardStateStore {
|
|
391
1255
|
records = new Map;
|
|
@@ -549,7 +1413,7 @@ function createLarkCardStateStore(dbPath) {
|
|
|
549
1413
|
try {
|
|
550
1414
|
return new SqliteLarkCardStateStore(path);
|
|
551
1415
|
} catch (err) {
|
|
552
|
-
|
|
1416
|
+
logger5.warn(`LarkCardStateStore: sqlite unavailable at ${path} (${err.message}) ` + `→ falling back to in-memory (卡片仍可发,但重启后无法 patch 旧卡)`);
|
|
553
1417
|
return new MemoryLarkCardStateStore;
|
|
554
1418
|
}
|
|
555
1419
|
}
|
|
@@ -631,7 +1495,7 @@ function decideCardChatTarget(input) {
|
|
|
631
1495
|
}
|
|
632
1496
|
|
|
633
1497
|
// src/env/task/plugins/lark-cli-task-notify-plugin.ts
|
|
634
|
-
var
|
|
1498
|
+
var logger6 = createLogger("lark-cli:task-notify");
|
|
635
1499
|
var CTX_FIELD_CHAT_ID = "larkPerTaskChatId";
|
|
636
1500
|
var CTX_FIELD_CHAT_PROFILE = "larkPerTaskChatProfile";
|
|
637
1501
|
function parseLarkPerTaskChatId(ctxStr) {
|
|
@@ -721,7 +1585,7 @@ class LarkCliTaskNotifyHook {
|
|
|
721
1585
|
}
|
|
722
1586
|
setCardRoutingPorts(ports) {
|
|
723
1587
|
this.cardRoutingPorts = ports;
|
|
724
|
-
|
|
1588
|
+
logger6.debug(`CardRoutingPorts injected: getTask=${typeof ports.getTask}`);
|
|
725
1589
|
}
|
|
726
1590
|
hasCardRoutingPorts() {
|
|
727
1591
|
return this.cardRoutingPorts !== undefined;
|
|
@@ -734,7 +1598,7 @@ class LarkCliTaskNotifyHook {
|
|
|
734
1598
|
if (this.pluginName && this.sourceId) {
|
|
735
1599
|
const key = `${this.sourceId}:${this.pluginName}`;
|
|
736
1600
|
if (env.pluginEnabled && env.pluginEnabled[key] !== true) {
|
|
737
|
-
|
|
1601
|
+
logger6.debug(`Plugin not enabled for source: ${key}`);
|
|
738
1602
|
return;
|
|
739
1603
|
}
|
|
740
1604
|
}
|
|
@@ -837,11 +1701,11 @@ class LarkCliTaskNotifyHook {
|
|
|
837
1701
|
try {
|
|
838
1702
|
record = this.cardStateStore?.get(taskId);
|
|
839
1703
|
} catch (err) {
|
|
840
|
-
|
|
1704
|
+
logger6.warn(`onAfterDelete: 读取卡片记录失败 task #${taskId}: ${err.message}`);
|
|
841
1705
|
}
|
|
842
1706
|
const messageId = record?.messageId || this.cardMessageIds.get(taskId);
|
|
843
1707
|
if (!messageId) {
|
|
844
|
-
|
|
1708
|
+
logger6.debug(`onAfterDelete: task #${taskId} 无卡片记录,skip(不发新卡)`);
|
|
845
1709
|
return;
|
|
846
1710
|
}
|
|
847
1711
|
this.restoreCardState(taskId);
|
|
@@ -856,17 +1720,17 @@ class LarkCliTaskNotifyHook {
|
|
|
856
1720
|
finalStep: "任务已取消(删除)"
|
|
857
1721
|
});
|
|
858
1722
|
} catch (err) {
|
|
859
|
-
|
|
1723
|
+
logger6.warn(`onAfterDelete: patch 卡片失败 task #${taskId} message=${messageId}: ${err.message}`);
|
|
860
1724
|
}
|
|
861
1725
|
try {
|
|
862
1726
|
await this.cardNotifier.unpinMessage(messageId, profile);
|
|
863
1727
|
} catch (err) {
|
|
864
|
-
|
|
1728
|
+
logger6.warn(`onAfterDelete: unpin 失败 task #${taskId} message=${messageId}: ${err.message}`);
|
|
865
1729
|
}
|
|
866
1730
|
this.persistClosed(taskId);
|
|
867
1731
|
this.persistPinned(taskId, false);
|
|
868
1732
|
this.cardMessageIds.delete(taskId);
|
|
869
|
-
|
|
1733
|
+
logger6.debug(`onAfterDelete: task #${taskId} 卡片已收尾 status=deleted message=${messageId}`);
|
|
870
1734
|
}
|
|
871
1735
|
async onAfterOperationCreate(ctx) {
|
|
872
1736
|
const { operation } = ctx.data;
|
|
@@ -881,11 +1745,11 @@ class LarkCliTaskNotifyHook {
|
|
|
881
1745
|
const persisted = this.restoreCardState(operation.taskId);
|
|
882
1746
|
const messageId = this.cardMessageIds.get(operation.taskId);
|
|
883
1747
|
if (!messageId || messageId === LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL || messageId === LarkCliTaskNotifyHook.CARD_START_INFLIGHT_SENTINEL) {
|
|
884
|
-
|
|
1748
|
+
logger6.debug(`onAfterOperationCreate: no usable card for task #${operation.taskId}, skip`);
|
|
885
1749
|
return;
|
|
886
1750
|
}
|
|
887
1751
|
if (persisted && persisted.status !== "open") {
|
|
888
|
-
|
|
1752
|
+
logger6.debug(`onAfterOperationCreate: task #${operation.taskId} card status=${persisted.status}, skip`);
|
|
889
1753
|
return;
|
|
890
1754
|
}
|
|
891
1755
|
const { milestones, processSteps } = this.deriveTimelineFromOperation(operation);
|
|
@@ -902,7 +1766,7 @@ class LarkCliTaskNotifyHook {
|
|
|
902
1766
|
processSteps
|
|
903
1767
|
});
|
|
904
1768
|
} catch (err) {
|
|
905
|
-
|
|
1769
|
+
logger6.error(`sendCardOperation failed for task #${operation.taskId}: ${err.message}`);
|
|
906
1770
|
}
|
|
907
1771
|
}
|
|
908
1772
|
restoreCardState(taskId) {
|
|
@@ -917,12 +1781,12 @@ class LarkCliTaskNotifyHook {
|
|
|
917
1781
|
if (rec.status !== "closed" && rec.messageId) {
|
|
918
1782
|
this.cardMessageIds.set(taskId, rec.messageId);
|
|
919
1783
|
this.hydrateNotifierState(rec);
|
|
920
|
-
|
|
1784
|
+
logger6.debug(`restoreCardState: task #${taskId} ← 持久层 message ${rec.messageId} ` + `(status=${rec.status}, pinned=${rec.pinned}, timeline=${rec.timeline ? "yes" : "no"})`);
|
|
921
1785
|
}
|
|
922
1786
|
}
|
|
923
1787
|
return rec;
|
|
924
1788
|
} catch (err) {
|
|
925
|
-
|
|
1789
|
+
logger6.warn(`restoreCardState failed for task #${taskId}: ${err.message}`);
|
|
926
1790
|
return;
|
|
927
1791
|
}
|
|
928
1792
|
}
|
|
@@ -933,7 +1797,7 @@ class LarkCliTaskNotifyHook {
|
|
|
933
1797
|
const snapshot = JSON.parse(rec.timeline);
|
|
934
1798
|
this.cardNotifier.hydrateState?.(rec.messageId, snapshot);
|
|
935
1799
|
} catch (err) {
|
|
936
|
-
|
|
1800
|
+
logger6.warn(`hydrateNotifierState failed for task #${rec.taskId}: ${err.message}`);
|
|
937
1801
|
}
|
|
938
1802
|
}
|
|
939
1803
|
snapshotTimeline(state) {
|
|
@@ -945,7 +1809,7 @@ class LarkCliTaskNotifyHook {
|
|
|
945
1809
|
return state;
|
|
946
1810
|
return { ...state, timeline: JSON.stringify(snap) };
|
|
947
1811
|
} catch (err) {
|
|
948
|
-
|
|
1812
|
+
logger6.warn(`snapshotTimeline failed for task #${state.taskId}: ${err.message}`);
|
|
949
1813
|
return state;
|
|
950
1814
|
}
|
|
951
1815
|
}
|
|
@@ -956,7 +1820,7 @@ class LarkCliTaskNotifyHook {
|
|
|
956
1820
|
const payload = includeTimeline ? this.snapshotTimeline(state) : state;
|
|
957
1821
|
this.cardStateStore.upsert(payload);
|
|
958
1822
|
} catch (err) {
|
|
959
|
-
|
|
1823
|
+
logger6.warn(`persistCardState failed for task #${state.taskId}: ${err.message}`);
|
|
960
1824
|
}
|
|
961
1825
|
}
|
|
962
1826
|
persistPinned(taskId, pinned) {
|
|
@@ -965,7 +1829,7 @@ class LarkCliTaskNotifyHook {
|
|
|
965
1829
|
try {
|
|
966
1830
|
this.cardStateStore.markPinned(taskId, pinned);
|
|
967
1831
|
} catch (err) {
|
|
968
|
-
|
|
1832
|
+
logger6.warn(`persistPinned failed for task #${taskId}: ${err.message}`);
|
|
969
1833
|
}
|
|
970
1834
|
}
|
|
971
1835
|
persistPaused(taskId) {
|
|
@@ -974,7 +1838,7 @@ class LarkCliTaskNotifyHook {
|
|
|
974
1838
|
try {
|
|
975
1839
|
this.cardStateStore.markPaused(taskId);
|
|
976
1840
|
} catch (err) {
|
|
977
|
-
|
|
1841
|
+
logger6.warn(`persistPaused failed for task #${taskId}: ${err.message}`);
|
|
978
1842
|
}
|
|
979
1843
|
}
|
|
980
1844
|
dropStaleMessageId(taskId) {
|
|
@@ -991,7 +1855,7 @@ class LarkCliTaskNotifyHook {
|
|
|
991
1855
|
updatedAt: Date.now()
|
|
992
1856
|
});
|
|
993
1857
|
} catch (err) {
|
|
994
|
-
|
|
1858
|
+
logger6.warn(`dropStaleMessageId failed for task #${taskId}: ${err.message}`);
|
|
995
1859
|
}
|
|
996
1860
|
}
|
|
997
1861
|
persistReopened(taskId) {
|
|
@@ -1000,7 +1864,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1000
1864
|
try {
|
|
1001
1865
|
this.cardStateStore.markReopened(taskId);
|
|
1002
1866
|
} catch (err) {
|
|
1003
|
-
|
|
1867
|
+
logger6.warn(`persistReopened failed for task #${taskId}: ${err.message}`);
|
|
1004
1868
|
}
|
|
1005
1869
|
}
|
|
1006
1870
|
persistClosed(taskId) {
|
|
@@ -1009,7 +1873,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1009
1873
|
try {
|
|
1010
1874
|
this.cardStateStore.markClosed(taskId);
|
|
1011
1875
|
} catch (err) {
|
|
1012
|
-
|
|
1876
|
+
logger6.warn(`persistClosed failed for task #${taskId}: ${err.message}`);
|
|
1013
1877
|
}
|
|
1014
1878
|
}
|
|
1015
1879
|
refreshTimeline(taskId) {
|
|
@@ -1021,7 +1885,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1021
1885
|
return;
|
|
1022
1886
|
this.cardStateStore.upsert(this.snapshotTimeline(rec));
|
|
1023
1887
|
} catch (err) {
|
|
1024
|
-
|
|
1888
|
+
logger6.warn(`refreshTimeline failed for task #${taskId}: ${err.message}`);
|
|
1025
1889
|
}
|
|
1026
1890
|
}
|
|
1027
1891
|
async repinAfterResume(task, persisted) {
|
|
@@ -1037,9 +1901,9 @@ class LarkCliTaskNotifyHook {
|
|
|
1037
1901
|
try {
|
|
1038
1902
|
await this.cardNotifier.pinMessage(persisted.messageId, profile);
|
|
1039
1903
|
this.persistPinned(task.id, true);
|
|
1040
|
-
|
|
1904
|
+
logger6.debug(`repinAfterResume: task #${task.id} 卡片 ${persisted.messageId} 已重新置顶`);
|
|
1041
1905
|
} catch (err) {
|
|
1042
|
-
|
|
1906
|
+
logger6.warn(`repinAfterResume failed (non-fatal) for task #${task.id}: ${err.message}`);
|
|
1043
1907
|
}
|
|
1044
1908
|
}
|
|
1045
1909
|
buildCardState(task, messageId, chatId, profile, pinned) {
|
|
@@ -1047,7 +1911,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1047
1911
|
try {
|
|
1048
1912
|
existing = this.cardStateStore?.get(task.id);
|
|
1049
1913
|
} catch (err) {
|
|
1050
|
-
|
|
1914
|
+
logger6.warn(`buildCardState: read existing failed for task #${task.id}: ${err.message}`);
|
|
1051
1915
|
}
|
|
1052
1916
|
const now = Date.now();
|
|
1053
1917
|
return {
|
|
@@ -1081,7 +1945,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1081
1945
|
}
|
|
1082
1946
|
isCardModeEnabled() {
|
|
1083
1947
|
if (this.config.cardMode === false || !this.cardNotifier) {
|
|
1084
|
-
|
|
1948
|
+
logger6.debug("cardMode=false (legacy opt-out), skip task notification (no text fallback)");
|
|
1085
1949
|
return false;
|
|
1086
1950
|
}
|
|
1087
1951
|
return true;
|
|
@@ -1151,7 +2015,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1151
2015
|
}
|
|
1152
2016
|
const claim = this.cardMessageIds.get(task.id);
|
|
1153
2017
|
if (claim) {
|
|
1154
|
-
|
|
2018
|
+
logger6.debug(`sendCardStart: task #${task.id} 已有首卡(${claim})→ 跳过重复发卡`);
|
|
1155
2019
|
return;
|
|
1156
2020
|
}
|
|
1157
2021
|
this.cardMessageIds.set(task.id, LarkCliTaskNotifyHook.CARD_START_INFLIGHT_SENTINEL);
|
|
@@ -1166,7 +2030,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1166
2030
|
const profile = target.profile;
|
|
1167
2031
|
if (!chatId) {
|
|
1168
2032
|
this.cardMessageIds.delete(task.id);
|
|
1169
|
-
|
|
2033
|
+
logger6.debug(`sendCardStart: no chatId for task #${task.id}, skip`);
|
|
1170
2034
|
return;
|
|
1171
2035
|
}
|
|
1172
2036
|
try {
|
|
@@ -1184,10 +2048,10 @@ class LarkCliTaskNotifyHook {
|
|
|
1184
2048
|
});
|
|
1185
2049
|
this.cardMessageIds.set(task.id, messageId);
|
|
1186
2050
|
this.persistCardState(this.buildCardState(task, messageId, chatId, profile, pinned), true);
|
|
1187
|
-
|
|
2051
|
+
logger6.debug(`sendCardStart: task #${task.id} → message ${messageId} (pinned=${pinned}) ` + `chat=${chatId} source=${target.source} reason=${target.reason}`);
|
|
1188
2052
|
} catch (err) {
|
|
1189
2053
|
this.cardMessageIds.set(task.id, LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL);
|
|
1190
|
-
|
|
2054
|
+
logger6.error(`sendCardStart failed for task #${task.id}: ${err.message}`);
|
|
1191
2055
|
}
|
|
1192
2056
|
}
|
|
1193
2057
|
async sendCardUpdate(task, changes) {
|
|
@@ -1197,21 +2061,21 @@ class LarkCliTaskNotifyHook {
|
|
|
1197
2061
|
if (persisted?.status === "closed") {
|
|
1198
2062
|
const stillTerminal = isTerminalTaskStatus(String(task.status));
|
|
1199
2063
|
if (stillTerminal) {
|
|
1200
|
-
|
|
2064
|
+
logger6.debug(`sendCardUpdate: task #${task.id} 卡片已终结(closed)且任务仍终态 → skip patch`);
|
|
1201
2065
|
return;
|
|
1202
2066
|
}
|
|
1203
|
-
|
|
2067
|
+
logger6.info(`sendCardUpdate: task #${task.id} 已 reopen(closed 记录 + 当前 status=${task.status})` + `→ 重新建卡`);
|
|
1204
2068
|
this.cardMessageIds.delete(task.id);
|
|
1205
2069
|
this.dropStaleMessageId(task.id);
|
|
1206
2070
|
}
|
|
1207
2071
|
if (persisted?.status === "paused" && String(task.status) !== "paused") {
|
|
1208
2072
|
this.persistReopened(task.id);
|
|
1209
|
-
|
|
2073
|
+
logger6.debug(`sendCardUpdate: task #${task.id} resume(paused → open),复用卡片 ${persisted.messageId}`);
|
|
1210
2074
|
await this.repinAfterResume(task, persisted);
|
|
1211
2075
|
}
|
|
1212
2076
|
let messageId = this.cardMessageIds.get(task.id);
|
|
1213
2077
|
if (!messageId && isTerminalTaskStatus(String(task.status))) {
|
|
1214
|
-
|
|
2078
|
+
logger6.debug(`sendCardUpdate: task #${task.id} 状态=${task.status}(终结/归档)且无卡片 → skip`);
|
|
1215
2079
|
return;
|
|
1216
2080
|
}
|
|
1217
2081
|
if (!messageId) {
|
|
@@ -1223,15 +2087,15 @@ class LarkCliTaskNotifyHook {
|
|
|
1223
2087
|
if (!chatId) {
|
|
1224
2088
|
if (!messageId)
|
|
1225
2089
|
this.cardMessageIds.delete(task.id);
|
|
1226
|
-
|
|
2090
|
+
logger6.debug(`sendCardUpdate: no chatId for task #${task.id}, skip`);
|
|
1227
2091
|
return;
|
|
1228
2092
|
}
|
|
1229
2093
|
if (messageId === LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL) {
|
|
1230
|
-
|
|
2094
|
+
logger6.debug(`sendCardUpdate: startTask failed previously for task #${task.id}, skip`);
|
|
1231
2095
|
return;
|
|
1232
2096
|
}
|
|
1233
2097
|
if (messageId === LarkCliTaskNotifyHook.CARD_START_INFLIGHT_SENTINEL) {
|
|
1234
|
-
|
|
2098
|
+
logger6.debug(`sendCardUpdate: task #${task.id} 首卡正在发送中(in-flight)→ 跳过本次 update`);
|
|
1235
2099
|
return;
|
|
1236
2100
|
}
|
|
1237
2101
|
if (!messageId) {
|
|
@@ -1246,7 +2110,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1246
2110
|
this.persistCardState(this.buildCardState(task, messageId, chatId, profile, res.pinned), true);
|
|
1247
2111
|
} catch (err) {
|
|
1248
2112
|
this.cardMessageIds.set(task.id, LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL);
|
|
1249
|
-
|
|
2113
|
+
logger6.error(`sendCardUpdate fallback start failed: ${err.message}`);
|
|
1250
2114
|
return;
|
|
1251
2115
|
}
|
|
1252
2116
|
}
|
|
@@ -1273,7 +2137,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1273
2137
|
this.persistPinned(task.id, current?.pinned ?? persisted?.pinned ?? true);
|
|
1274
2138
|
this.refreshTimeline(task.id);
|
|
1275
2139
|
} catch (err) {
|
|
1276
|
-
|
|
2140
|
+
logger6.error(`sendCardUpdate failed: ${err.message}`);
|
|
1277
2141
|
}
|
|
1278
2142
|
}
|
|
1279
2143
|
async sendCardFinish(task) {
|
|
@@ -1284,7 +2148,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1284
2148
|
const ctx = getEnvContextOrEmpty();
|
|
1285
2149
|
const profile = this.resolveProfile(task, ctx);
|
|
1286
2150
|
if (!messageId || messageId === LarkCliTaskNotifyHook.CARD_START_FAILED_SENTINEL) {
|
|
1287
|
-
|
|
2151
|
+
logger6.debug(`sendCardFinish: no card messageId for task #${task.id}, skip ` + `(记录缺失 → 不发新卡,保持幂等)`);
|
|
1288
2152
|
return;
|
|
1289
2153
|
}
|
|
1290
2154
|
const rawStatus = String(task.status);
|
|
@@ -1309,9 +2173,9 @@ class LarkCliTaskNotifyHook {
|
|
|
1309
2173
|
this.persistPinned(task.id, false);
|
|
1310
2174
|
this.cardMessageIds.delete(task.id);
|
|
1311
2175
|
}
|
|
1312
|
-
|
|
2176
|
+
logger6.debug(`sendCardFinish: task #${task.id} → status=${status} ` + `(raw=${rawStatus}) message=${messageId}`);
|
|
1313
2177
|
} catch (err) {
|
|
1314
|
-
|
|
2178
|
+
logger6.error(`sendCardFinish failed: ${err.message}`);
|
|
1315
2179
|
}
|
|
1316
2180
|
}
|
|
1317
2181
|
resolveChatId(task, env) {
|
|
@@ -1388,7 +2252,7 @@ class LarkCliTaskNotifyHook {
|
|
|
1388
2252
|
const now = Date.now();
|
|
1389
2253
|
const lastTime = this.lastNotifyTimestamps.get(key);
|
|
1390
2254
|
if (lastTime && now - lastTime < throttleMs) {
|
|
1391
|
-
|
|
2255
|
+
logger6.debug(`[throttle] skip OPERATION_CREATED patch for task #${operation.taskId} (${now - lastTime}ms ago)`);
|
|
1392
2256
|
return true;
|
|
1393
2257
|
}
|
|
1394
2258
|
this.lastNotifyTimestamps.set(key, now);
|
|
@@ -1431,7 +2295,7 @@ init_decorator();
|
|
|
1431
2295
|
init_logger();
|
|
1432
2296
|
init_context();
|
|
1433
2297
|
import { spawn } from "child_process";
|
|
1434
|
-
var
|
|
2298
|
+
var logger7 = createLogger("lark-cli:per-task-group");
|
|
1435
2299
|
function isPluginDebugEnabled() {
|
|
1436
2300
|
return process.env.ROY_TASK_NOTIFY_DEBUG === "1" || process.env.ROY_TRACE_DEBUG === "1";
|
|
1437
2301
|
}
|
|
@@ -1486,7 +2350,7 @@ class PerTaskGroupChatPlugin {
|
|
|
1486
2350
|
const renderedName = this.renderTemplate(this.config.nameTemplate, task);
|
|
1487
2351
|
const existingChatId = this.getTaskChatId(task);
|
|
1488
2352
|
if (existingChatId) {
|
|
1489
|
-
|
|
2353
|
+
logger7.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 已有专属群 ${existingChatId} → 复用(不重复建群)`);
|
|
1490
2354
|
return {
|
|
1491
2355
|
chatId: existingChatId,
|
|
1492
2356
|
profile: this.parseProfileFromContext(task.context),
|
|
@@ -1495,7 +2359,7 @@ class PerTaskGroupChatPlugin {
|
|
|
1495
2359
|
}
|
|
1496
2360
|
const inflight = this.groupChatInflight.get(task.id);
|
|
1497
2361
|
if (inflight) {
|
|
1498
|
-
|
|
2362
|
+
logger7.debug(`ensureGroupChat[${this.pluginName}]: task #${task.id} 建群已在进行中 → 复用同一 promise`);
|
|
1499
2363
|
return inflight;
|
|
1500
2364
|
}
|
|
1501
2365
|
const pending = this.doEnsureGroupChat(task, renderedName, replyChannelOverride);
|
|
@@ -1509,7 +2373,7 @@ class PerTaskGroupChatPlugin {
|
|
|
1509
2373
|
async doEnsureGroupChat(task, name, replyChannelOverride) {
|
|
1510
2374
|
const replyChannel = replyChannelOverride ?? getEnvContextOrEmpty().replyChannel;
|
|
1511
2375
|
if (!replyChannel?.chatId && this.config.requireReplyChannel) {
|
|
1512
|
-
|
|
2376
|
+
logger7.warn(`ensureGroupChat[${this.pluginName}]: no replyChannel.chatId for task #${task.id} ` + `(requireReplyChannel=true) → 无法建群`);
|
|
1513
2377
|
return;
|
|
1514
2378
|
}
|
|
1515
2379
|
const description = this.renderTemplate(this.config.descriptionTemplate, task);
|
|
@@ -1527,30 +2391,30 @@ class PerTaskGroupChatPlugin {
|
|
|
1527
2391
|
await this.writeChatBinding(task, chatId, profile, "creating");
|
|
1528
2392
|
await this.attachChatIdToTask(task, chatId, profile);
|
|
1529
2393
|
await this.writeChatBinding(task, chatId, profile, "ready");
|
|
1530
|
-
|
|
2394
|
+
logger7.info(`ensureGroupChat[${this.pluginName}]: created group ${chatId} for task #${task.id}`);
|
|
1531
2395
|
return { chatId, profile, name };
|
|
1532
2396
|
} catch (error) {
|
|
1533
|
-
|
|
2397
|
+
logger7.error(`ensureGroupChat[${this.pluginName}]: failed for task #${task.id}: ${error}`);
|
|
1534
2398
|
return;
|
|
1535
2399
|
}
|
|
1536
2400
|
}
|
|
1537
2401
|
setTaskUpdater(updater) {
|
|
1538
2402
|
this.taskUpdater = updater;
|
|
1539
|
-
|
|
2403
|
+
logger7.debug(`TaskUpdater injected for plugin ${this.name} (sourceId=${this.sourceId})`);
|
|
1540
2404
|
}
|
|
1541
2405
|
hasTaskUpdater() {
|
|
1542
2406
|
return this.taskUpdater !== undefined;
|
|
1543
2407
|
}
|
|
1544
2408
|
setTaskGetter(getter) {
|
|
1545
2409
|
this.taskGetter = getter;
|
|
1546
|
-
|
|
2410
|
+
logger7.debug(`TaskGetter injected for plugin ${this.name} (sourceId=${this.sourceId})`);
|
|
1547
2411
|
}
|
|
1548
2412
|
hasTaskGetter() {
|
|
1549
2413
|
return this.taskGetter !== undefined;
|
|
1550
2414
|
}
|
|
1551
2415
|
setSessionComponent(sessionComponent) {
|
|
1552
2416
|
this.sessionComponent = sessionComponent;
|
|
1553
|
-
|
|
2417
|
+
logger7.debug(`SessionComponent injected for plugin ${this.name} (sourceId=${this.sourceId})`);
|
|
1554
2418
|
}
|
|
1555
2419
|
hasSessionComponent() {
|
|
1556
2420
|
return this.sessionComponent !== undefined;
|
|
@@ -1563,7 +2427,7 @@ class PerTaskGroupChatPlugin {
|
|
|
1563
2427
|
if (process.env.ROY_HOOK_DEBUG === "1") {
|
|
1564
2428
|
console.error(`[DEBUG per-task-group-chat.execute] → SKIP (config.enabled=false)`);
|
|
1565
2429
|
}
|
|
1566
|
-
|
|
2430
|
+
logger7.debug(`execute: plugin disabled (pluginName=${this.pluginName})`);
|
|
1567
2431
|
return;
|
|
1568
2432
|
}
|
|
1569
2433
|
if (this.pluginName && this.sourceId) {
|
|
@@ -1576,13 +2440,13 @@ class PerTaskGroupChatPlugin {
|
|
|
1576
2440
|
if (process.env.ROY_HOOK_DEBUG === "1") {
|
|
1577
2441
|
console.error(`[DEBUG per-task-group-chat.execute] → SKIP (plugin NOT enabled for ${key})`);
|
|
1578
2442
|
}
|
|
1579
|
-
|
|
2443
|
+
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`);
|
|
1580
2444
|
return;
|
|
1581
2445
|
}
|
|
1582
2446
|
const enabledState = env.pluginEnabled ? env.pluginEnabled[key] === true ? "✅ enabled" : "❌ NOT enabled" : "⚠️ env.pluginEnabled 未设置 (env 没创建 / 插件未注册到 eventsource)";
|
|
1583
|
-
|
|
2447
|
+
logger7.debug(`execute[${this.pluginName}]: ${enabledState} (key=${key}, ` + `hookPoint=${ctx.hookPoint}, taskUpdaterInjected=${this.hasTaskUpdater()})`);
|
|
1584
2448
|
} else {
|
|
1585
|
-
|
|
2449
|
+
logger7.debug(`execute[${this.pluginName}]: missing pluginName/sourceId metadata ` + `(pluginName=${this.pluginName}, sourceId=${this.sourceId}), ` + `skipping pluginEnabled check. hookPoint=${ctx.hookPoint}`);
|
|
1586
2450
|
}
|
|
1587
2451
|
if (process.env.ROY_HOOK_DEBUG === "1") {
|
|
1588
2452
|
console.error(`[DEBUG per-task-group-chat.execute] → PASSED pluginEnabled check, will dispatch hookPoint=${ctx.hookPoint}`);
|
|
@@ -1601,13 +2465,13 @@ class PerTaskGroupChatPlugin {
|
|
|
1601
2465
|
await this.onAfterComplete(ctx);
|
|
1602
2466
|
break;
|
|
1603
2467
|
default:
|
|
1604
|
-
|
|
2468
|
+
logger7.debug(`execute: unhandled hookPoint=${ctx.hookPoint}, skip`);
|
|
1605
2469
|
}
|
|
1606
2470
|
}
|
|
1607
2471
|
async onAfterCreate(ctx) {
|
|
1608
2472
|
const data = ctx.data;
|
|
1609
2473
|
if (!data?.task) {
|
|
1610
|
-
|
|
2474
|
+
logger7.debug("onAfterCreate: task is missing, skip");
|
|
1611
2475
|
return;
|
|
1612
2476
|
}
|
|
1613
2477
|
const { task } = data;
|
|
@@ -1618,38 +2482,38 @@ class PerTaskGroupChatPlugin {
|
|
|
1618
2482
|
console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.messageId=${env.messageId}`);
|
|
1619
2483
|
console.error(`[DEBUG per-task-group-chat.onAfterCreate] env.sourceId=${env.sourceId}`);
|
|
1620
2484
|
}
|
|
1621
|
-
|
|
2485
|
+
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}`);
|
|
1622
2486
|
if (!this.config.autoCreateGroup) {
|
|
1623
|
-
|
|
2487
|
+
logger7.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} autoCreateGroup=false ` + `→ skip group chat creation(按需建群:用户在卡片顶部按钮触发)`);
|
|
1624
2488
|
return;
|
|
1625
2489
|
}
|
|
1626
2490
|
const replyChannel = this.getReplyChannelFromCtx(ctx);
|
|
1627
2491
|
if (!replyChannel?.chatId && this.config.requireReplyChannel) {
|
|
1628
|
-
|
|
2492
|
+
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/无)`);
|
|
1629
2493
|
return;
|
|
1630
2494
|
}
|
|
1631
2495
|
if (!replyChannel?.chatId && !this.config.requireReplyChannel) {
|
|
1632
|
-
|
|
2496
|
+
logger7.debug(`onAfterCreate[${this.pluginName}]: no replyChannel but requireReplyChannel=false, ` + `proceeding with profile fallback (fallbackProfile="${this.config.fallbackProfile}")`);
|
|
1633
2497
|
}
|
|
1634
2498
|
if (this.isSubTask(task) && !this.config.disableSubTaskSkip) {
|
|
1635
|
-
|
|
2499
|
+
logger7.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task, ` + `skip group chat creation. 关闭方式:config.disableSubTaskSkip=true`);
|
|
1636
2500
|
return;
|
|
1637
2501
|
}
|
|
1638
2502
|
if (this.isSubTask(task) && this.config.disableSubTaskSkip) {
|
|
1639
|
-
|
|
2503
|
+
logger7.debug(`onAfterCreate[${this.pluginName}]: task #${task.id} is sub-task but ` + `disableSubTaskSkip=true, proceeding to create group chat`);
|
|
1640
2504
|
}
|
|
1641
2505
|
try {
|
|
1642
2506
|
const created = await this.ensureGroupChat(task, replyChannel);
|
|
1643
2507
|
if (!created?.chatId) {
|
|
1644
|
-
|
|
2508
|
+
logger7.error(`onAfterCreate[${this.pluginName}]: ensureGroupChat 未返回 chatId for task #${task.id}`);
|
|
1645
2509
|
if (!this.config.fallbackToMain) {
|
|
1646
2510
|
throw new Error(`per-task group chat unavailable for task #${task.id}`);
|
|
1647
2511
|
}
|
|
1648
2512
|
return;
|
|
1649
2513
|
}
|
|
1650
|
-
|
|
2514
|
+
logger7.info(`onAfterCreate[${this.pluginName}]: task #${task.id} → group ${created.chatId} ` + `(initial card owned by LarkCliTaskNotifyHook)`);
|
|
1651
2515
|
} catch (error) {
|
|
1652
|
-
|
|
2516
|
+
logger7.error(`onAfterCreate[${this.pluginName}]: group chat failed for task #${task.id}: ${error}`);
|
|
1653
2517
|
if (!this.config.fallbackToMain)
|
|
1654
2518
|
throw error;
|
|
1655
2519
|
}
|
|
@@ -1657,7 +2521,7 @@ class PerTaskGroupChatPlugin {
|
|
|
1657
2521
|
async writeChatBinding(task, chatId, profile, status, errorMessage) {
|
|
1658
2522
|
if (!this.sessionComponent) {
|
|
1659
2523
|
if (isPluginDebugEnabled()) {
|
|
1660
|
-
|
|
2524
|
+
logger7.debug(`writeChatBinding[${this.pluginName}]: no sessionComponent injected, ` + `skip chat-task binding for chat ${chatId} (status=${status})`);
|
|
1661
2525
|
}
|
|
1662
2526
|
return;
|
|
1663
2527
|
}
|
|
@@ -1670,37 +2534,37 @@ class PerTaskGroupChatPlugin {
|
|
|
1670
2534
|
errorMessage: errorMessage ?? null
|
|
1671
2535
|
});
|
|
1672
2536
|
} catch (bindErr) {
|
|
1673
|
-
|
|
2537
|
+
logger7.warn(`writeChatBinding[${this.pluginName}]: failed to write chat-task binding for ` + `chat ${chatId} / task #${task.id} (status=${status}): ${bindErr}. ` + `群消息 handler 将无法自动注入 parentTaskId。`);
|
|
1674
2538
|
}
|
|
1675
2539
|
}
|
|
1676
2540
|
async onAfterUpdate(ctx) {
|
|
1677
2541
|
const data = ctx.data;
|
|
1678
2542
|
if (!data?.task) {
|
|
1679
|
-
|
|
2543
|
+
logger7.debug("onAfterUpdate: task is missing, skip");
|
|
1680
2544
|
return;
|
|
1681
2545
|
}
|
|
1682
|
-
|
|
2546
|
+
logger7.debug(`onAfterUpdate[${this.pluginName}]: task #${data.task.id} 卡片由 ` + `LarkCliTaskNotifyHook 负责(本插件不再 patch),skip`);
|
|
1683
2547
|
}
|
|
1684
2548
|
async onAfterOperationCreate(ctx) {
|
|
1685
2549
|
const data = ctx.data;
|
|
1686
2550
|
if (!data?.operation) {
|
|
1687
|
-
|
|
2551
|
+
logger7.debug("onAfterOperationCreate: operation is missing, skip");
|
|
1688
2552
|
return;
|
|
1689
2553
|
}
|
|
1690
|
-
|
|
2554
|
+
logger7.debug(`onAfterOperationCreate[${this.pluginName}]: operation #${data.operation.id} ` + `卡片由 LarkCliTaskNotifyHook 负责,skip`);
|
|
1691
2555
|
}
|
|
1692
2556
|
async onAfterComplete(ctx) {
|
|
1693
2557
|
const data = ctx.data;
|
|
1694
2558
|
if (!data?.task) {
|
|
1695
|
-
|
|
2559
|
+
logger7.debug("onAfterComplete: task is missing, skip");
|
|
1696
2560
|
return;
|
|
1697
2561
|
}
|
|
1698
|
-
|
|
2562
|
+
logger7.debug(`onAfterComplete[${this.pluginName}]: task #${data.task.id} 终态卡片由 ` + `LarkCliTaskNotifyHook 负责,skip`);
|
|
1699
2563
|
}
|
|
1700
2564
|
createGroupChat(opts) {
|
|
1701
2565
|
const args = this.buildCreateGroupChatArgs(opts);
|
|
1702
2566
|
if (isPluginDebugEnabled()) {
|
|
1703
|
-
|
|
2567
|
+
logger7.debug(`Creating group chat: lark-cli ${args.join(" ")}`);
|
|
1704
2568
|
}
|
|
1705
2569
|
return new Promise((resolve, reject) => {
|
|
1706
2570
|
const proc = spawn("lark-cli", args, {
|
|
@@ -1775,16 +2639,16 @@ class PerTaskGroupChatPlugin {
|
|
|
1775
2639
|
parsed[CTX_FIELD_CHAT_TASK_ID] = task.id;
|
|
1776
2640
|
const newContext = JSON.stringify(parsed);
|
|
1777
2641
|
if (!this.taskUpdater) {
|
|
1778
|
-
|
|
2642
|
+
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。`);
|
|
1779
2643
|
task.context = newContext;
|
|
1780
2644
|
return;
|
|
1781
2645
|
}
|
|
1782
2646
|
try {
|
|
1783
2647
|
await this.taskUpdater(task.id, newContext);
|
|
1784
2648
|
task.context = newContext;
|
|
1785
|
-
|
|
2649
|
+
logger7.debug(`attachChatIdToTask[${this.pluginName}]: ✅ persisted chat_id ${chatId} ` + `to task #${task.id} context`);
|
|
1786
2650
|
} catch (error) {
|
|
1787
|
-
|
|
2651
|
+
logger7.error(`Failed to persist chat_id to task #${task.id}: ${error}`);
|
|
1788
2652
|
task.context = newContext;
|
|
1789
2653
|
}
|
|
1790
2654
|
}
|
|
@@ -1879,15 +2743,15 @@ class PerTaskGroupChatPlugin {
|
|
|
1879
2743
|
}
|
|
1880
2744
|
resolveBotAppIds(replyChannel) {
|
|
1881
2745
|
if (this.config.botAppIds.length > 0) {
|
|
1882
|
-
|
|
2746
|
+
logger7.debug(`resolveBotAppIds[${this.pluginName}]: using config.botAppIds=${JSON.stringify(this.config.botAppIds)}`);
|
|
1883
2747
|
return this.config.botAppIds;
|
|
1884
2748
|
}
|
|
1885
2749
|
const appId = replyChannel ? replyChannel.appId : undefined;
|
|
1886
2750
|
if (appId) {
|
|
1887
|
-
|
|
2751
|
+
logger7.debug(`resolveBotAppIds[${this.pluginName}]: using replyChannel.appId=${appId} (auto-detected from event header.app_id)`);
|
|
1888
2752
|
return [appId];
|
|
1889
2753
|
}
|
|
1890
|
-
|
|
2754
|
+
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。`);
|
|
1891
2755
|
return [];
|
|
1892
2756
|
}
|
|
1893
2757
|
resolveUserIds(replyChannel) {
|
|
@@ -1897,7 +2761,7 @@ class PerTaskGroupChatPlugin {
|
|
|
1897
2761
|
if (id && id !== "sender" && id.startsWith("ou_")) {
|
|
1898
2762
|
ids.add(id);
|
|
1899
2763
|
} else if (id && id !== "sender") {
|
|
1900
|
-
|
|
2764
|
+
logger7.debug(`resolveUserIds[${this.pluginName}]: skip non-open_id in defaultInviteUserIds: "${id}"`);
|
|
1901
2765
|
}
|
|
1902
2766
|
}
|
|
1903
2767
|
}
|
|
@@ -1920,7 +2784,7 @@ class PerTaskGroupChatPlugin {
|
|
|
1920
2784
|
}
|
|
1921
2785
|
}
|
|
1922
2786
|
if (isPluginDebugEnabled()) {
|
|
1923
|
-
|
|
2787
|
+
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))}`);
|
|
1924
2788
|
}
|
|
1925
2789
|
return Array.from(ids);
|
|
1926
2790
|
}
|
|
@@ -2073,4 +2937,4 @@ __legacyDecorateClassTS([
|
|
|
2073
2937
|
function createPerTaskGroupChatPlugin(config = {}) {
|
|
2074
2938
|
return new PerTaskGroupChatPlugin(config);
|
|
2075
2939
|
}
|
|
2076
|
-
export { createAutoTaskPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
|
|
2940
|
+
export { createAutoTaskPlugin, LAYA_REUSE_THRESHOLD, LayaDecisionPlugin, createLayaDecisionPlugin, WORKFLOW_EXTRACTOR_PROMPT, formatExtractorInput, formatExtractorInputFromDescription, parseExtractorOutput, TaskTagPlugin, extractPerTaskChatId, extractPerTaskChatName, NotifyType, LarkCliTaskNotifyHook, createLarkCliTaskNotifyHook, PerTaskGroupChatPlugin, createPerTaskGroupChatPlugin };
|