@ai-setting/roy-task-plugin 0.7.12 → 0.7.14

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 CHANGED
@@ -449,69 +449,159 @@ class NdjsonAssembler {
449
449
  }
450
450
  }
451
451
 
452
- // ../agent/src/roy-prompt-core.ts
453
- import {
454
- TASK_DISCIPLINE_COMPLETE,
455
- TASK_DISCIPLINE_GOALS,
456
- TASK_DISCIPLINE_LIFECYCLE,
457
- TASK_DISCIPLINE_OPERATION
458
- } from "@ai-setting/roy-agent-core/env/task/prompt";
459
-
460
- // ../agent/src/trim-permission.ts
461
- var TRIM_RULES = Object.freeze([
462
- {
463
- name: "complete-禁止调用",
464
- match: (line) => /不要调用/.test(line) && /task_complete/.test(line) && /权限/.test(line)
465
- },
466
- {
467
- name: "track-禁止置终态",
468
- match: (line) => /终态由验收方落/.test(line) || /不要/.test(line) && /置为/.test(line) && /completed/.test(line)
469
- },
470
- {
471
- name: "lifecycle-角色二分标题",
472
- match: (line) => /收尾分两种角色/.test(line),
473
- replace: () => "6. COMPLETE — 收尾:用 `task_complete` after item-by-item verification",
474
- dropFollowing: 5
475
- },
476
- {
477
- name: "complete-标题角色限定",
478
- match: (line) => /^#{1,6}\s/.test(line) && /仅验收方适用/.test(line),
479
- replace: (line) => line.replace(/\s*(\*\*仅验收方适用\*\*)/, "")
480
- },
481
- {
482
- name: "complete-角色作用域引用块",
483
- match: (line) => /角色作用域/.test(line) || /若你是/.test(line) && /执行方/.test(line) && /权限/.test(line) || /你的收尾动作是/.test(line) && /验收由主智能体负责/.test(line)
484
- }
452
+ // ../../../packages/cli/src/daemon/discipline.ts
453
+ var PLUGIN_TASK_DISCIPLINE_PROMPT = `### Task Lifecycle — the plugin's contract
454
+
455
+ > **Task is the first-class citizen.** No task, no work.
456
+
457
+ You are the worker agent: you act through the tools, and your output is
458
+ what the user sees. The user reads task titles and milestones, not a
459
+ process log.
460
+
461
+ #### 0. Orient before you create
462
+
463
+ - If a \`[当前 Task #N]\` marker is in your context → \`task_get #N\`
464
+ directly, skip searching.
465
+ - Otherwise \`task_list\` / \`task_search\` for related work first.
466
+ - \`task_tree(depth=1)\` before creating: decide whether the work belongs
467
+ under an existing root instead of piling up new roots. Keep the root
468
+ tasks ≤ 10 — a wide forest is a sign the work was not oriented.
469
+
470
+ #### 1. Create or reuse
471
+
472
+ \`task_create\` with a **title**, a **description**, and
473
+ \`goals_and_expected_deliverables\` written as **numbered sub-items**
474
+ (编号子项) — each one separately verifiable:
475
+
476
+ 1. 实现 X(含文件路径)
477
+ 2. 测试 Y 覆盖 Z 场景(pass/fail 计数)
478
+ 3. 文档 W 落盘到 <path>
479
+
480
+ Each sub-item must be checkable by one of the four atomic forms:
481
+ **文件断言** (file exists / size / content grep), **进程断言**
482
+ (command exit code / stdout), **测试断言** (pass/fail counts, no NEW
483
+ fail), **状态断言** (version / HTTP 200 / status field). A vague
484
+ one-line goal gives the reviewer nothing to check against.
485
+
486
+ Reuse before you create: if a matching task exists, \`task_update\` it
487
+ (append to description / goals) instead of creating a near-duplicate.
488
+
489
+ #### 2. Execute, and record as you go
490
+
491
+ - Work in stages; each stage is one \`task_operation_create\` call.
492
+ - **边干边记** — record the moment a stage lands, never batch at the
493
+ end. Milestones recorded in a batch at the end read as filler.
494
+
495
+ **Operation records carry two dimensions** — keep them apart:
496
+
497
+ | dimension | field | what it answers |
498
+ |---|---|---|
499
+ | state | \`milestone_type\` + \`milestone_title\` + \`milestone_description\` | **what** was reached |
500
+ | process | \`process_description\` (100–500 characters) | **how** it was reached |
501
+
502
+ Record **as you work**, **not after the fact**. The chain is the evidence
503
+ the acceptance side reads, so it has to be complete and honest.
504
+
505
+ \`milestone_title\` is a **短语** (≤ 20 字, verb-first) naming what was
506
+ locked down — e.g. \`泄漏源锁定\`, \`测试全绿\`. Not a label like
507
+ "progress" and not a sentence.
508
+ \`milestone_description\` is a dense technical narrative — file paths,
509
+ symbols, commands, numbers (pass/fail counts, exit codes, sizes).
510
+ \`process_description\` is the route (strategy + tool steps) and must
511
+ **not** repeat the description.
512
+
513
+ #### 3. Track
514
+
515
+ - \`task_update(progress=…, current_status=…)\` as you advance.
516
+ - Status vocabulary: \`todo\` → \`active\` → \`to_validate\` → \`completed\`.
517
+ **Do not** set \`completed\` yourself before the acceptance step below.
518
+
519
+ #### 4. Submit for acceptance — \`task_pre_complete\`
520
+
521
+ When the work is done and the deliverables exist, **submit**:
522
+
523
+ \`\`\`
524
+ task_pre_complete(
525
+ task_id=<id>,
526
+ deliverable_storage_path="<path(s)>",
527
+ current_status="<one-line progress summary>"
528
+ )
529
+ \`\`\`
530
+
531
+ - It sets \`status = "to_validate"\` (待验证) — **not** a terminal state.
532
+ - The deliverable path lands in \`task.context.deliverable_storage_path\`
533
+ and in the operation trail, so the acceptance side can check it.
534
+ - \`deliverable_storage_path\` may name **several paths**, space-separated
535
+ (\`/tmp/report.md /tmp/diff.patch\`); \`,\` \`;\` \`、\` and \` + \` also
536
+ split. A local path may carry the \`file://\` scheme. Declare **every**
537
+ artefact you produced — a path left out reads as "not produced".
538
+ - \`cycle\` tasks are periodic by design: they cannot be submitted or
539
+ completed. Use \`task_update\` to reset their progress instead.
540
+
541
+ **Self-check on submission.** Submitting runs the goal check (the
542
+ "goal vs. deliverables" judge) **once**, at submission time. If the
543
+ result carries a \`[TASK_COMPLETION_HINT]\`, you MUST verify it
544
+ (核实义务) — never ignore it:
545
+
546
+ 1. Re-read the task with \`task_get(task_id=<id>)\`.
547
+ 2. Then do exactly one of:
548
+ - **not met** → fix the gap, then submit again (\`task_pre_complete\`
549
+ is idempotent — a later submission replaces the earlier one's
550
+ status / deliverable, and each one leaves its own operation);
551
+ - **met after all** → record a \`decision\` operation
552
+ (\`task_operation_create(milestone_type="decision")\`) stating why the
553
+ sub-items are actually satisfied.
554
+
555
+ #### 5. Accept — \`task_complete\`
556
+
557
+ The acceptance step is a **separate, explicit** call. Before it:
558
+
559
+ 1. Read the task's goal and delivery fields (\`task_get\`).
560
+ 2. Validate **item-by-item** (按子项逐条验收) against the operation
561
+ chain and the deliverable evidence in
562
+ \`context.deliverable_storage_path\`.
563
+ 3. Honour a \`[TASK_COMPLETION_HINT]\` if one is present — same verify
564
+ duty as above: either **reopen** the task (\`task_update(status="todo")\`)
565
+ and finish the missing work, or **state why** the sub-items are already
566
+ met (a \`decision\` operation).
567
+ 4. If the goal and the deliverables disagree, either extend the task
568
+ (\`task_update\`) or record the discrepancy as a \`decision\` operation.
569
+
570
+ \`task_complete(task_id=<id>)\` then sets \`status = "completed"\` and
571
+ \`progress = 100\`. It takes **no** deliverable parameter — the
572
+ deliverables were already submitted through \`task_pre_complete\`.
573
+
574
+ ⚠️ If \`task_pre_complete\` is not in your tool surface (an older
575
+ core), submit with \`task_complete(deliverable_storage_path=...)\`
576
+ instead — the legacy one-call contract.
577
+
578
+ #### Where tasks live
579
+
580
+ All tasks and operations are persisted by the plugin in the **single**
581
+ project store — locally SQLite at \`~/.roy-task-plugin/tasks.db\` (or the
582
+ remote service when the plugin runs with \`persistence: "remote"\`). Do
583
+ not create side databases, do not keep your own to-do list in files, and
584
+ do not track work in chat only: if it is not in the task store, it is
585
+ not tracked.
586
+
587
+ #### Style
588
+
589
+ - Be direct and concrete.
590
+ - Report the task id and its state whenever you finish a step.
591
+ - If a task is blocked, mark it (\`task_update(status="paused")\`) and
592
+ record a \`problem\` milestone explaining the blocker instead of going
593
+ silent.`;
594
+ var RUNTIMES_WITH_AGENT_SYSTEM_PROMPT = Object.freeze([
595
+ "opencode",
596
+ "claude"
485
597
  ]);
486
- function trimTaskCompletePermissionText(text) {
487
- if (!text)
488
- return text;
489
- const lines = text.split(`
490
- `);
491
- const out = [];
492
- for (let i = 0;i < lines.length; i++) {
493
- const line = lines[i];
494
- const rule = TRIM_RULES.find((r) => r.match(line));
495
- if (!rule) {
496
- out.push(line);
497
- continue;
498
- }
499
- if (rule.replace)
500
- out.push(rule.replace(line));
501
- const drop = rule.dropFollowing ?? 0;
502
- for (let k = 0;k < drop && i + 1 < lines.length; k++) {
503
- i++;
504
- }
505
- }
506
- return out.join(`
507
- `);
598
+ function needsDispatchInjection(runtime) {
599
+ if (!runtime)
600
+ return false;
601
+ return !RUNTIMES_WITH_AGENT_SYSTEM_PROMPT.includes(runtime);
508
602
  }
509
603
 
510
604
  // ../agent/src/roy-prompt-core.ts
511
- var TRIMMED_LIFECYCLE = trimTaskCompletePermissionText(TASK_DISCIPLINE_LIFECYCLE);
512
- var TRIMMED_GOALS = trimTaskCompletePermissionText(TASK_DISCIPLINE_GOALS);
513
- var TRIMMED_OPERATION = trimTaskCompletePermissionText(TASK_DISCIPLINE_OPERATION);
514
- var TRIMMED_COMPLETE = trimTaskCompletePermissionText(TASK_DISCIPLINE_COMPLETE);
515
605
  var ROY_PROMPT_COMMON = `# Roy — task-first-class-citizen agent
516
606
 
517
607
  You are **Roy**, an agent whose defining rule is simple:
@@ -521,44 +611,17 @@ You are **Roy**, an agent whose defining rule is simple:
521
611
  You are a worker agent: you act through the tools, and your output is what
522
612
  the user sees.
523
613
 
524
- ${TRIMMED_LIFECYCLE}
525
-
526
- ${TRIMMED_GOALS}
527
-
528
- The tool returns a **numeric task id** — that number is your handle for the
529
- rest of the conversation.
530
-
531
- ${TRIMMED_OPERATION}
532
-
533
- ${TRIMMED_COMPLETE}
534
-
535
- #### Several deliverables in one field
536
- \`deliverable_storage_path\` may name **multiple paths**, space-separated
537
- (\`/tmp/report.md /tmp/diff.patch\`) — the judge splits on whitespace as well
538
- as \`,\` \`;\` \`、\` and \` + \`. A local path may also be written in its
539
- \`file://\` form (\`file:///tmp/report.md\`) — the scheme is stripped and the
540
- path kept. Declare **every** artefact you produced: a path left out reads as
541
- "not produced" and drags the task toward a false completion hint.
542
-
543
- \`task_complete\` cannot be used on \`cycle\` tasks (they are periodic by
544
- design) — use \`task_update\` to reset those.
614
+ ${PLUGIN_TASK_DISCIPLINE_PROMPT}
545
615
 
546
616
  ## Other tools
547
617
 
548
618
  - \`task_get(task_id, include_operations)\` — full detail; use it before
549
- resuming or completing a task.
619
+ resuming, submitting or accepting a task.
550
620
  - \`task_list(...)\` — flat listing with filters (status / priority / type).
551
621
  - \`task_search(keywords)\` — find by title / description / goals / tags.
552
622
  **Search before creating**, to avoid duplicates.
553
-
554
- ## Where tasks live
555
-
556
- All tasks and operations are persisted by the roy-task-plugin in the
557
- **single** project store — locally that is SQLite at
558
- \`~/.roy-task-plugin/tasks.db\` (or the remote service when the plugin is
559
- configured with \`persistence: "remote"\`). Do not create side databases, do
560
- not maintain your own to-do list in files, and do not track work in chat only:
561
- if it is not in the task store, it is not tracked.
623
+ - \`task_tree(depth=1)\` — the task hierarchy; use it to place new work
624
+ under an existing root instead of piling up roots.
562
625
 
563
626
  ## Style
564
627
 
@@ -649,7 +712,8 @@ loads. Call them directly:
649
712
  | \`task_create\` | \`mcp__roy-task__task_create\` |
650
713
  | \`task_update\` | \`mcp__roy-task__task_update\` |
651
714
  | \`task_operation_create\` | \`mcp__roy-task__task_operation_create\` |
652
- | \`task_complete\` | \`mcp__roy-task__task_complete\` |
715
+ | \`task_pre_complete\` (submit for acceptance) | \`mcp__roy-task__task_pre_complete\` |
716
+ | \`task_complete\` (accept) | \`mcp__roy-task__task_complete\` |
653
717
  | \`task_get\` | \`mcp__roy-task__task_get\` |
654
718
  | \`task_list\` | \`mcp__roy-task__task_list\` |
655
719
  | \`task_search\` | \`mcp__roy-task__task_search\` |
@@ -665,12 +729,13 @@ instead of silently skipping the lifecycle.`;
665
729
 
666
730
  // ../../../packages/cli/src/daemon/runners/claude-agents.ts
667
731
  var CLAUDE_ROY_AGENT_NAME = "roy";
668
- var ROY_AGENT_DESCRIPTION = "Roy — task-as-first-class-citizen agent. Uses the roy-agent task lifecycle " + "(task_tree → task_create → task_update → task_operation_create → task_complete) " + "so every piece of work is tracked in the local task store.";
732
+ var ROY_AGENT_DESCRIPTION = "Roy — task-as-first-class-citizen agent. Uses the roy-agent task lifecycle " + "(task_tree → task_create → task_update → task_operation_create → " + "task_pre_complete → task_complete) " + "so every piece of work is tracked in the local task store.";
669
733
  var ROY_TASK_MCP_SERVER = "roy-task";
670
734
  var ROY_TASK_MCP_TOOLS = Object.freeze([
671
735
  "task_create",
672
736
  "task_update",
673
737
  "task_operation_create",
738
+ "task_pre_complete",
674
739
  "task_complete",
675
740
  "task_get",
676
741
  "task_list",
@@ -1209,19 +1274,6 @@ function errMsg(err) {
1209
1274
  return String(err);
1210
1275
  }
1211
1276
 
1212
- // ../../../packages/cli/src/daemon/discipline.ts
1213
- import { TASK_DISCIPLINE_PROMPT } from "@ai-setting/roy-agent-core/env/task/prompt";
1214
- var PLUGIN_TASK_DISCIPLINE_PROMPT = trimTaskCompletePermissionText(TASK_DISCIPLINE_PROMPT);
1215
- var RUNTIMES_WITH_AGENT_SYSTEM_PROMPT = Object.freeze([
1216
- "opencode",
1217
- "claude"
1218
- ]);
1219
- function needsDispatchInjection(runtime) {
1220
- if (!runtime)
1221
- return false;
1222
- return !RUNTIMES_WITH_AGENT_SYSTEM_PROMPT.includes(runtime);
1223
- }
1224
-
1225
1277
  // ../../../packages/cli/src/daemon/runners/codex.ts
1226
1278
  var DEFAULT_CODEX_TIMEOUT_MS = 10 * 60 * 1000;
1227
1279
  var CODEX_STDIO = Object.freeze([
@@ -1820,7 +1872,7 @@ function buildDispatchPrompt(input) {
1820
1872
  ""
1821
1873
  ];
1822
1874
  if (needsDispatchInjection(input.runtime)) {
1823
- parts.push("## 任务纪律(Task #4889 — 来自 roy-agent core 单一真相源)", "", PLUGIN_TASK_DISCIPLINE_PROMPT, "");
1875
+ parts.push("## 任务纪律(Task #5078 — 插件自维护的契约)", "", PLUGIN_TASK_DISCIPLINE_PROMPT, "");
1824
1876
  }
1825
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), "```");
1826
1878
  if (input.recommendedAction) {
@@ -3559,7 +3611,7 @@ function formatStatus(report) {
3559
3611
  }
3560
3612
 
3561
3613
  // ../../../packages/cli/src/main.ts
3562
- var VERSION = "0.7.12";
3614
+ var VERSION = "0.7.14";
3563
3615
  async function main(argv) {
3564
3616
  const parsed = parseArgs(argv);
3565
3617
  switch (parsed.command) {