@ai-setting/roy-task-plugin 0.7.11 → 0.7.13
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/claude-plugin/agents/roy.md +84 -8
- package/dist/claude-plugin/mcp/bridge.mjs +27 -8
- package/dist/claude-plugin/mcp/goal-check.mjs +452 -0
- package/dist/cli.js +212 -7
- package/dist/index.js +485 -243
- package/dist/index.js.map +14 -13
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -456,6 +456,113 @@ import {
|
|
|
456
456
|
TASK_DISCIPLINE_LIFECYCLE,
|
|
457
457
|
TASK_DISCIPLINE_OPERATION
|
|
458
458
|
} from "@ai-setting/roy-agent-core/env/task/prompt";
|
|
459
|
+
|
|
460
|
+
// ../agent/src/trim-permission.ts
|
|
461
|
+
var ROLE_SCOPE_QUALIFIER = /验收方|收尾分两种角色/;
|
|
462
|
+
var TRIM_RULES = Object.freeze([
|
|
463
|
+
{
|
|
464
|
+
name: "complete-禁止调用",
|
|
465
|
+
match: (line) => /(不要|禁止|别|无需|不需要|不能|无权|不得|仍无|不持有|不具备|未获得)/.test(line) && /task_complete/.test(line)
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: "角色权限断言",
|
|
469
|
+
match: (line) => /(执行方|验收方)/.test(line) && /(权限|资格|无权|不能|没有)/.test(line) && /task_complete/.test(line) && !/你的收尾动作是/.test(line)
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "track-禁止置终态",
|
|
473
|
+
match: (line) => /终态由验收方落/.test(line) || /不要/.test(line) && /置为/.test(line) && /completed/.test(line)
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: "lifecycle-角色二分标题",
|
|
477
|
+
match: (line) => /收尾分两种角色/.test(line),
|
|
478
|
+
replace: () => "6. COMPLETE — 收尾:用 `task_complete` after item-by-item verification" + " (see Complete Discipline below)"
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
name: "lifecycle-执行方条目",
|
|
482
|
+
match: (line) => /执行方/.test(line) && /(roy )?sub-agent/.test(line)
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: "lifecycle-执行方收尾指令",
|
|
486
|
+
match: (line, ctx) => (/结束并返回结果|结束并返回/.test(line) || /最后一条/.test(line) && /task_operation_create/.test(line)) && (/执行方/.test(line) || /执行方/.test(ctx.prevLine ?? ""))
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: "lifecycle-验收方条目",
|
|
490
|
+
match: (line) => /验收方/.test(line) && /(default agent|接待员)/.test(line)
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: "complete-标题角色限定",
|
|
494
|
+
match: (line) => /^#{1,6}\s/.test(line) && /验收方/.test(line),
|
|
495
|
+
replace: (line) => line.replace(/\s*[((][^))]*验收方[^))]*[))]/g, "")
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: "complete-角色作用域引用块",
|
|
499
|
+
match: (line) => /角色作用域/.test(line) || /若你是/.test(line) && /执行方/.test(line) && /权限/.test(line) || /你的收尾动作是/.test(line) && /验收由主智能体负责/.test(line)
|
|
500
|
+
}
|
|
501
|
+
]);
|
|
502
|
+
function isListItemHead(line) {
|
|
503
|
+
return /^\s*([-*+]\s|\d+\.\s)/.test(line);
|
|
504
|
+
}
|
|
505
|
+
function isContinuationOf(line, head) {
|
|
506
|
+
if (isListItemHead(line))
|
|
507
|
+
return false;
|
|
508
|
+
if (/^\s*(#{1,6}\s|>)/.test(line))
|
|
509
|
+
return false;
|
|
510
|
+
if (/^\s*```/.test(line))
|
|
511
|
+
return false;
|
|
512
|
+
if (line.trim() === "")
|
|
513
|
+
return false;
|
|
514
|
+
const indent = (s) => s.match(/^\s*/)?.[0].length ?? 0;
|
|
515
|
+
return indent(line) > indent(head);
|
|
516
|
+
}
|
|
517
|
+
function trimTaskCompletePermissionText(text) {
|
|
518
|
+
if (!text)
|
|
519
|
+
return text;
|
|
520
|
+
const lines = text.split(`
|
|
521
|
+
`);
|
|
522
|
+
const out = [];
|
|
523
|
+
for (let i = 0;i < lines.length; i++) {
|
|
524
|
+
const line = lines[i];
|
|
525
|
+
const rule = TRIM_RULES.find((r) => r.match(line, { prevLine: lines[i - 1] }));
|
|
526
|
+
if (!rule) {
|
|
527
|
+
out.push(line);
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (rule.replace) {
|
|
531
|
+
const replaced = rule.replace(line);
|
|
532
|
+
if (ROLE_SCOPE_QUALIFIER.test(replaced)) {
|
|
533
|
+
throw new Error(`trim-permission: rule "${rule.name}" matched the line but its replace() ` + `left the role qualifier in place — core's wording drifted and this ` + `trim rule must be updated.
|
|
534
|
+
line: ${line}`);
|
|
535
|
+
}
|
|
536
|
+
out.push(replaced);
|
|
537
|
+
}
|
|
538
|
+
if (!rule.replace && isListItemHead(line)) {
|
|
539
|
+
while (i + 1 < lines.length && isContinuationOf(lines[i + 1], line)) {
|
|
540
|
+
i++;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const collapsed = [];
|
|
545
|
+
let inFence = false;
|
|
546
|
+
for (const line of out) {
|
|
547
|
+
if (/^\s*```/.test(line)) {
|
|
548
|
+
inFence = !inFence;
|
|
549
|
+
collapsed.push(line);
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (!inFence && line.trim() === "" && collapsed.length > 0 && collapsed[collapsed.length - 1].trim() === "") {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
collapsed.push(line);
|
|
556
|
+
}
|
|
557
|
+
return collapsed.join(`
|
|
558
|
+
`);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ../agent/src/roy-prompt-core.ts
|
|
562
|
+
var TRIMMED_LIFECYCLE = trimTaskCompletePermissionText(TASK_DISCIPLINE_LIFECYCLE);
|
|
563
|
+
var TRIMMED_GOALS = trimTaskCompletePermissionText(TASK_DISCIPLINE_GOALS);
|
|
564
|
+
var TRIMMED_OPERATION = trimTaskCompletePermissionText(TASK_DISCIPLINE_OPERATION);
|
|
565
|
+
var TRIMMED_COMPLETE = trimTaskCompletePermissionText(TASK_DISCIPLINE_COMPLETE);
|
|
459
566
|
var ROY_PROMPT_COMMON = `# Roy — task-first-class-citizen agent
|
|
460
567
|
|
|
461
568
|
You are **Roy**, an agent whose defining rule is simple:
|
|
@@ -465,16 +572,16 @@ You are **Roy**, an agent whose defining rule is simple:
|
|
|
465
572
|
You are a worker agent: you act through the tools, and your output is what
|
|
466
573
|
the user sees.
|
|
467
574
|
|
|
468
|
-
${
|
|
575
|
+
${TRIMMED_LIFECYCLE}
|
|
469
576
|
|
|
470
|
-
${
|
|
577
|
+
${TRIMMED_GOALS}
|
|
471
578
|
|
|
472
579
|
The tool returns a **numeric task id** — that number is your handle for the
|
|
473
580
|
rest of the conversation.
|
|
474
581
|
|
|
475
|
-
${
|
|
582
|
+
${TRIMMED_OPERATION}
|
|
476
583
|
|
|
477
|
-
${
|
|
584
|
+
${TRIMMED_COMPLETE}
|
|
478
585
|
|
|
479
586
|
#### Several deliverables in one field
|
|
480
587
|
\`deliverable_storage_path\` may name **multiple paths**, space-separated
|
|
@@ -510,7 +617,78 @@ if it is not in the task store, it is not tracked.
|
|
|
510
617
|
- Report the task id and its state whenever you finish a step.
|
|
511
618
|
- If a task is blocked, mark it (\`task_update(status="paused")\`) and record a
|
|
512
619
|
\`problem\` milestone explaining the blocker instead of going silent.`;
|
|
620
|
+
var ROY_PROMPT_CLAUDE_DISCIPLINE = `
|
|
621
|
+
## Milestone discipline (Claude runtime)
|
|
622
|
+
|
|
623
|
+
The task card renders **your** milestones. A run that calls tools but
|
|
624
|
+
records nothing leaves the card empty, and the user sees a failed task with
|
|
625
|
+
no trace of the work. Measured failure: 67 tool calls, 2 automatic hooks,
|
|
626
|
+
0 real milestones, then the run was killed and the card froze in a failed
|
|
627
|
+
state. Read this section as binding.
|
|
628
|
+
|
|
629
|
+
### Record per stage, immediately
|
|
630
|
+
|
|
631
|
+
- Every time you finish a **阶段** — a sub-goal, an investigation, a file
|
|
632
|
+
written, a test that passed, a decision, a blocker — call
|
|
633
|
+
\`task_operation_create\` **立即**, in that same turn.
|
|
634
|
+
- Do **not** batch. A milestone written after six tool calls reads as
|
|
635
|
+
retrospective filler; the point is that the card shows the work *as it
|
|
636
|
+
happens*.
|
|
637
|
+
- If a single tool call produced a stage-level result, that call is
|
|
638
|
+
followed by exactly one milestone — no more, no less.
|
|
639
|
+
- Long-running work (> 5 minutes) still records as it goes, exactly as the
|
|
640
|
+
shared contract requires.
|
|
641
|
+
|
|
642
|
+
### The shape of a milestone
|
|
643
|
+
|
|
644
|
+
\`milestone_title\` — a **短语**, ≤ 20 字, verb-first, naming what was
|
|
645
|
+
locked down. Not a sentence, not a label like "progress".
|
|
646
|
+
|
|
647
|
+
- ✅ \`泄漏源锁定\`, \`测试全绿\`, \`缓存层落地\`, \`回滚路径确认\`
|
|
648
|
+
- ❌ \`进度更新\`, \`继续处理\`, \`做了一些工作\`, \`阶段完成\`
|
|
649
|
+
|
|
650
|
+
Those ❌ examples are **空壳** milestones: they carry no information about
|
|
651
|
+
what changed, so they are worse than useless — they make the card look
|
|
652
|
+
busy while hiding the work. Never write a title like those.
|
|
653
|
+
|
|
654
|
+
\`milestone_description\` — a dense **技术叙述** of the state you reached.
|
|
655
|
+
Name the concrete things, so a reader can reconstruct the work without
|
|
656
|
+
re-running it:
|
|
657
|
+
|
|
658
|
+
- the **文件路径** you wrote or read
|
|
659
|
+
- the **函数名** / symbol you changed or traced
|
|
660
|
+
- the **命令** you ran and its result
|
|
661
|
+
- the numbers: pass/fail counts, line counts, exit codes, sizes, timings
|
|
662
|
+
|
|
663
|
+
Example (title + description):
|
|
664
|
+
|
|
665
|
+
\`\`\`
|
|
666
|
+
milestone_title: "泄漏源锁定"
|
|
667
|
+
milestone_description: "test_workflow_error_handling 的生成器被放弃 → registry 由 GC 延迟回收;泄漏路径锁定在 WorkflowRegistry.dispose(),packages/core/src/registry.ts:412"
|
|
668
|
+
\`\`\`
|
|
669
|
+
|
|
670
|
+
\`process_description\` (100–500 字符) — how it was reached (**如何达到**):
|
|
671
|
+
the strategy and the tool steps. It must **not** repeat the description —
|
|
672
|
+
the description is the *state*, the process is the *route*. Repeating
|
|
673
|
+
one in the other is a second ❌.
|
|
674
|
+
|
|
675
|
+
### Tool-call budget
|
|
676
|
+
|
|
677
|
+
If you have made several **连续的工具调用** without recording a
|
|
678
|
+
milestone, the next action is \`task_operation_create\` — not another
|
|
679
|
+
tool call. Two, three tool calls in a row is normal exploration; a long
|
|
680
|
+
streak without a milestone is the failure mode measured above. When the
|
|
681
|
+
streak is getting long, write down what you have already established, then
|
|
682
|
+
continue.
|
|
683
|
+
|
|
684
|
+
### What good looks like
|
|
685
|
+
|
|
686
|
+
For a task with 3–4 stages you should end up with **3–4 hand-written
|
|
687
|
+
milestones** in the card — not 2 automatic hooks and nothing else. The
|
|
688
|
+
automatic \`收到消息\` / \`Task 完成\` hooks are not a substitute for yours.
|
|
689
|
+
`;
|
|
513
690
|
var ROY_PROMPT_CLAUDE_TAIL = `
|
|
691
|
+
${ROY_PROMPT_CLAUDE_DISCIPLINE}
|
|
514
692
|
## Tool surface on Claude Code
|
|
515
693
|
|
|
516
694
|
The \`task_*\` tools are served by the **\`roy-task\` MCP server** this runtime
|
|
@@ -1084,6 +1262,8 @@ function errMsg(err) {
|
|
|
1084
1262
|
|
|
1085
1263
|
// ../../../packages/cli/src/daemon/discipline.ts
|
|
1086
1264
|
import { TASK_DISCIPLINE_PROMPT } from "@ai-setting/roy-agent-core/env/task/prompt";
|
|
1265
|
+
var CORE_TASK_DISCIPLINE_PROMPT_RAW = TASK_DISCIPLINE_PROMPT;
|
|
1266
|
+
var PLUGIN_TASK_DISCIPLINE_PROMPT = trimTaskCompletePermissionText(CORE_TASK_DISCIPLINE_PROMPT_RAW);
|
|
1087
1267
|
var RUNTIMES_WITH_AGENT_SYSTEM_PROMPT = Object.freeze([
|
|
1088
1268
|
"opencode",
|
|
1089
1269
|
"claude"
|
|
@@ -1152,7 +1332,7 @@ function buildCodexArgs(input) {
|
|
|
1152
1332
|
return args;
|
|
1153
1333
|
}
|
|
1154
1334
|
function withDiscipline(prompt, systemPrompt) {
|
|
1155
|
-
const contract = systemPrompt === undefined ?
|
|
1335
|
+
const contract = systemPrompt === undefined ? PLUGIN_TASK_DISCIPLINE_PROMPT : systemPrompt;
|
|
1156
1336
|
if (!contract)
|
|
1157
1337
|
return prompt;
|
|
1158
1338
|
if (prompt.includes(contract))
|
|
@@ -1692,7 +1872,7 @@ function buildDispatchPrompt(input) {
|
|
|
1692
1872
|
""
|
|
1693
1873
|
];
|
|
1694
1874
|
if (needsDispatchInjection(input.runtime)) {
|
|
1695
|
-
parts.push("## 任务纪律(Task #4889 — 来自 roy-agent core 单一真相源)", "",
|
|
1875
|
+
parts.push("## 任务纪律(Task #4889 — 来自 roy-agent core 单一真相源)", "", PLUGIN_TASK_DISCIPLINE_PROMPT, "");
|
|
1696
1876
|
}
|
|
1697
1877
|
parts.push("## 硬性规则", "", "1. **不要**自己调用 `lark-cli` 发送回复。守护进程会在你结束后,把你的", " **最终回答文本**用 `lark-cli im +messages-reply` 一次性发回飞书。", " 你自己发会造成重复消息。", "2. 把**最终回答**写成给发件人看的**普通散文 / Markdown**。", " 不要输出 JSON 包裹、不要输出“我调用了某某工具”的过程流水账。", " 这段文本会**原样**成为飞书里的回复正文。", "3. 需要落任务就用 `task_*` 工具(task_create / task_update / …),", " 这是你的主要职责;不要只在回答里“描述”你打算做什么。", "", "## 什么时候该建任务(Task #4433)", "", "任务由**你**决定建不建,守护进程不会替你建。判断标准:", "", "- **该建**:需要多步操作、要动文件/跑命令、要调研并产出结论、", " 耗时预计超过一两分钟、或用户明确要求「跟踪 / 建个任务 / 后续跟进」。", " → 用 `task_create` 建任务,**然后**再开始干活(先立任务,再动手)。", "- **不必建**:寒暄、谢谢、纯问答(「今天几号」「这个参数叫啥」)、", " 一两句话就能答完且不需要任何工具的问题。", " → 直接回答,不要为了「显得勤快」而建一个空任务。", "- **建完要更新**:动手过程中用 `task_update` 推进度 / 改 current_status,", " 干完用 `task_complete` 收尾。建了任务却从不更新,比不建更糟——", " 看板上会留下一个卡住的任务。", "", "⚠️ **卡片与任务一一对应**:只有你建了任务,用户才会收到一张进展卡片;", "没建任务就只有文字回复。所以**不要**为了「想要一张卡片」而去建任务,", "也不要建了任务又不用它——卡片上显示的就是你建的那个任务的真 id 和标题。", "", "## 环境事实(必须使用下面这些值,不要自己猜)", "", `- lark-cli profile:\`${profile}\`(飞书 openId 是 per-app 隔离的,换 profile 会失败)`, `- chat_id:\`${chatId}\``, `- message_id:\`${messageId}\``, `- event_id:\`${eventId}\``, "", "## 原始事件(完整 JSON)", "", "```json", JSON.stringify(input.rawEvent, null, 2), "```");
|
|
1698
1878
|
if (input.recommendedAction) {
|
|
@@ -1710,6 +1890,25 @@ function buildOpencodeArgs(input) {
|
|
|
1710
1890
|
}
|
|
1711
1891
|
|
|
1712
1892
|
// ../../../packages/cli/src/daemon/task-card.ts
|
|
1893
|
+
async function hydrateCardTimeline(notifier, input) {
|
|
1894
|
+
try {
|
|
1895
|
+
if (typeof notifier.hydrateState !== "function")
|
|
1896
|
+
return;
|
|
1897
|
+
const taskId = taskIdOf(input.meta);
|
|
1898
|
+
if (taskId === undefined)
|
|
1899
|
+
return;
|
|
1900
|
+
const store = await loadLedger();
|
|
1901
|
+
if (!store)
|
|
1902
|
+
return;
|
|
1903
|
+
const record = store.get(taskId);
|
|
1904
|
+
if (!record?.messageId || !record.timeline)
|
|
1905
|
+
return;
|
|
1906
|
+
if (record.messageId !== input.messageId)
|
|
1907
|
+
return;
|
|
1908
|
+
const snapshot = JSON.parse(record.timeline);
|
|
1909
|
+
notifier.hydrateState(record.messageId, snapshot);
|
|
1910
|
+
} catch {}
|
|
1911
|
+
}
|
|
1713
1912
|
var CARD_OP_TIMEOUT_MS = 15000;
|
|
1714
1913
|
|
|
1715
1914
|
class CardTimeoutError extends Error {
|
|
@@ -1735,11 +1934,14 @@ function withTimeout(op, promise, ms) {
|
|
|
1735
1934
|
});
|
|
1736
1935
|
}
|
|
1737
1936
|
var ledgerStores = new Map;
|
|
1937
|
+
var ledgerOverride;
|
|
1738
1938
|
var ledgerInFlight;
|
|
1739
1939
|
function ledgerPathKey() {
|
|
1740
1940
|
return process.env.ROY_AGENT_LARK_CARD_DB ?? "";
|
|
1741
1941
|
}
|
|
1742
1942
|
async function loadLedger() {
|
|
1943
|
+
if (ledgerOverride)
|
|
1944
|
+
return ledgerOverride();
|
|
1743
1945
|
const key = ledgerPathKey();
|
|
1744
1946
|
if (ledgerStores.has(key))
|
|
1745
1947
|
return ledgerStores.get(key);
|
|
@@ -1826,6 +2028,8 @@ function createRoyAgentCardPort(options = {}) {
|
|
|
1826
2028
|
const opTimeoutMs = options.opTimeoutMs ?? CARD_OP_TIMEOUT_MS;
|
|
1827
2029
|
let notifier;
|
|
1828
2030
|
let inFlight;
|
|
2031
|
+
if (options.loadLedger)
|
|
2032
|
+
ledgerOverride = options.loadLedger;
|
|
1829
2033
|
const loadNotifier = async () => {
|
|
1830
2034
|
const mod = await import("@ai-setting/roy-agent-core/env/task/plugins");
|
|
1831
2035
|
if (typeof mod.LarkCardPatchNotifier !== "function") {
|
|
@@ -1879,6 +2083,7 @@ function createRoyAgentCardPort(options = {}) {
|
|
|
1879
2083
|
},
|
|
1880
2084
|
async finish(input) {
|
|
1881
2085
|
const n = await withTimeout("load", getNotifier(), opTimeoutMs);
|
|
2086
|
+
await hydrateCardTimeline(n, input);
|
|
1882
2087
|
await withTimeout("finish", n.finishTask({
|
|
1883
2088
|
messageId: input.messageId,
|
|
1884
2089
|
chatId: input.chatId,
|
|
@@ -3406,7 +3611,7 @@ function formatStatus(report) {
|
|
|
3406
3611
|
}
|
|
3407
3612
|
|
|
3408
3613
|
// ../../../packages/cli/src/main.ts
|
|
3409
|
-
var VERSION = "0.7.
|
|
3614
|
+
var VERSION = "0.7.13";
|
|
3410
3615
|
async function main(argv) {
|
|
3411
3616
|
const parsed = parseArgs(argv);
|
|
3412
3617
|
switch (parsed.command) {
|