@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.
@@ -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. Verify the goal and delivery actually match what you delivered. If they do
72
- not, either extend the task (`task_update`) or say so honestly.
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
- // ../../../packages/cli/src/daemon/runners/claude-agents.ts
448
- var CLAUDE_ROY_AGENT_NAME = "roy";
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. Verify the goal and delivery actually match what you delivered. If they do
537
- not, either extend the task (\`task_update\`) or say so honestly.
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
- ## Style
588
-
589
- - Be direct and concrete; the user is reading task titles and milestones.
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 record a
592
- \`problem\` milestone explaining the blocker instead of going silent.`;
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 value = process.env[key];
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
- return withTimeout("start", n.startTask({
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, opts.mcpEnv ?? collectMcpNotifyEnv());
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.6";
3437
+ var VERSION = "0.7.7";
3274
3438
  async function main(argv) {
3275
3439
  const parsed = parseArgs(argv);
3276
3440
  switch (parsed.command) {