@ai-setting/roy-task-plugin 0.2.0 → 0.3.0
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/cli.js +386 -16
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
2
4
|
|
|
3
5
|
// ../cli/src/args.ts
|
|
4
6
|
var DEFAULT_PROFILE = "default";
|
|
@@ -38,6 +40,7 @@ function parseServe(argv) {
|
|
|
38
40
|
dryRun: false,
|
|
39
41
|
once: false,
|
|
40
42
|
replyEnabled: true,
|
|
43
|
+
cardEnabled: true,
|
|
41
44
|
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
42
45
|
opencodeBin: "opencode"
|
|
43
46
|
};
|
|
@@ -120,6 +123,12 @@ function parseServe(argv) {
|
|
|
120
123
|
case "--reply":
|
|
121
124
|
serve.replyEnabled = true;
|
|
122
125
|
break;
|
|
126
|
+
case "--card":
|
|
127
|
+
serve.cardEnabled = true;
|
|
128
|
+
break;
|
|
129
|
+
case "--no-card":
|
|
130
|
+
serve.cardEnabled = false;
|
|
131
|
+
break;
|
|
123
132
|
default:
|
|
124
133
|
return { command: "unknown", error: `unknown flag "${flag}"` };
|
|
125
134
|
}
|
|
@@ -174,6 +183,11 @@ SERVE OPTIONS
|
|
|
174
183
|
--dry-run never spawn opencode, never reply
|
|
175
184
|
--once stop after the first event (testing / CI)
|
|
176
185
|
--reply / --no-reply send the reply back to Feishu (default: on)
|
|
186
|
+
--card / --no-card render a roy-agent progress card (default: on)
|
|
187
|
+
(one pinned card per dispatch, patched to
|
|
188
|
+
a terminal state; rendered by roy-agent's
|
|
189
|
+
own LarkCardPatchNotifier). Card failures
|
|
190
|
+
never block the text reply.
|
|
177
191
|
|
|
178
192
|
NOTES
|
|
179
193
|
• stdin is forced to EOF for \`opencode run\` (stdio[0]="ignore"). Without it
|
|
@@ -182,6 +196,9 @@ NOTES
|
|
|
182
196
|
That side is handled by roy-agent-core, which we import.
|
|
183
197
|
• Replies are sent by the daemon (not by the agent) as a single structured
|
|
184
198
|
post, so nothing is duplicated and nothing is segmented.
|
|
199
|
+
• The progress card reuses roy-agent's card renderer verbatim (card JSON
|
|
200
|
+
schema 2.0, timeline, spinner, pin/unpin). roy-task only supplies the two
|
|
201
|
+
things the renderer asks for: chatId and profile.
|
|
185
202
|
`;
|
|
186
203
|
}
|
|
187
204
|
|
|
@@ -352,7 +369,207 @@ function buildOpencodeArgs(input) {
|
|
|
352
369
|
return args;
|
|
353
370
|
}
|
|
354
371
|
|
|
372
|
+
// ../cli/src/daemon/task-card.ts
|
|
373
|
+
var CARD_OP_TIMEOUT_MS = 15000;
|
|
374
|
+
|
|
375
|
+
class CardTimeoutError extends Error {
|
|
376
|
+
constructor(op, ms) {
|
|
377
|
+
super(`card ${op} timed out after ${ms}ms`);
|
|
378
|
+
this.name = "CardTimeoutError";
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function withTimeout(op, promise, ms) {
|
|
382
|
+
if (ms <= 0)
|
|
383
|
+
return promise;
|
|
384
|
+
return new Promise((resolve, reject) => {
|
|
385
|
+
const timer = setTimeout(() => reject(new CardTimeoutError(op, ms)), ms);
|
|
386
|
+
if (typeof timer === "object" && timer && "unref" in timer)
|
|
387
|
+
timer.unref();
|
|
388
|
+
promise.then((v) => {
|
|
389
|
+
clearTimeout(timer);
|
|
390
|
+
resolve(v);
|
|
391
|
+
}, (e) => {
|
|
392
|
+
clearTimeout(timer);
|
|
393
|
+
reject(e);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function createRoyAgentCardPort(options = {}) {
|
|
398
|
+
const opTimeoutMs = options.opTimeoutMs ?? CARD_OP_TIMEOUT_MS;
|
|
399
|
+
let notifier;
|
|
400
|
+
let inFlight;
|
|
401
|
+
const loadNotifier = async () => {
|
|
402
|
+
const mod = await import("@ai-setting/roy-agent-core/env/task/plugins");
|
|
403
|
+
const hook = mod.createLarkCliTaskNotifyHook({ enabled: true, cardMode: true });
|
|
404
|
+
const maybeNotifier = hook.cardNotifier;
|
|
405
|
+
if (!maybeNotifier) {
|
|
406
|
+
throw new Error("roy-agent did not expose cardNotifier from createLarkCliTaskNotifyHook");
|
|
407
|
+
}
|
|
408
|
+
notifier = maybeNotifier;
|
|
409
|
+
return notifier;
|
|
410
|
+
};
|
|
411
|
+
const getNotifier = () => {
|
|
412
|
+
if (options.loadNotifier)
|
|
413
|
+
return options.loadNotifier();
|
|
414
|
+
if (notifier)
|
|
415
|
+
return Promise.resolve(notifier);
|
|
416
|
+
inFlight ??= loadNotifier().catch((err) => {
|
|
417
|
+
inFlight = undefined;
|
|
418
|
+
throw err;
|
|
419
|
+
});
|
|
420
|
+
return inFlight;
|
|
421
|
+
};
|
|
422
|
+
return {
|
|
423
|
+
async start(input) {
|
|
424
|
+
const n = await withTimeout("load", getNotifier(), opTimeoutMs);
|
|
425
|
+
return withTimeout("start", n.startTask({
|
|
426
|
+
chatId: input.chatId,
|
|
427
|
+
profile: input.profile,
|
|
428
|
+
meta: input.meta,
|
|
429
|
+
milestones: input.milestones
|
|
430
|
+
}), opTimeoutMs);
|
|
431
|
+
},
|
|
432
|
+
async update(input) {
|
|
433
|
+
const n = await withTimeout("load", getNotifier(), opTimeoutMs);
|
|
434
|
+
await withTimeout("update", n.updateTask({
|
|
435
|
+
messageId: input.messageId,
|
|
436
|
+
chatId: input.chatId,
|
|
437
|
+
profile: input.profile,
|
|
438
|
+
status: input.status ?? "running",
|
|
439
|
+
progress: input.progress,
|
|
440
|
+
meta: input.meta,
|
|
441
|
+
milestones: input.milestones,
|
|
442
|
+
processSteps: input.processSteps
|
|
443
|
+
}), opTimeoutMs);
|
|
444
|
+
},
|
|
445
|
+
async finish(input) {
|
|
446
|
+
const n = await withTimeout("load", getNotifier(), opTimeoutMs);
|
|
447
|
+
await withTimeout("finish", n.finishTask({
|
|
448
|
+
messageId: input.messageId,
|
|
449
|
+
chatId: input.chatId,
|
|
450
|
+
profile: input.profile,
|
|
451
|
+
status: input.status,
|
|
452
|
+
meta: input.meta,
|
|
453
|
+
finalStep: input.finalStep,
|
|
454
|
+
progress: input.progress
|
|
455
|
+
}), opTimeoutMs);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function createNoopCardPort() {
|
|
460
|
+
return {
|
|
461
|
+
async start() {
|
|
462
|
+
return { messageId: "", pinned: false };
|
|
463
|
+
},
|
|
464
|
+
async update() {},
|
|
465
|
+
async finish() {}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function buildCardIdentity(input) {
|
|
469
|
+
const maxTitle = input.maxTitleChars ?? 60;
|
|
470
|
+
const normalized = input.text.replace(/\r\n/g, `
|
|
471
|
+
`).trim();
|
|
472
|
+
const lines = normalized.split(`
|
|
473
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
474
|
+
const first = lines[0] ?? "(非文本消息)";
|
|
475
|
+
const rest = lines.slice(1).join(`
|
|
476
|
+
`);
|
|
477
|
+
const title = first.length > maxTitle ? `${first.slice(0, maxTitle - 1)}…` : first;
|
|
478
|
+
return {
|
|
479
|
+
id: `om:${input.messageId.slice(-12)}`,
|
|
480
|
+
title,
|
|
481
|
+
description: rest || first,
|
|
482
|
+
goals: "完成用户请求并把结果回复到触发消息所在会话"
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
var TOOL_LABELS = {
|
|
486
|
+
shell: "执行命令",
|
|
487
|
+
read: "读取文件",
|
|
488
|
+
write: "写入文件",
|
|
489
|
+
edit: "修改文件",
|
|
490
|
+
bash: "执行命令",
|
|
491
|
+
glob: "搜索文件",
|
|
492
|
+
grep: "搜索内容",
|
|
493
|
+
task: "调用子任务",
|
|
494
|
+
webfetch: "获取网页",
|
|
495
|
+
todowrite: "更新待办",
|
|
496
|
+
task_create: "创建任务",
|
|
497
|
+
task_update: "更新任务",
|
|
498
|
+
task_complete: "完成任务",
|
|
499
|
+
task_operation_create: "记录进展"
|
|
500
|
+
};
|
|
501
|
+
function describeToolUse(tool) {
|
|
502
|
+
return {
|
|
503
|
+
title: TOOL_LABELS[tool] ?? tool,
|
|
504
|
+
timestamp: new Date().toISOString(),
|
|
505
|
+
category: tool
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
355
509
|
// ../cli/src/daemon/dispatcher.ts
|
|
510
|
+
function cardReport(handle) {
|
|
511
|
+
const out = {};
|
|
512
|
+
if (handle.messageId)
|
|
513
|
+
out.cardMessageId = handle.messageId;
|
|
514
|
+
if (handle.error)
|
|
515
|
+
out.cardError = handle.error;
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
function errorMessage(err) {
|
|
519
|
+
return err instanceof Error ? err.message : String(err);
|
|
520
|
+
}
|
|
521
|
+
function extractUserText(raw) {
|
|
522
|
+
if (!raw)
|
|
523
|
+
return "";
|
|
524
|
+
const content = raw.content;
|
|
525
|
+
if (typeof content !== "string" || !content.trim())
|
|
526
|
+
return "";
|
|
527
|
+
let parsed;
|
|
528
|
+
try {
|
|
529
|
+
parsed = JSON.parse(content);
|
|
530
|
+
} catch {
|
|
531
|
+
if (/^\s*[{[]/.test(content))
|
|
532
|
+
return "";
|
|
533
|
+
return content;
|
|
534
|
+
}
|
|
535
|
+
if (parsed && typeof parsed === "object") {
|
|
536
|
+
const obj = parsed;
|
|
537
|
+
if (typeof obj.text === "string")
|
|
538
|
+
return obj.text;
|
|
539
|
+
const rich = extractRichPostText(obj);
|
|
540
|
+
if (rich)
|
|
541
|
+
return rich;
|
|
542
|
+
return "";
|
|
543
|
+
}
|
|
544
|
+
return typeof parsed === "string" ? parsed : "";
|
|
545
|
+
}
|
|
546
|
+
function extractRichPostText(obj) {
|
|
547
|
+
const locale = obj.zh_cn ?? obj.en_us ?? Object.values(obj).find((v) => !!v && typeof v === "object");
|
|
548
|
+
if (!locale || typeof locale !== "object")
|
|
549
|
+
return "";
|
|
550
|
+
const content = locale.content;
|
|
551
|
+
if (!Array.isArray(content)) {
|
|
552
|
+
const title = locale.title;
|
|
553
|
+
return typeof title === "string" ? title : "";
|
|
554
|
+
}
|
|
555
|
+
const paragraphs = [];
|
|
556
|
+
for (const paragraph of content) {
|
|
557
|
+
if (!Array.isArray(paragraph))
|
|
558
|
+
continue;
|
|
559
|
+
const line = paragraph.map((node) => {
|
|
560
|
+
if (node && typeof node === "object" && "text" in node) {
|
|
561
|
+
const t = node.text;
|
|
562
|
+
return typeof t === "string" ? t : "";
|
|
563
|
+
}
|
|
564
|
+
return "";
|
|
565
|
+
}).join("").trim();
|
|
566
|
+
if (line)
|
|
567
|
+
paragraphs.push(line);
|
|
568
|
+
}
|
|
569
|
+
return paragraphs.join(`
|
|
570
|
+
`).trim();
|
|
571
|
+
}
|
|
572
|
+
|
|
356
573
|
class Dispatcher {
|
|
357
574
|
deps;
|
|
358
575
|
opts;
|
|
@@ -392,19 +609,52 @@ class Dispatcher {
|
|
|
392
609
|
return { status: "dry-run", plan };
|
|
393
610
|
}
|
|
394
611
|
const run = async () => {
|
|
612
|
+
const cardPromise = this.openCard({ chatId, messageId, profile, raw });
|
|
395
613
|
const result = await this.deps.runAgent({
|
|
396
614
|
prompt: plan.prompt,
|
|
397
615
|
agent: plan.agent,
|
|
398
616
|
sessionId: plan.sessionId
|
|
399
617
|
});
|
|
618
|
+
const card = await cardPromise;
|
|
619
|
+
if (card.messageId) {
|
|
620
|
+
await this.patchCard(card, {
|
|
621
|
+
chatId,
|
|
622
|
+
profile,
|
|
623
|
+
tools: result.tools,
|
|
624
|
+
status: result.error ? "failed" : "running"
|
|
625
|
+
});
|
|
626
|
+
}
|
|
400
627
|
if (result.error) {
|
|
401
|
-
|
|
628
|
+
await this.closeCard(card, {
|
|
629
|
+
chatId,
|
|
630
|
+
profile,
|
|
631
|
+
status: "failed",
|
|
632
|
+
finalStep: `agent 报错:${result.error}`
|
|
633
|
+
});
|
|
634
|
+
return { status: "agent-error", plan, error: result.error, ...cardReport(card) };
|
|
402
635
|
}
|
|
403
636
|
if (!result.text.trim()) {
|
|
404
|
-
|
|
637
|
+
await this.closeCard(card, {
|
|
638
|
+
chatId,
|
|
639
|
+
profile,
|
|
640
|
+
status: "failed",
|
|
641
|
+
finalStep: "agent 未产出可用文本,未回复"
|
|
642
|
+
});
|
|
643
|
+
return {
|
|
644
|
+
status: "no-output",
|
|
645
|
+
plan,
|
|
646
|
+
toolCalls: result.toolCalls ?? 0,
|
|
647
|
+
...cardReport(card)
|
|
648
|
+
};
|
|
405
649
|
}
|
|
406
650
|
if (this.opts.replyEnabled === false) {
|
|
407
|
-
|
|
651
|
+
await this.closeCard(card, {
|
|
652
|
+
chatId,
|
|
653
|
+
profile,
|
|
654
|
+
status: "completed",
|
|
655
|
+
finalStep: "--no-reply:agent 已完成,未发送回复"
|
|
656
|
+
});
|
|
657
|
+
return { status: "no-reply", plan, text: result.text, ...cardReport(card) };
|
|
408
658
|
}
|
|
409
659
|
const sent = await this.deps.reply({
|
|
410
660
|
messageId,
|
|
@@ -414,12 +664,106 @@ class Dispatcher {
|
|
|
414
664
|
atOpenId: senderOpenId
|
|
415
665
|
});
|
|
416
666
|
if (!sent.ok) {
|
|
417
|
-
|
|
667
|
+
await this.closeCard(card, {
|
|
668
|
+
chatId,
|
|
669
|
+
profile,
|
|
670
|
+
status: "failed",
|
|
671
|
+
finalStep: `回复发送失败:${sent.error ?? "unknown"}`
|
|
672
|
+
});
|
|
673
|
+
return {
|
|
674
|
+
status: "reply-failed",
|
|
675
|
+
plan,
|
|
676
|
+
error: sent.error ?? "unknown",
|
|
677
|
+
text: result.text,
|
|
678
|
+
...cardReport(card)
|
|
679
|
+
};
|
|
418
680
|
}
|
|
419
|
-
|
|
681
|
+
await this.closeCard(card, {
|
|
682
|
+
chatId,
|
|
683
|
+
profile,
|
|
684
|
+
status: "completed",
|
|
685
|
+
finalStep: "已回复到原消息"
|
|
686
|
+
});
|
|
687
|
+
return {
|
|
688
|
+
status: "dispatched",
|
|
689
|
+
plan,
|
|
690
|
+
replyMessageId: sent.messageId,
|
|
691
|
+
text: result.text,
|
|
692
|
+
...cardReport(card)
|
|
693
|
+
};
|
|
420
694
|
};
|
|
421
695
|
return this.deps.gate ? this.deps.gate.run(plan.sessionId, run) : run();
|
|
422
696
|
}
|
|
697
|
+
async openCard(input) {
|
|
698
|
+
const handle = { messageId: "" };
|
|
699
|
+
if (!this.deps.card)
|
|
700
|
+
return handle;
|
|
701
|
+
try {
|
|
702
|
+
const identity = buildCardIdentity({
|
|
703
|
+
messageId: input.messageId,
|
|
704
|
+
text: extractUserText(input.raw)
|
|
705
|
+
});
|
|
706
|
+
const started = await this.deps.card.start({
|
|
707
|
+
chatId: input.chatId,
|
|
708
|
+
profile: input.profile,
|
|
709
|
+
meta: {
|
|
710
|
+
id: identity.id,
|
|
711
|
+
title: identity.title,
|
|
712
|
+
description: identity.description,
|
|
713
|
+
goals: identity.goals,
|
|
714
|
+
type: "normal",
|
|
715
|
+
priority: "high",
|
|
716
|
+
currentStatus: "已收到消息,正在处理"
|
|
717
|
+
},
|
|
718
|
+
milestones: [
|
|
719
|
+
{
|
|
720
|
+
title: "收到消息",
|
|
721
|
+
timestamp: new Date().toISOString(),
|
|
722
|
+
description: `来自飞书消息 ${input.messageId}`
|
|
723
|
+
}
|
|
724
|
+
]
|
|
725
|
+
});
|
|
726
|
+
handle.messageId = started.messageId;
|
|
727
|
+
handle.pinned = started.pinned;
|
|
728
|
+
} catch (err) {
|
|
729
|
+
handle.error = errorMessage(err);
|
|
730
|
+
}
|
|
731
|
+
return handle;
|
|
732
|
+
}
|
|
733
|
+
async patchCard(handle, input) {
|
|
734
|
+
if (!this.deps.card || !handle.messageId || handle.error)
|
|
735
|
+
return;
|
|
736
|
+
const tools = input.tools ?? [];
|
|
737
|
+
if (tools.length === 0)
|
|
738
|
+
return;
|
|
739
|
+
try {
|
|
740
|
+
await this.deps.card.update({
|
|
741
|
+
messageId: handle.messageId,
|
|
742
|
+
chatId: input.chatId,
|
|
743
|
+
profile: input.profile,
|
|
744
|
+
status: input.status,
|
|
745
|
+
processSteps: tools.map(describeToolUse)
|
|
746
|
+
});
|
|
747
|
+
} catch (err) {
|
|
748
|
+
handle.error ??= errorMessage(err);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
async closeCard(handle, input) {
|
|
752
|
+
if (!this.deps.card || !handle.messageId || handle.error)
|
|
753
|
+
return;
|
|
754
|
+
try {
|
|
755
|
+
await this.deps.card.finish({
|
|
756
|
+
messageId: handle.messageId,
|
|
757
|
+
chatId: input.chatId,
|
|
758
|
+
profile: input.profile,
|
|
759
|
+
status: input.status,
|
|
760
|
+
finalStep: input.finalStep,
|
|
761
|
+
progress: input.status === "completed" ? 100 : undefined
|
|
762
|
+
});
|
|
763
|
+
} catch (err) {
|
|
764
|
+
handle.error ??= errorMessage(err);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
423
767
|
buildPlan(input) {
|
|
424
768
|
const derive = this.opts.sessionIdFor ?? deriveSessionId;
|
|
425
769
|
return {
|
|
@@ -486,6 +830,7 @@ class NdjsonAssembler {
|
|
|
486
830
|
error;
|
|
487
831
|
toolCalls = 0;
|
|
488
832
|
sessionId;
|
|
833
|
+
tools = [];
|
|
489
834
|
pushChunk(chunk) {
|
|
490
835
|
this.tail += chunk;
|
|
491
836
|
let idx = this.tail.indexOf(`
|
|
@@ -522,6 +867,10 @@ class NdjsonAssembler {
|
|
|
522
867
|
}
|
|
523
868
|
if (parsed.type === "tool_use") {
|
|
524
869
|
this.toolCalls += 1;
|
|
870
|
+
const name = parsed.part?.tool ?? parsed.tool;
|
|
871
|
+
if (typeof name === "string" && name) {
|
|
872
|
+
this.tools.push(name);
|
|
873
|
+
}
|
|
525
874
|
return;
|
|
526
875
|
}
|
|
527
876
|
if (parsed.type === "error") {
|
|
@@ -540,7 +889,8 @@ class NdjsonAssembler {
|
|
|
540
889
|
text,
|
|
541
890
|
error: this.error,
|
|
542
891
|
toolCalls: this.toolCalls,
|
|
543
|
-
sessionId: this.sessionId
|
|
892
|
+
sessionId: this.sessionId,
|
|
893
|
+
tools: this.tools
|
|
544
894
|
};
|
|
545
895
|
}
|
|
546
896
|
}
|
|
@@ -711,23 +1061,32 @@ function makeOpencodeRunner(opts = {}) {
|
|
|
711
1061
|
text: result.text,
|
|
712
1062
|
error: `opencode timed out after ${timeoutMs}ms`,
|
|
713
1063
|
sessionId: result.sessionId,
|
|
714
|
-
toolCalls: result.toolCalls
|
|
1064
|
+
toolCalls: result.toolCalls,
|
|
1065
|
+
tools: result.tools
|
|
715
1066
|
};
|
|
716
1067
|
}
|
|
717
1068
|
if (result.error) {
|
|
718
|
-
return {
|
|
1069
|
+
return {
|
|
1070
|
+
text: result.text,
|
|
1071
|
+
error: result.error,
|
|
1072
|
+
sessionId: result.sessionId,
|
|
1073
|
+
toolCalls: result.toolCalls,
|
|
1074
|
+
tools: result.tools
|
|
1075
|
+
};
|
|
719
1076
|
}
|
|
720
1077
|
if (code !== 0 && !result.text) {
|
|
721
1078
|
return {
|
|
722
1079
|
text: "",
|
|
723
1080
|
error: `opencode exited ${code}: ${(stderr || "no stderr").trim().slice(0, 1000)}`,
|
|
724
|
-
toolCalls: result.toolCalls
|
|
1081
|
+
toolCalls: result.toolCalls,
|
|
1082
|
+
tools: result.tools
|
|
725
1083
|
};
|
|
726
1084
|
}
|
|
727
1085
|
return {
|
|
728
1086
|
text: result.text,
|
|
729
1087
|
sessionId: result.sessionId,
|
|
730
|
-
toolCalls: result.toolCalls
|
|
1088
|
+
toolCalls: result.toolCalls,
|
|
1089
|
+
tools: result.tools
|
|
731
1090
|
};
|
|
732
1091
|
};
|
|
733
1092
|
}
|
|
@@ -827,11 +1186,13 @@ async function startServe(options, deps) {
|
|
|
827
1186
|
const log = deps.log ?? ((line) => console.log(line));
|
|
828
1187
|
const gate = new ConcurrencyGate({ maxConcurrency: options.maxConcurrency });
|
|
829
1188
|
const deduper = new Deduper;
|
|
1189
|
+
const card = deps.card ?? (options.cardEnabled && !options.dryRun ? createRoyAgentCardPort() : createNoopCardPort());
|
|
830
1190
|
const dispatcher = new Dispatcher({
|
|
831
1191
|
runAgent: deps.runAgent ?? makeOpencodeRunner({ bin: options.opencodeBin, timeoutMs: options.timeoutMs }),
|
|
832
1192
|
reply: deps.reply ?? defaultReplyRunner,
|
|
833
1193
|
gate,
|
|
834
|
-
deduper
|
|
1194
|
+
deduper,
|
|
1195
|
+
card
|
|
835
1196
|
}, {
|
|
836
1197
|
dryRun: options.dryRun,
|
|
837
1198
|
replyEnabled: options.replyEnabled,
|
|
@@ -907,6 +1268,7 @@ function formatBanner(options, commands, maxConcurrency) {
|
|
|
907
1268
|
` opencode bin : ${options.opencodeBin}`,
|
|
908
1269
|
` max concurrency : ${maxConcurrency}`,
|
|
909
1270
|
` reply enabled : ${options.replyEnabled}`,
|
|
1271
|
+
` progress cards : ${options.cardEnabled && !options.dryRun}`,
|
|
910
1272
|
` dry run : ${options.dryRun}`,
|
|
911
1273
|
` once : ${options.once}`,
|
|
912
1274
|
` timeout : ${options.timeoutMs}ms`,
|
|
@@ -917,22 +1279,30 @@ function formatBanner(options, commands, maxConcurrency) {
|
|
|
917
1279
|
return lines.join(`
|
|
918
1280
|
`);
|
|
919
1281
|
}
|
|
1282
|
+
function cardSuffix(result) {
|
|
1283
|
+
if (!("cardMessageId" in result) && !("cardError" in result))
|
|
1284
|
+
return "";
|
|
1285
|
+
if (result.cardError)
|
|
1286
|
+
return ` card=FAILED(${result.cardError})`;
|
|
1287
|
+
return ` card=${result.cardMessageId ?? "(none)"}`;
|
|
1288
|
+
}
|
|
920
1289
|
function formatResult(label, result) {
|
|
1290
|
+
const card = cardSuffix(result);
|
|
921
1291
|
switch (result.status) {
|
|
922
1292
|
case "dispatched":
|
|
923
|
-
return `[roy-task] ${label} dispatched chat=${result.plan.chatId} session=${result.plan.sessionId} ` + `reply=${result.replyMessageId ?? "(no id)"} chars=${result.text.length}`;
|
|
1293
|
+
return `[roy-task] ${label} dispatched chat=${result.plan.chatId} session=${result.plan.sessionId} ` + `reply=${result.replyMessageId ?? "(no id)"} chars=${result.text.length}${card}`;
|
|
924
1294
|
case "dry-run":
|
|
925
1295
|
return `[roy-task] ${label} DRY-RUN would run agent=${result.plan.agent} ` + `session=${result.plan.sessionId} chat=${result.plan.chatId} prompt=${result.plan.prompt.length}chars`;
|
|
926
1296
|
case "duplicate":
|
|
927
1297
|
return `[roy-task] ${label} duplicate dropped key=${result.key ?? "(unknown)"}`;
|
|
928
1298
|
case "no-output":
|
|
929
|
-
return `[roy-task] ${label} no-output (agent produced no text; not replying) toolCalls=${result.toolCalls}`;
|
|
1299
|
+
return `[roy-task] ${label} no-output (agent produced no text; not replying) ` + `toolCalls=${result.toolCalls}${card}`;
|
|
930
1300
|
case "no-reply":
|
|
931
|
-
return `[roy-task] ${label} no-reply (reply disabled) chars=${result.text.length}`;
|
|
1301
|
+
return `[roy-task] ${label} no-reply (reply disabled) chars=${result.text.length}${card}`;
|
|
932
1302
|
case "agent-error":
|
|
933
|
-
return `[roy-task] ${label} agent-error: ${result.error}`;
|
|
1303
|
+
return `[roy-task] ${label} agent-error: ${result.error}${card}`;
|
|
934
1304
|
case "reply-failed":
|
|
935
|
-
return `[roy-task] ${label} reply-failed: ${result.error}`;
|
|
1305
|
+
return `[roy-task] ${label} reply-failed: ${result.error}${card}`;
|
|
936
1306
|
case "no-target":
|
|
937
1307
|
return `[roy-task] ${label} skipped: ${result.reason}`;
|
|
938
1308
|
}
|