@ai-setting/roy-task-plugin 0.7.5 → 0.7.7
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 +62 -12
- package/dist/claude-plugin/commands/task-milestone.md +1 -1
- package/dist/cli.js +228 -57
- package/dist/index.js +370 -232
- package/dist/index.js.map +15 -12
- package/package.json +2 -2
|
@@ -34,6 +34,30 @@ Create the task **before** doing the work. Pass:
|
|
|
34
34
|
- `project_path`, `context` (JSON string: worktree_path / branch / type …)
|
|
35
35
|
- `parent_task_id` when the tree said so
|
|
36
36
|
|
|
37
|
+
#### ⚠️ Goals must be numbered sub-items
|
|
38
|
+
`goals_and_expected_deliverables` **MUST** be written as **numbered
|
|
39
|
+
sub-items** (编号子项) that can each be verified **one by one**
|
|
40
|
+
(逐条验收) — for `type=normal` tasks:
|
|
41
|
+
|
|
42
|
+
1. 实现 X(含文件路径)
|
|
43
|
+
2. 测试 Y 覆盖 Z 场景(pass/fail 计数)
|
|
44
|
+
3. 文档 W 落盘到 <path>
|
|
45
|
+
|
|
46
|
+
Why: the completion hook judges the goal **item by item** against the
|
|
47
|
+
operation-record chain, using each sub-item as fine-grained evidence. A
|
|
48
|
+
vague one-line goal (一句话模糊目标,如「做好 X」「完成调研」) gives the
|
|
49
|
+
judge nothing to check and reliably triggers a completion hint.
|
|
50
|
+
|
|
51
|
+
Reference the four atomic assertion forms when writing each sub-item:
|
|
52
|
+
- **文件断言** (file assertion) — file exists / size / content grep
|
|
53
|
+
- **进程断言** (process assertion) — command exit code / stdout match
|
|
54
|
+
- **测试断言** (test assertion) — pass/fail counts, no NEW fail
|
|
55
|
+
- **状态断言** (state assertion) — version number / HTTP 200 / status field
|
|
56
|
+
|
|
57
|
+
For `type=longterm` / `type=cycle` tasks there is no per-item completion
|
|
58
|
+
acceptance — for those, split goals into staged, individually checkable
|
|
59
|
+
sub-items instead of one vague sentence.
|
|
60
|
+
|
|
37
61
|
The tool returns a **numeric task id** — that number is your handle for the
|
|
38
62
|
rest of the conversation.
|
|
39
63
|
|
|
@@ -46,13 +70,20 @@ Report progress as you go, not only at the end:
|
|
|
46
70
|
If the scope changes, `task_update` the description/goals instead of silently
|
|
47
71
|
doing something else.
|
|
48
72
|
|
|
49
|
-
### 4. Record milestones — `task_operation_create`
|
|
73
|
+
### 4. Record milestones — `task_operation_create` — **as you work**
|
|
74
|
+
Call this tool the moment a stage is reached — **not after the fact**,
|
|
75
|
+
not in a batch at the end. Finished a chunk of code? A test just went
|
|
76
|
+
green? Hit a blocker? Made a decision? Record it **immediately**, then
|
|
77
|
+
keep working. The record is the running log of the work, written while
|
|
78
|
+
it happens.
|
|
79
|
+
|
|
50
80
|
This tool has **two orthogonal dimensions** and you must keep them distinct:
|
|
51
81
|
- **state / milestone dimension** — `milestone_type` + `milestone_title` +
|
|
52
82
|
`milestone_description`: *what* was reached (e.g. "GREEN: all tests pass").
|
|
53
83
|
- **process dimension** — `process_description`: *how* you got there —
|
|
54
|
-
|
|
55
|
-
Too short = rejected (no value);
|
|
84
|
+
the operations just performed and why, written in the moment,
|
|
85
|
+
**100–500 characters**. Too short = rejected (no value);
|
|
86
|
+
too long = rejected (that is a log, not a summary).
|
|
56
87
|
|
|
57
88
|
Rules: do **not** use the process field to restate the milestone, and do not
|
|
58
89
|
use milestones as a substitute for progress. Milestones mark *state
|
|
@@ -61,12 +92,32 @@ transitions*: RED→GREEN, blocker found, decision made, review passed.
|
|
|
61
92
|
### 5. Close — `task_complete`
|
|
62
93
|
Before completing:
|
|
63
94
|
1. `task_get(task_id)` and re-read goal + delivery.
|
|
64
|
-
2.
|
|
65
|
-
|
|
95
|
+
2. **Accept the goal item-by-item** (按子项逐条验收): check **each** numbered
|
|
96
|
+
sub-item against the operation-record chain and the deliverable evidence.
|
|
97
|
+
If a sub-item is not met, either extend the task (`task_update`) or say
|
|
98
|
+
so honestly.
|
|
66
99
|
3. Call `task_complete` with `deliverable_storage_path` — a locator for the
|
|
67
100
|
work product: a file path, a git branch/worktree, a doc URL, a commit URL.
|
|
68
101
|
The path is persisted into the task context and as an operation record.
|
|
69
102
|
|
|
103
|
+
#### ⚠️ The completion hint is a verify duty, not a notification
|
|
104
|
+
If the task context carries a `[TASK_COMPLETION_HINT]`, you **MUST** verify
|
|
105
|
+
it (核实义务) — never ignore it (不允许无视). Re-read the task via
|
|
106
|
+
`task_get`, then do **exactly one** of:
|
|
107
|
+
- (a) the goal is **not** met → `task_update(task_id=<id>, status=todo)` to
|
|
108
|
+
reopen and continue the work; or
|
|
109
|
+
- (b) the goal **is** met → `task_operation_create`
|
|
110
|
+
(milestone_type=decision) stating why the sub-items are actually met
|
|
111
|
+
(说明理由 — "state why").
|
|
112
|
+
|
|
113
|
+
#### Several deliverables in one field
|
|
114
|
+
`deliverable_storage_path` may name **multiple paths**, space-separated
|
|
115
|
+
(`/tmp/report.md /tmp/diff.patch`) — the judge splits on whitespace as well
|
|
116
|
+
as `,` `;` `、` and ` + `. A local path may also be written in its
|
|
117
|
+
`file://` form (`file:///tmp/report.md`) — the scheme is stripped and the
|
|
118
|
+
path kept. Declare **every** artefact you produced: a path left out reads as
|
|
119
|
+
"not produced" and drags the task toward a false completion hint.
|
|
120
|
+
|
|
70
121
|
`task_complete` cannot be used on `cycle` tasks (they are periodic by
|
|
71
122
|
design) — use `task_update` to reset those.
|
|
72
123
|
|
|
@@ -87,6 +138,12 @@ configured with `persistence: "remote"`). Do not create side databases, do
|
|
|
87
138
|
not maintain your own to-do list in files, and do not track work in chat only:
|
|
88
139
|
if it is not in the task store, it is not tracked.
|
|
89
140
|
|
|
141
|
+
## Style
|
|
142
|
+
|
|
143
|
+
- Be direct and concrete; the user is reading task titles and milestones.
|
|
144
|
+
- Report the task id and its state whenever you finish a step.
|
|
145
|
+
- If a task is blocked, mark it (`task_update(status="paused")`) and record a
|
|
146
|
+
`problem` milestone explaining the blocker instead of going silent.
|
|
90
147
|
## Tool surface on Claude Code
|
|
91
148
|
|
|
92
149
|
The `task_*` tools are served by the **`roy-task` MCP server** this runtime
|
|
@@ -111,10 +168,3 @@ Measured: it has no way to write a milestone (its `operations <id>` subcommand
|
|
|
111
168
|
is a *reader*), and it targets a different database than this server. Use the
|
|
112
169
|
MCP tools above; if they are genuinely unavailable, say so in your answer
|
|
113
170
|
instead of silently skipping the lifecycle.
|
|
114
|
-
|
|
115
|
-
## Style
|
|
116
|
-
|
|
117
|
-
- Be direct and concrete; the user is reading task titles and milestones.
|
|
118
|
-
- Report the task id and its state whenever you finish a step.
|
|
119
|
-
- If a task is blocked, mark it (`task_update(status="paused")`) and record a
|
|
120
|
-
`problem` milestone explaining the blocker instead of going silent.
|
|
@@ -32,7 +32,7 @@ If you do have the MCP server, call `task_operation_create` with:
|
|
|
32
32
|
`solution`, `decision`, `review`, `completed`
|
|
33
33
|
- `milestone_title` — one line
|
|
34
34
|
- `milestone_description` — what was reached
|
|
35
|
-
- `process_description` — **how** you got there,
|
|
35
|
+
- `process_description` — **how** you got there, 100–500 characters
|
|
36
36
|
(this is enforced; too short or too long is rejected)
|
|
37
37
|
|
|
38
38
|
Keep the two dimensions distinct: the milestone says *what* was reached, the
|
package/dist/cli.js
CHANGED
|
@@ -444,33 +444,8 @@ class NdjsonAssembler {
|
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
//
|
|
448
|
-
var
|
|
449
|
-
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.";
|
|
450
|
-
var ROY_TASK_MCP_SERVER = "roy-task";
|
|
451
|
-
var ROY_TASK_MCP_TOOLS = Object.freeze([
|
|
452
|
-
"task_create",
|
|
453
|
-
"task_update",
|
|
454
|
-
"task_operation_create",
|
|
455
|
-
"task_complete",
|
|
456
|
-
"task_get",
|
|
457
|
-
"task_list",
|
|
458
|
-
"task_tree",
|
|
459
|
-
"task_search"
|
|
460
|
-
].map((t) => `mcp__${ROY_TASK_MCP_SERVER}__${t}`));
|
|
461
|
-
var CLAUDE_ROY_TOOLS = Object.freeze([
|
|
462
|
-
"Read",
|
|
463
|
-
"Write",
|
|
464
|
-
"Edit",
|
|
465
|
-
"Bash",
|
|
466
|
-
"Grep",
|
|
467
|
-
"Glob",
|
|
468
|
-
"TodoWrite",
|
|
469
|
-
"WebFetch",
|
|
470
|
-
"WebSearch",
|
|
471
|
-
...ROY_TASK_MCP_TOOLS
|
|
472
|
-
]);
|
|
473
|
-
var CLAUDE_ROY_SYSTEM_PROMPT = `# Roy — task-first-class-citizen agent
|
|
447
|
+
// ../agent/src/roy-prompt-core.ts
|
|
448
|
+
var ROY_PROMPT_COMMON = `# Roy — task-first-class-citizen agent
|
|
474
449
|
|
|
475
450
|
You are **Roy**, an agent whose defining rule is simple:
|
|
476
451
|
|
|
@@ -499,6 +474,30 @@ Create the task **before** doing the work. Pass:
|
|
|
499
474
|
- \`project_path\`, \`context\` (JSON string: worktree_path / branch / type …)
|
|
500
475
|
- \`parent_task_id\` when the tree said so
|
|
501
476
|
|
|
477
|
+
#### ⚠️ Goals must be numbered sub-items
|
|
478
|
+
\`goals_and_expected_deliverables\` **MUST** be written as **numbered
|
|
479
|
+
sub-items** (编号子项) that can each be verified **one by one**
|
|
480
|
+
(逐条验收) — for \`type=normal\` tasks:
|
|
481
|
+
|
|
482
|
+
1. 实现 X(含文件路径)
|
|
483
|
+
2. 测试 Y 覆盖 Z 场景(pass/fail 计数)
|
|
484
|
+
3. 文档 W 落盘到 <path>
|
|
485
|
+
|
|
486
|
+
Why: the completion hook judges the goal **item by item** against the
|
|
487
|
+
operation-record chain, using each sub-item as fine-grained evidence. A
|
|
488
|
+
vague one-line goal (一句话模糊目标,如「做好 X」「完成调研」) gives the
|
|
489
|
+
judge nothing to check and reliably triggers a completion hint.
|
|
490
|
+
|
|
491
|
+
Reference the four atomic assertion forms when writing each sub-item:
|
|
492
|
+
- **文件断言** (file assertion) — file exists / size / content grep
|
|
493
|
+
- **进程断言** (process assertion) — command exit code / stdout match
|
|
494
|
+
- **测试断言** (test assertion) — pass/fail counts, no NEW fail
|
|
495
|
+
- **状态断言** (state assertion) — version number / HTTP 200 / status field
|
|
496
|
+
|
|
497
|
+
For \`type=longterm\` / \`type=cycle\` tasks there is no per-item completion
|
|
498
|
+
acceptance — for those, split goals into staged, individually checkable
|
|
499
|
+
sub-items instead of one vague sentence.
|
|
500
|
+
|
|
502
501
|
The tool returns a **numeric task id** — that number is your handle for the
|
|
503
502
|
rest of the conversation.
|
|
504
503
|
|
|
@@ -511,13 +510,20 @@ Report progress as you go, not only at the end:
|
|
|
511
510
|
If the scope changes, \`task_update\` the description/goals instead of silently
|
|
512
511
|
doing something else.
|
|
513
512
|
|
|
514
|
-
### 4. Record milestones — \`task_operation_create\`
|
|
513
|
+
### 4. Record milestones — \`task_operation_create\` — **as you work**
|
|
514
|
+
Call this tool the moment a stage is reached — **not after the fact**,
|
|
515
|
+
not in a batch at the end. Finished a chunk of code? A test just went
|
|
516
|
+
green? Hit a blocker? Made a decision? Record it **immediately**, then
|
|
517
|
+
keep working. The record is the running log of the work, written while
|
|
518
|
+
it happens.
|
|
519
|
+
|
|
515
520
|
This tool has **two orthogonal dimensions** and you must keep them distinct:
|
|
516
521
|
- **state / milestone dimension** — \`milestone_type\` + \`milestone_title\` +
|
|
517
522
|
\`milestone_description\`: *what* was reached (e.g. "GREEN: all tests pass").
|
|
518
523
|
- **process dimension** — \`process_description\`: *how* you got there —
|
|
519
|
-
|
|
520
|
-
Too short = rejected (no value);
|
|
524
|
+
the operations just performed and why, written in the moment,
|
|
525
|
+
**100–500 characters**. Too short = rejected (no value);
|
|
526
|
+
too long = rejected (that is a log, not a summary).
|
|
521
527
|
|
|
522
528
|
Rules: do **not** use the process field to restate the milestone, and do not
|
|
523
529
|
use milestones as a substitute for progress. Milestones mark *state
|
|
@@ -526,12 +532,32 @@ transitions*: RED→GREEN, blocker found, decision made, review passed.
|
|
|
526
532
|
### 5. Close — \`task_complete\`
|
|
527
533
|
Before completing:
|
|
528
534
|
1. \`task_get(task_id)\` and re-read goal + delivery.
|
|
529
|
-
2.
|
|
530
|
-
|
|
535
|
+
2. **Accept the goal item-by-item** (按子项逐条验收): check **each** numbered
|
|
536
|
+
sub-item against the operation-record chain and the deliverable evidence.
|
|
537
|
+
If a sub-item is not met, either extend the task (\`task_update\`) or say
|
|
538
|
+
so honestly.
|
|
531
539
|
3. Call \`task_complete\` with \`deliverable_storage_path\` — a locator for the
|
|
532
540
|
work product: a file path, a git branch/worktree, a doc URL, a commit URL.
|
|
533
541
|
The path is persisted into the task context and as an operation record.
|
|
534
542
|
|
|
543
|
+
#### ⚠️ The completion hint is a verify duty, not a notification
|
|
544
|
+
If the task context carries a \`[TASK_COMPLETION_HINT]\`, you **MUST** verify
|
|
545
|
+
it (核实义务) — never ignore it (不允许无视). Re-read the task via
|
|
546
|
+
\`task_get\`, then do **exactly one** of:
|
|
547
|
+
- (a) the goal is **not** met → \`task_update(task_id=<id>, status=todo)\` to
|
|
548
|
+
reopen and continue the work; or
|
|
549
|
+
- (b) the goal **is** met → \`task_operation_create\`
|
|
550
|
+
(milestone_type=decision) stating why the sub-items are actually met
|
|
551
|
+
(说明理由 — "state why").
|
|
552
|
+
|
|
553
|
+
#### Several deliverables in one field
|
|
554
|
+
\`deliverable_storage_path\` may name **multiple paths**, space-separated
|
|
555
|
+
(\`/tmp/report.md /tmp/diff.patch\`) — the judge splits on whitespace as well
|
|
556
|
+
as \`,\` \`;\` \`、\` and \` + \`. A local path may also be written in its
|
|
557
|
+
\`file://\` form (\`file:///tmp/report.md\`) — the scheme is stripped and the
|
|
558
|
+
path kept. Declare **every** artefact you produced: a path left out reads as
|
|
559
|
+
"not produced" and drags the task toward a false completion hint.
|
|
560
|
+
|
|
535
561
|
\`task_complete\` cannot be used on \`cycle\` tasks (they are periodic by
|
|
536
562
|
design) — use \`task_update\` to reset those.
|
|
537
563
|
|
|
@@ -552,6 +578,13 @@ configured with \`persistence: "remote"\`). Do not create side databases, do
|
|
|
552
578
|
not maintain your own to-do list in files, and do not track work in chat only:
|
|
553
579
|
if it is not in the task store, it is not tracked.
|
|
554
580
|
|
|
581
|
+
## Style
|
|
582
|
+
|
|
583
|
+
- Be direct and concrete; the user is reading task titles and milestones.
|
|
584
|
+
- Report the task id and its state whenever you finish a step.
|
|
585
|
+
- If a task is blocked, mark it (\`task_update(status="paused")\`) and record a
|
|
586
|
+
\`problem\` milestone explaining the blocker instead of going silent.`;
|
|
587
|
+
var ROY_PROMPT_CLAUDE_TAIL = `
|
|
555
588
|
## Tool surface on Claude Code
|
|
556
589
|
|
|
557
590
|
The \`task_*\` tools are served by the **\`roy-task\` MCP server** this runtime
|
|
@@ -575,14 +608,35 @@ progress card renders the task these tools create.
|
|
|
575
608
|
Measured: it has no way to write a milestone (its \`operations <id>\` subcommand
|
|
576
609
|
is a *reader*), and it targets a different database than this server. Use the
|
|
577
610
|
MCP tools above; if they are genuinely unavailable, say so in your answer
|
|
578
|
-
instead of silently skipping the lifecycle
|
|
611
|
+
instead of silently skipping the lifecycle.`;
|
|
579
612
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
613
|
+
// ../../../packages/cli/src/daemon/runners/claude-agents.ts
|
|
614
|
+
var CLAUDE_ROY_AGENT_NAME = "roy";
|
|
615
|
+
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.";
|
|
616
|
+
var ROY_TASK_MCP_SERVER = "roy-task";
|
|
617
|
+
var ROY_TASK_MCP_TOOLS = Object.freeze([
|
|
618
|
+
"task_create",
|
|
619
|
+
"task_update",
|
|
620
|
+
"task_operation_create",
|
|
621
|
+
"task_complete",
|
|
622
|
+
"task_get",
|
|
623
|
+
"task_list",
|
|
624
|
+
"task_tree",
|
|
625
|
+
"task_search"
|
|
626
|
+
].map((t) => `mcp__${ROY_TASK_MCP_SERVER}__${t}`));
|
|
627
|
+
var CLAUDE_ROY_TOOLS = Object.freeze([
|
|
628
|
+
"Read",
|
|
629
|
+
"Write",
|
|
630
|
+
"Edit",
|
|
631
|
+
"Bash",
|
|
632
|
+
"Grep",
|
|
633
|
+
"Glob",
|
|
634
|
+
"TodoWrite",
|
|
635
|
+
"WebFetch",
|
|
636
|
+
"WebSearch",
|
|
637
|
+
...ROY_TASK_MCP_TOOLS
|
|
638
|
+
]);
|
|
639
|
+
var CLAUDE_ROY_SYSTEM_PROMPT = ROY_PROMPT_COMMON + ROY_PROMPT_CLAUDE_TAIL;
|
|
586
640
|
function buildClaudeAgentsJson(opts = {}) {
|
|
587
641
|
const def = {
|
|
588
642
|
description: opts.description ?? ROY_AGENT_DESCRIPTION,
|
|
@@ -954,13 +1008,17 @@ function resolveClaudePluginDir(roots, explicit) {
|
|
|
954
1008
|
}
|
|
955
1009
|
return;
|
|
956
1010
|
}
|
|
957
|
-
function buildRoyTaskMcpConfig(pluginDir) {
|
|
1011
|
+
function buildRoyTaskMcpConfig(pluginDir, env) {
|
|
1012
|
+
const server = {
|
|
1013
|
+
command: process.execPath,
|
|
1014
|
+
args: [join(pluginDir, "mcp", "roy-task-mcp-server.mjs")]
|
|
1015
|
+
};
|
|
1016
|
+
const clean = env ? Object.fromEntries(Object.entries(env).filter(([, v]) => v !== undefined && v !== "")) : {};
|
|
1017
|
+
if (Object.keys(clean).length > 0)
|
|
1018
|
+
server.env = clean;
|
|
958
1019
|
return JSON.stringify({
|
|
959
1020
|
mcpServers: {
|
|
960
|
-
"roy-task":
|
|
961
|
-
command: process.execPath,
|
|
962
|
-
args: [join(pluginDir, "mcp", "roy-task-mcp-server.mjs")]
|
|
963
|
-
}
|
|
1021
|
+
"roy-task": server
|
|
964
1022
|
}
|
|
965
1023
|
});
|
|
966
1024
|
}
|
|
@@ -988,8 +1046,8 @@ function makeClaudeRunner(opts = {}) {
|
|
|
988
1046
|
const agentsJson = opts.agentsJson ?? buildClaudeAgentsJson();
|
|
989
1047
|
const sessions = opts.sessions ?? createClaudeSessionRegistry();
|
|
990
1048
|
const pluginDir = opts.pluginDir ?? resolveClaudePluginDir([dirname(fileURLToPath(import.meta.url))]);
|
|
991
|
-
const mcpConfig = pluginDir ? buildRoyTaskMcpConfig(pluginDir) : undefined;
|
|
992
1049
|
return async function runAgent(input) {
|
|
1050
|
+
const mcpConfig = pluginDir ? buildRoyTaskMcpConfig(pluginDir, input.mcpEnv) : undefined;
|
|
993
1051
|
const known = input.sessionId ? sessions.get(input.sessionId) : undefined;
|
|
994
1052
|
const args = buildClaudeArgs({
|
|
995
1053
|
prompt: input.prompt,
|
|
@@ -1098,7 +1156,7 @@ var CODEX_SANDBOX_MODES = Object.freeze([
|
|
|
1098
1156
|
function isCodexSandboxMode(value) {
|
|
1099
1157
|
return CODEX_SANDBOX_MODES.includes(value);
|
|
1100
1158
|
}
|
|
1101
|
-
function buildRoyTaskCodexConfigArgs(pluginDir) {
|
|
1159
|
+
function buildRoyTaskCodexConfigArgs(pluginDir, env) {
|
|
1102
1160
|
const args = [
|
|
1103
1161
|
`-cmcp_servers.${ROY_TASK_MCP_SERVER}.command=${JSON.stringify(process.execPath)}`,
|
|
1104
1162
|
`-cmcp_servers.${ROY_TASK_MCP_SERVER}.args=${JSON.stringify([
|
|
@@ -1106,12 +1164,12 @@ function buildRoyTaskCodexConfigArgs(pluginDir) {
|
|
|
1106
1164
|
])}`,
|
|
1107
1165
|
`-cmcp_servers.${ROY_TASK_MCP_SERVER}.default_tools_approval_mode="approve"`
|
|
1108
1166
|
];
|
|
1109
|
-
const envArg = buildMcpServerEnvArg();
|
|
1167
|
+
const envArg = buildMcpServerEnvArg(env);
|
|
1110
1168
|
if (envArg)
|
|
1111
1169
|
args.push(envArg);
|
|
1112
1170
|
return args;
|
|
1113
1171
|
}
|
|
1114
|
-
function buildMcpServerEnvArg() {
|
|
1172
|
+
function buildMcpServerEnvArg(env) {
|
|
1115
1173
|
const keys = [
|
|
1116
1174
|
"ROY_TASK_NOTIFY_ENABLED",
|
|
1117
1175
|
"ROY_TASK_NOTIFY_CHAT_ID",
|
|
@@ -1119,7 +1177,8 @@ function buildMcpServerEnvArg() {
|
|
|
1119
1177
|
];
|
|
1120
1178
|
const pairs = [];
|
|
1121
1179
|
for (const key of keys) {
|
|
1122
|
-
const
|
|
1180
|
+
const fromEnv = env ? env[key] : undefined;
|
|
1181
|
+
const value = fromEnv ?? (env ? undefined : process.env[key]);
|
|
1123
1182
|
if (!value)
|
|
1124
1183
|
continue;
|
|
1125
1184
|
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
@@ -1159,8 +1218,8 @@ function makeCodexRunner(opts = {}) {
|
|
|
1159
1218
|
const sandbox = opts.sandbox ?? "read-only";
|
|
1160
1219
|
const sessions = opts.sessions ?? createCodexSessionRegistry();
|
|
1161
1220
|
const pluginDir = opts.pluginDir ?? resolveClaudePluginDir([dirname2(fileURLToPath2(import.meta.url))]);
|
|
1162
|
-
const mcpConfigArgs = pluginDir ? buildRoyTaskCodexConfigArgs(pluginDir) : undefined;
|
|
1163
1221
|
return async function runAgent(input) {
|
|
1222
|
+
const mcpConfigArgs = pluginDir ? buildRoyTaskCodexConfigArgs(pluginDir, input.mcpEnv) : undefined;
|
|
1164
1223
|
const known = input.sessionId ? sessions.get(input.sessionId) : undefined;
|
|
1165
1224
|
const args = known ? buildCodexResumeArgs(known, { prompt: input.prompt, model, sandbox, mcpConfigArgs }) : buildCodexArgs({ prompt: input.prompt, model, sandbox, mcpConfigArgs });
|
|
1166
1225
|
const decoder = new CodexStreamDecoder;
|
|
@@ -1747,6 +1806,92 @@ function withTimeout(op, promise, ms) {
|
|
|
1747
1806
|
});
|
|
1748
1807
|
});
|
|
1749
1808
|
}
|
|
1809
|
+
var ledgerStores = new Map;
|
|
1810
|
+
var ledgerInFlight;
|
|
1811
|
+
function ledgerPathKey() {
|
|
1812
|
+
return process.env.ROY_AGENT_LARK_CARD_DB ?? "";
|
|
1813
|
+
}
|
|
1814
|
+
async function loadLedger() {
|
|
1815
|
+
const key = ledgerPathKey();
|
|
1816
|
+
if (ledgerStores.has(key))
|
|
1817
|
+
return ledgerStores.get(key);
|
|
1818
|
+
if (!ledgerInFlight) {
|
|
1819
|
+
ledgerInFlight = (async () => {
|
|
1820
|
+
try {
|
|
1821
|
+
const mod = await import("@ai-setting/roy-agent-core/env/task/plugins");
|
|
1822
|
+
if (typeof mod.createLarkCardStateStore !== "function") {
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
const store2 = mod.createLarkCardStateStore();
|
|
1826
|
+
ledgerStores.set(key, store2);
|
|
1827
|
+
return store2;
|
|
1828
|
+
} catch {
|
|
1829
|
+
return;
|
|
1830
|
+
} finally {
|
|
1831
|
+
ledgerInFlight = undefined;
|
|
1832
|
+
}
|
|
1833
|
+
})();
|
|
1834
|
+
}
|
|
1835
|
+
const store = await ledgerInFlight;
|
|
1836
|
+
ledgerStores.set(key, store);
|
|
1837
|
+
return store;
|
|
1838
|
+
}
|
|
1839
|
+
function taskIdOf(meta) {
|
|
1840
|
+
const id = meta?.id;
|
|
1841
|
+
return typeof id === "number" ? id : undefined;
|
|
1842
|
+
}
|
|
1843
|
+
async function adoptExistingCard(input) {
|
|
1844
|
+
const taskId = taskIdOf(input.meta);
|
|
1845
|
+
if (taskId === undefined)
|
|
1846
|
+
return;
|
|
1847
|
+
try {
|
|
1848
|
+
const store = await loadLedger();
|
|
1849
|
+
if (!store)
|
|
1850
|
+
return;
|
|
1851
|
+
const record = store.get(taskId);
|
|
1852
|
+
if (!record || !record.messageId)
|
|
1853
|
+
return;
|
|
1854
|
+
if (record.status === "closed")
|
|
1855
|
+
return;
|
|
1856
|
+
return { messageId: record.messageId, pinned: record.pinned };
|
|
1857
|
+
} catch {
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
async function recordCardInLedger(input, res) {
|
|
1862
|
+
const taskId = taskIdOf(input.meta);
|
|
1863
|
+
if (taskId === undefined || !res.messageId)
|
|
1864
|
+
return;
|
|
1865
|
+
try {
|
|
1866
|
+
const store = await loadLedger();
|
|
1867
|
+
if (!store)
|
|
1868
|
+
return;
|
|
1869
|
+
const existing = store.get(taskId);
|
|
1870
|
+
const now = Date.now();
|
|
1871
|
+
store.upsert({
|
|
1872
|
+
taskId,
|
|
1873
|
+
messageId: res.messageId,
|
|
1874
|
+
chatId: input.chatId,
|
|
1875
|
+
profile: input.profile,
|
|
1876
|
+
pinned: res.pinned,
|
|
1877
|
+
status: "open",
|
|
1878
|
+
createdAt: existing?.createdAt ?? now,
|
|
1879
|
+
updatedAt: now
|
|
1880
|
+
});
|
|
1881
|
+
} catch {}
|
|
1882
|
+
}
|
|
1883
|
+
async function closeCardInLedger(input) {
|
|
1884
|
+
const taskId = taskIdOf(input.meta);
|
|
1885
|
+
if (taskId === undefined)
|
|
1886
|
+
return;
|
|
1887
|
+
try {
|
|
1888
|
+
const store = await loadLedger();
|
|
1889
|
+
if (!store)
|
|
1890
|
+
return;
|
|
1891
|
+
store.markClosed(taskId);
|
|
1892
|
+
store.markPinned(taskId, false);
|
|
1893
|
+
} catch {}
|
|
1894
|
+
}
|
|
1750
1895
|
function createRoyAgentCardPort(options = {}) {
|
|
1751
1896
|
const opTimeoutMs = options.opTimeoutMs ?? CARD_OP_TIMEOUT_MS;
|
|
1752
1897
|
let notifier;
|
|
@@ -1776,13 +1921,18 @@ function createRoyAgentCardPort(options = {}) {
|
|
|
1776
1921
|
return {
|
|
1777
1922
|
enabled: true,
|
|
1778
1923
|
async start(input) {
|
|
1924
|
+
const adopted = await adoptExistingCard(input);
|
|
1925
|
+
if (adopted)
|
|
1926
|
+
return adopted;
|
|
1779
1927
|
const n = await withTimeout("load", getNotifier(), opTimeoutMs);
|
|
1780
|
-
|
|
1928
|
+
const res = await withTimeout("start", n.startTask({
|
|
1781
1929
|
chatId: input.chatId,
|
|
1782
1930
|
profile: input.profile,
|
|
1783
1931
|
meta: input.meta,
|
|
1784
1932
|
milestones: input.milestones
|
|
1785
1933
|
}), opTimeoutMs);
|
|
1934
|
+
await recordCardInLedger(input, res);
|
|
1935
|
+
return res;
|
|
1786
1936
|
},
|
|
1787
1937
|
async update(input) {
|
|
1788
1938
|
const n = await withTimeout("load", getNotifier(), opTimeoutMs);
|
|
@@ -1808,6 +1958,7 @@ function createRoyAgentCardPort(options = {}) {
|
|
|
1808
1958
|
finalStep: input.finalStep,
|
|
1809
1959
|
progress: input.progress
|
|
1810
1960
|
}), opTimeoutMs);
|
|
1961
|
+
await closeCardInLedger(input);
|
|
1811
1962
|
}
|
|
1812
1963
|
};
|
|
1813
1964
|
}
|
|
@@ -1868,6 +2019,23 @@ function normaliseMcpToolName(tool) {
|
|
|
1868
2019
|
return tool;
|
|
1869
2020
|
}
|
|
1870
2021
|
|
|
2022
|
+
// ../../../packages/cli/src/daemon/mcp-notify.ts
|
|
2023
|
+
function buildMcpNotifyEnv(destination, options = {}) {
|
|
2024
|
+
const env = {};
|
|
2025
|
+
if (!destination.chatId)
|
|
2026
|
+
return env;
|
|
2027
|
+
const enabled = options.enabled ?? true;
|
|
2028
|
+
if (enabled)
|
|
2029
|
+
env["ROY_TASK_NOTIFY_ENABLED"] = "1";
|
|
2030
|
+
env["ROY_TASK_NOTIFY_CHAT_ID"] = destination.chatId;
|
|
2031
|
+
if (destination.profile)
|
|
2032
|
+
env["ROY_TASK_NOTIFY_PROFILE"] = destination.profile;
|
|
2033
|
+
return env;
|
|
2034
|
+
}
|
|
2035
|
+
function notifyEnvFromDispatch(plan, options = {}) {
|
|
2036
|
+
return buildMcpNotifyEnv({ chatId: plan.chatId, profile: plan.profile }, options);
|
|
2037
|
+
}
|
|
2038
|
+
|
|
1871
2039
|
// ../../../packages/cli/src/daemon/dispatcher.ts
|
|
1872
2040
|
function cardReport(handle) {
|
|
1873
2041
|
const out = {};
|
|
@@ -1943,10 +2111,12 @@ class Dispatcher {
|
|
|
1943
2111
|
deps;
|
|
1944
2112
|
opts;
|
|
1945
2113
|
deduper;
|
|
2114
|
+
notifyOptions;
|
|
1946
2115
|
constructor(deps, opts = {}) {
|
|
1947
2116
|
this.deps = deps;
|
|
1948
2117
|
this.opts = opts;
|
|
1949
2118
|
this.deduper = deps.deduper ?? new Deduper;
|
|
2119
|
+
this.notifyOptions = opts.notify ?? {};
|
|
1950
2120
|
}
|
|
1951
2121
|
get dedup() {
|
|
1952
2122
|
return this.deduper;
|
|
@@ -1994,7 +2164,8 @@ class Dispatcher {
|
|
|
1994
2164
|
prompt: plan.prompt,
|
|
1995
2165
|
agent: plan.agent,
|
|
1996
2166
|
sessionId: plan.sessionId,
|
|
1997
|
-
onProgress
|
|
2167
|
+
onProgress,
|
|
2168
|
+
mcpEnv: notifyEnvFromDispatch({ chatId, profile }, this.notifyOptions)
|
|
1998
2169
|
}), (r) => r.error);
|
|
1999
2170
|
const card = earlyCard ? await earlyCard : await this.trace("dispatch.openCard", () => this.openCard({
|
|
2000
2171
|
chatId,
|
|
@@ -2599,7 +2770,7 @@ function makeCursorRunner(opts = {}) {
|
|
|
2599
2770
|
let workspaceDir = opts.workspaceDir;
|
|
2600
2771
|
if (!workspaceDir && pluginDir) {
|
|
2601
2772
|
try {
|
|
2602
|
-
workspaceDir = createCursorWorkspace(pluginDir,
|
|
2773
|
+
workspaceDir = createCursorWorkspace(pluginDir, collectMcpNotifyEnv());
|
|
2603
2774
|
} catch {
|
|
2604
2775
|
workspaceDir = undefined;
|
|
2605
2776
|
}
|
|
@@ -2610,18 +2781,18 @@ function makeCursorRunner(opts = {}) {
|
|
|
2610
2781
|
process.once("SIGINT", cleanup);
|
|
2611
2782
|
process.once("SIGTERM", cleanup);
|
|
2612
2783
|
}
|
|
2613
|
-
const refreshMcpConfig = () => {
|
|
2784
|
+
const refreshMcpConfig = (env) => {
|
|
2614
2785
|
if (!workspaceDir || !pluginDir)
|
|
2615
2786
|
return;
|
|
2616
2787
|
if (opts.workspaceDir)
|
|
2617
2788
|
return;
|
|
2618
2789
|
try {
|
|
2619
|
-
writeCursorMcpConfig(workspaceDir, pluginDir, opts.mcpEnv ?? collectMcpNotifyEnv());
|
|
2790
|
+
writeCursorMcpConfig(workspaceDir, pluginDir, env ?? opts.mcpEnv ?? collectMcpNotifyEnv());
|
|
2620
2791
|
} catch {}
|
|
2621
2792
|
};
|
|
2622
2793
|
const addDirs = opts.addDirs ?? [process.cwd()];
|
|
2623
2794
|
return async function runAgent(input) {
|
|
2624
|
-
refreshMcpConfig();
|
|
2795
|
+
refreshMcpConfig(input.mcpEnv);
|
|
2625
2796
|
const known = input.sessionId ? sessions.get(input.sessionId) : undefined;
|
|
2626
2797
|
const args = buildCursorArgs({
|
|
2627
2798
|
prompt: input.prompt,
|
|
@@ -3263,7 +3434,7 @@ function formatStatus(report) {
|
|
|
3263
3434
|
}
|
|
3264
3435
|
|
|
3265
3436
|
// ../../../packages/cli/src/main.ts
|
|
3266
|
-
var VERSION = "0.7.
|
|
3437
|
+
var VERSION = "0.7.7";
|
|
3267
3438
|
async function main(argv) {
|
|
3268
3439
|
const parsed = parseArgs(argv);
|
|
3269
3440
|
switch (parsed.command) {
|