@ai-setting/roy-task-plugin 0.7.6 → 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 +52 -9
- package/dist/cli.js +218 -54
- package/dist/index.js +330 -230
- package/dist/index.js.map +10 -8
- 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
|
|
|
@@ -68,12 +92,32 @@ transitions*: RED→GREEN, blocker found, decision made, review passed.
|
|
|
68
92
|
### 5. Close — `task_complete`
|
|
69
93
|
Before completing:
|
|
70
94
|
1. `task_get(task_id)` and re-read goal + delivery.
|
|
71
|
-
2.
|
|
72
|
-
|
|
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.
|
|
73
99
|
3. Call `task_complete` with `deliverable_storage_path` — a locator for the
|
|
74
100
|
work product: a file path, a git branch/worktree, a doc URL, a commit URL.
|
|
75
101
|
The path is persisted into the task context and as an operation record.
|
|
76
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
|
+
|
|
77
121
|
`task_complete` cannot be used on `cycle` tasks (they are periodic by
|
|
78
122
|
design) — use `task_update` to reset those.
|
|
79
123
|
|
|
@@ -94,6 +138,12 @@ configured with `persistence: "remote"`). Do not create side databases, do
|
|
|
94
138
|
not maintain your own to-do list in files, and do not track work in chat only:
|
|
95
139
|
if it is not in the task store, it is not tracked.
|
|
96
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.
|
|
97
147
|
## Tool surface on Claude Code
|
|
98
148
|
|
|
99
149
|
The `task_*` tools are served by the **`roy-task` MCP server** this runtime
|
|
@@ -118,10 +168,3 @@ Measured: it has no way to write a milestone (its `operations <id>` subcommand
|
|
|
118
168
|
is a *reader*), and it targets a different database than this server. Use the
|
|
119
169
|
MCP tools above; if they are genuinely unavailable, say so in your answer
|
|
120
170
|
instead of silently skipping the lifecycle.
|
|
121
|
-
|
|
122
|
-
## Style
|
|
123
|
-
|
|
124
|
-
- Be direct and concrete; the user is reading task titles and milestones.
|
|
125
|
-
- Report the task id and its state whenever you finish a step.
|
|
126
|
-
- If a task is blocked, mark it (`task_update(status="paused")`) and record a
|
|
127
|
-
`problem` milestone explaining the blocker instead of going silent.
|
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
|
|
|
@@ -533,12 +532,32 @@ transitions*: RED→GREEN, blocker found, decision made, review passed.
|
|
|
533
532
|
### 5. Close — \`task_complete\`
|
|
534
533
|
Before completing:
|
|
535
534
|
1. \`task_get(task_id)\` and re-read goal + delivery.
|
|
536
|
-
2.
|
|
537
|
-
|
|
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.
|
|
538
539
|
3. Call \`task_complete\` with \`deliverable_storage_path\` — a locator for the
|
|
539
540
|
work product: a file path, a git branch/worktree, a doc URL, a commit URL.
|
|
540
541
|
The path is persisted into the task context and as an operation record.
|
|
541
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
|
+
|
|
542
561
|
\`task_complete\` cannot be used on \`cycle\` tasks (they are periodic by
|
|
543
562
|
design) — use \`task_update\` to reset those.
|
|
544
563
|
|
|
@@ -559,6 +578,13 @@ configured with \`persistence: "remote"\`). Do not create side databases, do
|
|
|
559
578
|
not maintain your own to-do list in files, and do not track work in chat only:
|
|
560
579
|
if it is not in the task store, it is not tracked.
|
|
561
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 = `
|
|
562
588
|
## Tool surface on Claude Code
|
|
563
589
|
|
|
564
590
|
The \`task_*\` tools are served by the **\`roy-task\` MCP server** this runtime
|
|
@@ -582,14 +608,35 @@ progress card renders the task these tools create.
|
|
|
582
608
|
Measured: it has no way to write a milestone (its \`operations <id>\` subcommand
|
|
583
609
|
is a *reader*), and it targets a different database than this server. Use the
|
|
584
610
|
MCP tools above; if they are genuinely unavailable, say so in your answer
|
|
585
|
-
instead of silently skipping the lifecycle
|
|
611
|
+
instead of silently skipping the lifecycle.`;
|
|
586
612
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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;
|
|
593
640
|
function buildClaudeAgentsJson(opts = {}) {
|
|
594
641
|
const def = {
|
|
595
642
|
description: opts.description ?? ROY_AGENT_DESCRIPTION,
|
|
@@ -961,13 +1008,17 @@ function resolveClaudePluginDir(roots, explicit) {
|
|
|
961
1008
|
}
|
|
962
1009
|
return;
|
|
963
1010
|
}
|
|
964
|
-
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;
|
|
965
1019
|
return JSON.stringify({
|
|
966
1020
|
mcpServers: {
|
|
967
|
-
"roy-task":
|
|
968
|
-
command: process.execPath,
|
|
969
|
-
args: [join(pluginDir, "mcp", "roy-task-mcp-server.mjs")]
|
|
970
|
-
}
|
|
1021
|
+
"roy-task": server
|
|
971
1022
|
}
|
|
972
1023
|
});
|
|
973
1024
|
}
|
|
@@ -995,8 +1046,8 @@ function makeClaudeRunner(opts = {}) {
|
|
|
995
1046
|
const agentsJson = opts.agentsJson ?? buildClaudeAgentsJson();
|
|
996
1047
|
const sessions = opts.sessions ?? createClaudeSessionRegistry();
|
|
997
1048
|
const pluginDir = opts.pluginDir ?? resolveClaudePluginDir([dirname(fileURLToPath(import.meta.url))]);
|
|
998
|
-
const mcpConfig = pluginDir ? buildRoyTaskMcpConfig(pluginDir) : undefined;
|
|
999
1049
|
return async function runAgent(input) {
|
|
1050
|
+
const mcpConfig = pluginDir ? buildRoyTaskMcpConfig(pluginDir, input.mcpEnv) : undefined;
|
|
1000
1051
|
const known = input.sessionId ? sessions.get(input.sessionId) : undefined;
|
|
1001
1052
|
const args = buildClaudeArgs({
|
|
1002
1053
|
prompt: input.prompt,
|
|
@@ -1105,7 +1156,7 @@ var CODEX_SANDBOX_MODES = Object.freeze([
|
|
|
1105
1156
|
function isCodexSandboxMode(value) {
|
|
1106
1157
|
return CODEX_SANDBOX_MODES.includes(value);
|
|
1107
1158
|
}
|
|
1108
|
-
function buildRoyTaskCodexConfigArgs(pluginDir) {
|
|
1159
|
+
function buildRoyTaskCodexConfigArgs(pluginDir, env) {
|
|
1109
1160
|
const args = [
|
|
1110
1161
|
`-cmcp_servers.${ROY_TASK_MCP_SERVER}.command=${JSON.stringify(process.execPath)}`,
|
|
1111
1162
|
`-cmcp_servers.${ROY_TASK_MCP_SERVER}.args=${JSON.stringify([
|
|
@@ -1113,12 +1164,12 @@ function buildRoyTaskCodexConfigArgs(pluginDir) {
|
|
|
1113
1164
|
])}`,
|
|
1114
1165
|
`-cmcp_servers.${ROY_TASK_MCP_SERVER}.default_tools_approval_mode="approve"`
|
|
1115
1166
|
];
|
|
1116
|
-
const envArg = buildMcpServerEnvArg();
|
|
1167
|
+
const envArg = buildMcpServerEnvArg(env);
|
|
1117
1168
|
if (envArg)
|
|
1118
1169
|
args.push(envArg);
|
|
1119
1170
|
return args;
|
|
1120
1171
|
}
|
|
1121
|
-
function buildMcpServerEnvArg() {
|
|
1172
|
+
function buildMcpServerEnvArg(env) {
|
|
1122
1173
|
const keys = [
|
|
1123
1174
|
"ROY_TASK_NOTIFY_ENABLED",
|
|
1124
1175
|
"ROY_TASK_NOTIFY_CHAT_ID",
|
|
@@ -1126,7 +1177,8 @@ function buildMcpServerEnvArg() {
|
|
|
1126
1177
|
];
|
|
1127
1178
|
const pairs = [];
|
|
1128
1179
|
for (const key of keys) {
|
|
1129
|
-
const
|
|
1180
|
+
const fromEnv = env ? env[key] : undefined;
|
|
1181
|
+
const value = fromEnv ?? (env ? undefined : process.env[key]);
|
|
1130
1182
|
if (!value)
|
|
1131
1183
|
continue;
|
|
1132
1184
|
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
@@ -1166,8 +1218,8 @@ function makeCodexRunner(opts = {}) {
|
|
|
1166
1218
|
const sandbox = opts.sandbox ?? "read-only";
|
|
1167
1219
|
const sessions = opts.sessions ?? createCodexSessionRegistry();
|
|
1168
1220
|
const pluginDir = opts.pluginDir ?? resolveClaudePluginDir([dirname2(fileURLToPath2(import.meta.url))]);
|
|
1169
|
-
const mcpConfigArgs = pluginDir ? buildRoyTaskCodexConfigArgs(pluginDir) : undefined;
|
|
1170
1221
|
return async function runAgent(input) {
|
|
1222
|
+
const mcpConfigArgs = pluginDir ? buildRoyTaskCodexConfigArgs(pluginDir, input.mcpEnv) : undefined;
|
|
1171
1223
|
const known = input.sessionId ? sessions.get(input.sessionId) : undefined;
|
|
1172
1224
|
const args = known ? buildCodexResumeArgs(known, { prompt: input.prompt, model, sandbox, mcpConfigArgs }) : buildCodexArgs({ prompt: input.prompt, model, sandbox, mcpConfigArgs });
|
|
1173
1225
|
const decoder = new CodexStreamDecoder;
|
|
@@ -1754,6 +1806,92 @@ function withTimeout(op, promise, ms) {
|
|
|
1754
1806
|
});
|
|
1755
1807
|
});
|
|
1756
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
|
+
}
|
|
1757
1895
|
function createRoyAgentCardPort(options = {}) {
|
|
1758
1896
|
const opTimeoutMs = options.opTimeoutMs ?? CARD_OP_TIMEOUT_MS;
|
|
1759
1897
|
let notifier;
|
|
@@ -1783,13 +1921,18 @@ function createRoyAgentCardPort(options = {}) {
|
|
|
1783
1921
|
return {
|
|
1784
1922
|
enabled: true,
|
|
1785
1923
|
async start(input) {
|
|
1924
|
+
const adopted = await adoptExistingCard(input);
|
|
1925
|
+
if (adopted)
|
|
1926
|
+
return adopted;
|
|
1786
1927
|
const n = await withTimeout("load", getNotifier(), opTimeoutMs);
|
|
1787
|
-
|
|
1928
|
+
const res = await withTimeout("start", n.startTask({
|
|
1788
1929
|
chatId: input.chatId,
|
|
1789
1930
|
profile: input.profile,
|
|
1790
1931
|
meta: input.meta,
|
|
1791
1932
|
milestones: input.milestones
|
|
1792
1933
|
}), opTimeoutMs);
|
|
1934
|
+
await recordCardInLedger(input, res);
|
|
1935
|
+
return res;
|
|
1793
1936
|
},
|
|
1794
1937
|
async update(input) {
|
|
1795
1938
|
const n = await withTimeout("load", getNotifier(), opTimeoutMs);
|
|
@@ -1815,6 +1958,7 @@ function createRoyAgentCardPort(options = {}) {
|
|
|
1815
1958
|
finalStep: input.finalStep,
|
|
1816
1959
|
progress: input.progress
|
|
1817
1960
|
}), opTimeoutMs);
|
|
1961
|
+
await closeCardInLedger(input);
|
|
1818
1962
|
}
|
|
1819
1963
|
};
|
|
1820
1964
|
}
|
|
@@ -1875,6 +2019,23 @@ function normaliseMcpToolName(tool) {
|
|
|
1875
2019
|
return tool;
|
|
1876
2020
|
}
|
|
1877
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
|
+
|
|
1878
2039
|
// ../../../packages/cli/src/daemon/dispatcher.ts
|
|
1879
2040
|
function cardReport(handle) {
|
|
1880
2041
|
const out = {};
|
|
@@ -1950,10 +2111,12 @@ class Dispatcher {
|
|
|
1950
2111
|
deps;
|
|
1951
2112
|
opts;
|
|
1952
2113
|
deduper;
|
|
2114
|
+
notifyOptions;
|
|
1953
2115
|
constructor(deps, opts = {}) {
|
|
1954
2116
|
this.deps = deps;
|
|
1955
2117
|
this.opts = opts;
|
|
1956
2118
|
this.deduper = deps.deduper ?? new Deduper;
|
|
2119
|
+
this.notifyOptions = opts.notify ?? {};
|
|
1957
2120
|
}
|
|
1958
2121
|
get dedup() {
|
|
1959
2122
|
return this.deduper;
|
|
@@ -2001,7 +2164,8 @@ class Dispatcher {
|
|
|
2001
2164
|
prompt: plan.prompt,
|
|
2002
2165
|
agent: plan.agent,
|
|
2003
2166
|
sessionId: plan.sessionId,
|
|
2004
|
-
onProgress
|
|
2167
|
+
onProgress,
|
|
2168
|
+
mcpEnv: notifyEnvFromDispatch({ chatId, profile }, this.notifyOptions)
|
|
2005
2169
|
}), (r) => r.error);
|
|
2006
2170
|
const card = earlyCard ? await earlyCard : await this.trace("dispatch.openCard", () => this.openCard({
|
|
2007
2171
|
chatId,
|
|
@@ -2606,7 +2770,7 @@ function makeCursorRunner(opts = {}) {
|
|
|
2606
2770
|
let workspaceDir = opts.workspaceDir;
|
|
2607
2771
|
if (!workspaceDir && pluginDir) {
|
|
2608
2772
|
try {
|
|
2609
|
-
workspaceDir = createCursorWorkspace(pluginDir,
|
|
2773
|
+
workspaceDir = createCursorWorkspace(pluginDir, collectMcpNotifyEnv());
|
|
2610
2774
|
} catch {
|
|
2611
2775
|
workspaceDir = undefined;
|
|
2612
2776
|
}
|
|
@@ -2617,18 +2781,18 @@ function makeCursorRunner(opts = {}) {
|
|
|
2617
2781
|
process.once("SIGINT", cleanup);
|
|
2618
2782
|
process.once("SIGTERM", cleanup);
|
|
2619
2783
|
}
|
|
2620
|
-
const refreshMcpConfig = () => {
|
|
2784
|
+
const refreshMcpConfig = (env) => {
|
|
2621
2785
|
if (!workspaceDir || !pluginDir)
|
|
2622
2786
|
return;
|
|
2623
2787
|
if (opts.workspaceDir)
|
|
2624
2788
|
return;
|
|
2625
2789
|
try {
|
|
2626
|
-
writeCursorMcpConfig(workspaceDir, pluginDir, opts.mcpEnv ?? collectMcpNotifyEnv());
|
|
2790
|
+
writeCursorMcpConfig(workspaceDir, pluginDir, env ?? opts.mcpEnv ?? collectMcpNotifyEnv());
|
|
2627
2791
|
} catch {}
|
|
2628
2792
|
};
|
|
2629
2793
|
const addDirs = opts.addDirs ?? [process.cwd()];
|
|
2630
2794
|
return async function runAgent(input) {
|
|
2631
|
-
refreshMcpConfig();
|
|
2795
|
+
refreshMcpConfig(input.mcpEnv);
|
|
2632
2796
|
const known = input.sessionId ? sessions.get(input.sessionId) : undefined;
|
|
2633
2797
|
const args = buildCursorArgs({
|
|
2634
2798
|
prompt: input.prompt,
|
|
@@ -3270,7 +3434,7 @@ function formatStatus(report) {
|
|
|
3270
3434
|
}
|
|
3271
3435
|
|
|
3272
3436
|
// ../../../packages/cli/src/main.ts
|
|
3273
|
-
var VERSION = "0.7.
|
|
3437
|
+
var VERSION = "0.7.7";
|
|
3274
3438
|
async function main(argv) {
|
|
3275
3439
|
const parsed = parseArgs(argv);
|
|
3276
3440
|
switch (parsed.command) {
|