@mushi-mushi/plugin-cursor-cloud 0.5.1 → 0.6.1

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/README.md CHANGED
@@ -9,12 +9,14 @@ Part of the Mushi Mushi monorepo — plain-English bug comprehension for vibe co
9
9
  [![license](https://img.shields.io/npm/l/@mushi-mushi/plugin-cursor-cloud)](./LICENSE)
10
10
  [![types](https://img.shields.io/npm/types/@mushi-mushi/plugin-cursor-cloud)](./src/index.ts)
11
11
 
12
- > Mushi Mushi plugin: dispatch a [Cursor Cloud Agent](https://cursor.com/docs/cloud-agent/api/v0) when a critical bug is classified or a fix is requested. The agent investigates, opens a draft PR, and reports back through the Cursor REST API.
12
+ > Mushi Mushi plugin: dispatch a [Cursor Cloud Agent](https://cursor.com/docs/cloud-agent/api/endpoints) when a critical bug is classified or a fix is requested. The agent investigates, opens a draft PR, and reports back through the Cursor REST API (**v1**).
13
13
 
14
14
  This is the **opt-in marketplace** path. Teams install it in the Mushi admin
15
- console, configure their Cursor API key + workspace ID + repo URL, and pick a
16
- severity threshold. For the **project-wide default** Cursor agent path (no
17
- plugin install needed), see `@mushi-mushi/agents`'s `cursor-cloud` adapter.
15
+ console, configure their Cursor API key + repo URL, and pick a severity
16
+ threshold. For the **project-wide default** Cursor agent path (no plugin
17
+ install needed) set `autofix_agent = 'cursor_cloud'` in the Mushi console —
18
+ Mushi's own fix-worker then dispatches every fix through the same v1 API,
19
+ polls it to completion, and closes the loop on the draft PR.
18
20
 
19
21
  ## Why this exists
20
22
 
@@ -28,7 +30,7 @@ The flow:
28
30
  ```
29
31
  Mushi report → classify-report → report.classified (severity=critical)
30
32
  → @mushi-mushi/plugin-cursor-cloud
31
- → POST https://api.cursor.com/v0/agents
33
+ → POST https://api.cursor.com/v1/agents
32
34
  → Cursor cloud agent investigates → opens draft PR
33
35
  → audit log to Mushi console
34
36
  ```
@@ -42,7 +44,13 @@ This plugin spends real Cursor API credit. Three hard guards are baked in:
42
44
  2. **Repo gate** — silently no-ops when no `repoUrl` is configured.
43
45
  3. **Idempotent retries** — `withRetry` from `@mushi-mushi/plugin-sdk`
44
46
  honours `Retry-After` and treats 4xx (other than 429) as non-retryable
45
- so a bad config never burns money in a tight loop.
47
+ so a bad config never burns money in a tight loop. On top of that every
48
+ dispatch sends a deterministic `agentId` (`bc-<uuid>` derived from the
49
+ event + report id), so a redelivered Mushi event gets Cursor's
50
+ `409 agent_id_conflict` instead of a second agent.
51
+ 4. **No double dispatch** — when Mushi's own fix-worker already handed the
52
+ report to Cursor (`autofix_agent = 'cursor_cloud'`), the `fix.requested`
53
+ payload carries `data.fix.externalAgentId` and this plugin logs and skips.
46
54
 
47
55
  ## Install
48
56
 
@@ -57,16 +65,15 @@ import { createCursorCloudPlugin } from '@mushi-mushi/plugin-cursor-cloud'
57
65
 
58
66
  const handler = createCursorCloudPlugin({
59
67
  apiKey: process.env.CURSOR_API_KEY!,
60
- workspaceId: process.env.CURSOR_WORKSPACE_ID!,
61
68
  webhookSecret: process.env.MUSHI_PLUGIN_WEBHOOK_SECRET!,
62
69
  repoUrl: 'https://github.com/your-org/your-repo',
63
70
  severityThreshold: 'critical',
64
- // Optional — default is composer-2.5.
71
+ // Optional — branch/ref the agent starts from (repos[].startingRef).
72
+ startingRef: 'main',
73
+ // Optional — default is composer-2.5 (sent as model.id).
65
74
  model: 'composer-2.5',
66
- // Optional — default is true (auto-open draft PR).
75
+ // Optional — default is true (top-level autoCreatePR; Cursor opens drafts).
67
76
  autoCreatePR: true,
68
- // Optional — default is 1 (single iteration before reporting back).
69
- maxIterations: 1,
70
77
  })
71
78
 
72
79
  // Wire to your serverless platform — Mushi calls handler with a Standard
@@ -78,26 +85,47 @@ export default handler
78
85
 
79
86
  | Field | Type | Required | Default | Description |
80
87
  |---|---|---|---|---|
81
- | `apiKey` | string | yes | — | Your Cursor API key (`cur_…`). |
82
- | `workspaceId` | string | yes | — | Your Cursor workspace ID (`ws_…`). |
88
+ | `apiKey` | string | yes | — | Your Cursor API key (`crsr_…`). |
83
89
  | `webhookSecret` | string | yes* | `MUSHI_PLUGIN_WEBHOOK_SECRET` | Standard Webhooks HMAC secret for inbound Mushi events. *Required for self-hosted installs. |
84
- | `repoUrl` | string | recommended | — | Target repo URL. If omitted, the plugin silently no-ops. |
90
+ | `repoUrl` | string | recommended | — | Target repo URL (`https://github.com/org/repo`, sent as `repos[].url`). If omitted, the plugin silently no-ops. |
91
+ | `startingRef` | string | no | repo default branch | Branch/ref the agent starts from (`repos[].startingRef`). |
85
92
  | `severityThreshold` | `'low' \| 'medium' \| 'high' \| 'critical'` | no | `'critical'` | Minimum severity that triggers a run. |
86
- | `model` | string | no | `'composer-2.5'` | Cursor model slug. |
87
- | `autoCreatePR` | boolean | no | `true` | Whether the agent should open a draft PR. |
88
- | `maxIterations` | number | no | `1` | Max agent iterations per run. |
93
+ | `model` | string | no | `'composer-2.5'` | Cursor model id (`model.id`). |
94
+ | `autoCreatePR` | boolean | no | `true` | Whether Cursor should open a (draft) PR — top-level `autoCreatePR` in v1. |
95
+ | `apiBase` | string | no | `https://api.cursor.com` | Override for proxies / tests. |
89
96
  | `fetchImpl` | `typeof fetch` | no | global `fetch` | Override for tests. |
97
+ | `workspaceId` | string | deprecated | — | Not used by the v1 API; accepted so old configs still type-check. |
98
+ | `maxIterations` | number | deprecated | — | v1 has no iteration cap; ignored. |
90
99
 
91
100
  ## Events handled
92
101
 
93
102
  | Event | Action |
94
103
  |---|---|
95
104
  | `report.classified` | Dispatch a run when `data.classification.severity` ≥ `severityThreshold`. |
96
- | `fix.requested` | Always dispatch a run (the user explicitly asked for a fix). |
105
+ | `fix.requested` | Dispatch a run regardless of severity — unless `data.fix.externalAgentId` is set (Mushi's fix-worker already dispatched this fix to Cursor). |
97
106
  | `qa_story.failed` | Dispatch a run when a QA story run fails all its assertions. Requires `repoUrl`. |
107
+ | `skill_pipeline.step.dispatched` | Run one skill-pipeline step and check the `agentId` back in via `/v1/admin/skills/pipelines/:run/steps/:idx/checkin`. |
98
108
 
99
109
  Other events are ignored.
100
110
 
111
+ ## v0 vs v1
112
+
113
+ Cursor serves two Cloud Agents APIs. This plugin (since 0.6.0) and Mushi's
114
+ fix-worker speak **v1** (`POST /v1/agents`):
115
+
116
+ | | v0 (legacy, still served) | v1 (current) |
117
+ |---|---|---|
118
+ | Request | `{ prompt:{text}, source:{repository,ref}, target:{autoCreatePr,branchName,skipReviewerRequest}, model, webhook:{url,secret} }` | `{ prompt:{text}, repos:[{url,startingRef}], autoCreatePR, agentId:'bc-<uuid>', name, model:{id}, skipReviewerRequest }` |
119
+ | Response | `{ id, status, target:{url,branchName,prUrl} }` | `{ agent:{id,status,url,latestRunId}, run:{id,status,git:{branches:[{repoUrl,branch,prUrl}]}} }` |
120
+ | Idempotency | none | `agentId` reuse ⇒ `409 agent_id_conflict` |
121
+ | Branch name | `target.branchName` | not settable — ask for it in the prompt |
122
+ | Completion | `webhook` (`X-Webhook-Signature: sha256=<hex>`) on FINISHED/ERROR | poll `GET /v1/agents/{id}/runs/{runId}` (webhooks "coming soon") |
123
+
124
+ Mushi's server keeps a v0 code path behind `CURSOR_USE_V0_WEBHOOK=1` purely
125
+ for the push webhook (its `cursor-webhook` edge function verifies the
126
+ signature); this npm plugin has no completion tracking of its own and uses
127
+ v1 only.
128
+
101
129
  ## ⚠️ Breaking change in v0.3.0 — `webhookSecret` required
102
130
 
103
131
  Previous versions derived the HMAC secret from `workspaceId` internally,
@@ -136,9 +164,10 @@ The test suite covers:
136
164
 
137
165
  - Severity gate filters out anything below `severityThreshold` (no API call).
138
166
  - `repoUrl` missing → silent no-op.
139
- - `report.classified` with `severity: critical` → exactly one Cursor API call.
140
- - `fix.requested` → exactly one Cursor API call regardless of severity.
141
- - Retries on 503; bails on 401 (no money burned on bad keys).
167
+ - `report.classified` with `severity: critical` → exactly one `POST /v1/agents` with the documented v1 body.
168
+ - `fix.requested` → exactly one Cursor API call regardless of severity, and none when `externalAgentId` is already set.
169
+ - `409 agent_id_conflict` on a redelivered event is treated as success.
170
+ - Bails on 401 (no money burned on bad keys).
142
171
 
143
172
  ## License
144
173
 
@@ -148,4 +177,4 @@ MIT — see [LICENSE](./LICENSE).
148
177
  <!-- mushi-readme-stats-footer -->
149
178
  ---
150
179
 
151
- <sub>Monorepo scale (June 2026): 51 edge functions · 298 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
180
+ <sub>Monorepo scale (July 2026): 59 edge functions · 374 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
package/dist/index.d.ts CHANGED
@@ -4,36 +4,50 @@ import * as _mushi_mushi_plugin_sdk from '@mushi-mushi/plugin-sdk';
4
4
  * Cursor Cloud Agent plugin for Mushi Mushi.
5
5
  *
6
6
  * Path A: Marketplace plugin — subscribes to `report.classified`,
7
- * `qa_story.failed`, and `fix.requested`. When a qualifying event fires,
8
- * this plugin calls Cursor's HTTP REST API directly (no @cursor/sdk —
9
- * runs from Deno edge functions and Node alike) to create a Cloud Agent
10
- * run, and logs the resulting `agentId` + `prUrl` to the Mushi console.
7
+ * `qa_story.failed`, `fix.requested` and `skill_pipeline.step.dispatched`.
8
+ * When a qualifying event fires, this plugin calls Cursor's HTTP REST API
9
+ * directly (no @cursor/sdk — runs from Deno edge functions and Node alike)
10
+ * to create a Cloud Agent, and logs the resulting `agentId` + `prUrl` to the
11
+ * Mushi console.
11
12
  *
12
13
  * This is the *opt-in* path: teams install it from the Marketplace and
13
- * configure their Cursor API key + workspace ID in the plugin settings.
14
- * For the project-wide *default* Cursor agent path, see Path B in
15
- * packages/agents/src/adapters/cursor-cloud.ts.
14
+ * configure their Cursor API key + repo URL in the plugin settings. The
15
+ * project-wide *default* Cursor path is `autofix_agent = 'cursor_cloud'`,
16
+ * dispatched by Mushi's own fix-worker; when THAT path emits
17
+ * `fix.requested` the payload carries `data.fix.externalAgentId` and this
18
+ * plugin deliberately does not start a second agent.
16
19
  *
17
- * Cursor REST API docs: https://cursor.com/docs/cloud-agent/api/v0.md
20
+ * API: Cursor Cloud Agents **v1** — https://cursor.com/docs/cloud-agent/api/endpoints
21
+ * POST https://api.cursor.com/v1/agents
22
+ * { prompt: { text }, repos: [{ url, startingRef? }], autoCreatePR (top-level),
23
+ * agentId?: 'bc-<uuid>' (idempotency; re-use ⇒ 409 agent_id_conflict),
24
+ * name?, model?: { id }, skipReviewerRequest? }
25
+ * → { agent: { id, status, url }, run: { id, status, git: { branches: [{ repoUrl, branch, prUrl }] } } }
26
+ * v0 (`source` / `target.autoCreatePr` / `webhook`) is still served by
27
+ * Cursor but no longer used here — see the README section "v0 vs v1".
18
28
  */
29
+ declare const CURSOR_API_BASE = "https://api.cursor.com";
30
+ declare const CURSOR_DEFAULT_MODEL = "composer-2.5";
19
31
  interface CursorCloudPluginConfig {
20
32
  /** Cursor API key (crsr_…). */
21
33
  apiKey: string;
22
- /** @deprecated No longer required by Cursor Cloud Agents API. */
34
+ /** @deprecated Not used by the Cursor Cloud Agents v1 API; accepted for back-compat. */
23
35
  workspaceId?: string;
24
- /** Cursor model slug. Defaults to `composer-2.5`. */
36
+ /** Cursor model id (`model.id`). Defaults to `composer-2.5`. */
25
37
  model?: string;
26
- /** Whether the agent should automatically open a draft PR. Defaults to true. */
38
+ /** Whether Cursor should automatically open a (draft) PR. Defaults to true. */
27
39
  autoCreatePR?: boolean;
28
- /** Maximum iterations for the agent. Defaults to 1. */
40
+ /** @deprecated v1 has no iteration cap; kept so existing configs still type-check. */
29
41
  maxIterations?: number;
30
42
  /** Lowest severity that triggers a Cursor agent run. Defaults to `critical`. */
31
43
  severityThreshold?: 'low' | 'medium' | 'high' | 'critical';
32
44
  /**
33
- * URL of the target repository the Cursor agent will work in.
34
- * If not set, the plugin will attempt to read it from the event payload.
45
+ * URL of the target repository the Cursor agent will work in
46
+ * (`https://github.com/org/repo`). If not set, the plugin no-ops.
35
47
  */
36
48
  repoUrl?: string;
49
+ /** Branch/ref the agent starts from (`repos[].startingRef`). Defaults to the repo default branch. */
50
+ startingRef?: string;
37
51
  /**
38
52
  * Standard Webhooks secret for inbound Mushi events. Required for
39
53
  * self-hosted installs — never derive this from workspaceId.
@@ -41,10 +55,14 @@ interface CursorCloudPluginConfig {
41
55
  webhookSecret?: string;
42
56
  /** Override `fetch` for tests. */
43
57
  fetchImpl?: typeof fetch;
58
+ /** Override the API base (tests / proxies). */
59
+ apiBase?: string;
44
60
  }
45
61
  interface CursorAgentRunResponse {
46
62
  agentId: string;
63
+ runId?: string;
47
64
  status: string;
65
+ agentUrl?: string;
48
66
  prUrl?: string;
49
67
  }
50
68
  interface CursorDispatchResult {
@@ -52,7 +70,24 @@ interface CursorDispatchResult {
52
70
  reportId: string;
53
71
  model: string;
54
72
  }
73
+ /** Wire body for POST /v1/agents — exported so tests can pin the exact shape. */
74
+ declare function buildCreateAgentBody(input: {
75
+ prompt: string;
76
+ repoUrl: string;
77
+ startingRef?: string;
78
+ model: string;
79
+ autoCreatePR: boolean;
80
+ agentId?: string;
81
+ name?: string;
82
+ }): Record<string, unknown>;
83
+ /**
84
+ * `bc-<uuid>` derived from a stable seed (the Mushi delivery id) so a retried
85
+ * delivery re-uses the agent id and Cursor answers 409 `agent_id_conflict`
86
+ * instead of starting a second agent. RFC 9562 name-based layout over
87
+ * SHA-256 (WebCrypto — available in Node ≥ 20 and Deno).
88
+ */
89
+ declare function deterministicAgentId(seed: string): Promise<string>;
55
90
  /** Build the plugin handler and return a callable function. */
56
91
  declare function createCursorCloudPlugin(cfg: CursorCloudPluginConfig): (input: _mushi_mushi_plugin_sdk.HandlePluginRequestInput) => Promise<_mushi_mushi_plugin_sdk.HandlePluginResult>;
57
92
 
58
- export { type CursorAgentRunResponse, type CursorCloudPluginConfig, type CursorDispatchResult, createCursorCloudPlugin };
93
+ export { CURSOR_API_BASE, CURSOR_DEFAULT_MODEL, type CursorAgentRunResponse, type CursorCloudPluginConfig, type CursorDispatchResult, buildCreateAgentBody, createCursorCloudPlugin, deterministicAgentId };
package/dist/index.js CHANGED
@@ -3,46 +3,78 @@ import {
3
3
  createPluginHandler,
4
4
  withRetry
5
5
  } from "@mushi-mushi/plugin-sdk";
6
- var CURSOR_API_BASE = "https://api.cursor.com/v0";
6
+ var CURSOR_API_BASE = "https://api.cursor.com";
7
+ var CURSOR_DEFAULT_MODEL = "composer-2.5";
7
8
  var SEVERITY_RANK = {
8
9
  low: 1,
9
10
  medium: 2,
10
11
  high: 3,
11
12
  critical: 4
12
13
  };
14
+ function buildCreateAgentBody(input) {
15
+ const body = {
16
+ prompt: { text: input.prompt },
17
+ repos: [{ url: input.repoUrl, ...input.startingRef ? { startingRef: input.startingRef } : {} }],
18
+ autoCreatePR: input.autoCreatePR,
19
+ skipReviewerRequest: true,
20
+ model: { id: input.model }
21
+ };
22
+ if (input.agentId) body.agentId = input.agentId;
23
+ if (input.name) body.name = input.name.slice(0, 100);
24
+ return body;
25
+ }
26
+ async function deterministicAgentId(seed) {
27
+ const data = new TextEncoder().encode(`mushi-mushi:plugin-cursor-cloud:v1\0${seed}`);
28
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", data));
29
+ const b = digest.slice(0, 16);
30
+ b[6] = b[6] & 15 | 80;
31
+ b[8] = b[8] & 63 | 128;
32
+ let hex = "";
33
+ for (let i = 0; i < b.length; i++) hex += b[i].toString(16).padStart(2, "0");
34
+ return `bc-${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
35
+ }
13
36
  async function createCursorAgentRun(cfg, opts, f) {
14
37
  return withRetry(async () => {
15
- const res = await f(`${CURSOR_API_BASE}/agents`, {
38
+ const res = await f(`${cfg.apiBase ?? CURSOR_API_BASE}/v1/agents`, {
16
39
  method: "POST",
17
40
  headers: {
18
41
  "Content-Type": "application/json",
19
42
  Authorization: `Bearer ${cfg.apiKey}`
20
43
  },
21
- body: JSON.stringify({
22
- prompt: { text: opts.prompt },
23
- model: cfg.model || "default",
24
- source: {
25
- repository: opts.repoUrl,
26
- ref: "main"
27
- },
28
- target: {
29
- autoCreatePr: cfg.autoCreatePR,
30
- branchName: opts.branchName ?? `mushi/cursor-${Date.now()}`,
31
- skipReviewerRequest: true
32
- }
33
- })
44
+ body: JSON.stringify(
45
+ buildCreateAgentBody({
46
+ prompt: opts.prompt,
47
+ repoUrl: opts.repoUrl,
48
+ startingRef: cfg.startingRef,
49
+ model: cfg.model || CURSOR_DEFAULT_MODEL,
50
+ autoCreatePR: cfg.autoCreatePR,
51
+ agentId: opts.agentId,
52
+ name: opts.name
53
+ })
54
+ )
34
55
  });
56
+ if (res.status === 409 && opts.agentId) {
57
+ const errBody = await res.json().catch(() => null);
58
+ if (errBody?.error?.code === "agent_id_conflict" || !errBody) {
59
+ return { agentId: opts.agentId, status: "EXISTS" };
60
+ }
61
+ throw res;
62
+ }
35
63
  if (!res.ok) {
36
64
  throw res;
37
65
  }
38
66
  const body = await res.json();
67
+ const prUrl = body.run?.git?.branches?.find((b) => typeof b.prUrl === "string")?.prUrl;
39
68
  return {
40
- agentId: body.agentId ?? body.id ?? "",
41
- status: body.status ?? "queued",
42
- prUrl: body.prUrl
69
+ agentId: body.agent?.id ?? "",
70
+ runId: body.run?.id,
71
+ status: body.run?.status ?? body.agent?.status ?? "CREATING",
72
+ agentUrl: body.agent?.url,
73
+ prUrl
43
74
  };
44
75
  });
45
76
  }
77
+ var DRAFT_PR_INSTRUCTION = "Open the pull request as a DRAFT (Cursor opens drafts by default; keep it that way). Do not refactor unrelated code and never merge.";
46
78
  function buildPromptFromClassifiedReport(envelope, data) {
47
79
  const lines = [
48
80
  `A bug has been reported in your project and classified as ${data.classification.severity?.toUpperCase()}.`,
@@ -58,7 +90,7 @@ function buildPromptFromClassifiedReport(envelope, data) {
58
90
  }
59
91
  lines.push(
60
92
  ``,
61
- `Please investigate this bug, identify the root cause in the codebase, and open a draft PR with a minimal fix. Do not refactor unrelated code.`
93
+ `Please investigate this bug, identify the root cause in the codebase, and open a draft PR with a minimal fix titled "fix: <summary> (MUSHI-${data.report.id})". ${DRAFT_PR_INSTRUCTION}`
62
94
  );
63
95
  return lines.join("\n");
64
96
  }
@@ -68,9 +100,10 @@ function buildPromptFromFixRequested(envelope, fixData) {
68
100
  ``,
69
101
  `Report title: ${fixData.report.title ?? "(untitled)"}`,
70
102
  `Fix ID: ${fixData.fix.id}`,
103
+ fixData.fix.branch ? `Requested branch: ${fixData.fix.branch}` : "",
71
104
  ``,
72
- `Please investigate the reported issue, identify the root cause, and open a draft PR with a minimal fix. Do not refactor unrelated code.`
73
- ].join("\n");
105
+ `Please investigate the reported issue, identify the root cause, and open a draft PR with a minimal fix titled "fix: <summary> (MUSHI-${fixData.report.id})". ${DRAFT_PR_INSTRUCTION}`
106
+ ].filter((line) => line !== "").join("\n");
74
107
  }
75
108
  function buildPromptFromQaStoryFailed(envelope, data) {
76
109
  return [
@@ -80,16 +113,21 @@ function buildPromptFromQaStoryFailed(envelope, data) {
80
113
  `Title: ${data.title ?? "(untitled)"}`,
81
114
  data.failureReason ? `Failure: ${data.failureReason}` : "",
82
115
  ``,
83
- `Please investigate the failing assertion, identify the root cause in the codebase, and open a draft PR with a minimal fix. Do not refactor unrelated code.`
116
+ `Please investigate the failing assertion, identify the root cause in the codebase, and open a draft PR with a minimal fix. ${DRAFT_PR_INSTRUCTION}`
84
117
  ].filter((line) => line !== "").join("\n");
85
118
  }
86
119
  function createCursorCloudPlugin(cfg) {
87
120
  const minRank = SEVERITY_RANK[cfg.severityThreshold ?? "critical"];
88
- const model = cfg.model ?? "composer-2.5";
121
+ const model = cfg.model ?? CURSOR_DEFAULT_MODEL;
89
122
  const autoCreatePR = cfg.autoCreatePR ?? true;
90
- const maxIterations = cfg.maxIterations ?? 1;
91
123
  const f = cfg.fetchImpl ?? fetch;
92
- const resolvedCfg = { apiKey: cfg.apiKey, model, autoCreatePR, maxIterations };
124
+ const resolvedCfg = {
125
+ apiKey: cfg.apiKey,
126
+ model,
127
+ autoCreatePR,
128
+ startingRef: cfg.startingRef,
129
+ apiBase: cfg.apiBase
130
+ };
93
131
  const webhookSecret = cfg.webhookSecret ?? (typeof process !== "undefined" ? process.env.MUSHI_PLUGIN_WEBHOOK_SECRET : void 0);
94
132
  if (!webhookSecret) {
95
133
  throw new Error(
@@ -112,7 +150,9 @@ function createCursorCloudPlugin(cfg) {
112
150
  resolvedCfg,
113
151
  {
114
152
  repoUrl,
115
- prompt: buildPromptFromClassifiedReport(e, data)
153
+ prompt: buildPromptFromClassifiedReport(e, data),
154
+ agentId: await deterministicAgentId(`report.classified:${e.projectId}:${data.report.id}`),
155
+ name: `Mushi report ${data.report.id.slice(0, 8)}`
116
156
  },
117
157
  f
118
158
  );
@@ -121,6 +161,13 @@ function createCursorCloudPlugin(cfg) {
121
161
  },
122
162
  "fix.requested": async (e) => {
123
163
  const data = e.data;
164
+ if (typeof data.fix?.externalAgentId === "string" && data.fix.externalAgentId.length > 0) {
165
+ console.warn(
166
+ "[cursor-cloud] fix.requested already dispatched by Mushi fix-worker",
167
+ JSON.stringify({ agentId: data.fix.externalAgentId, reportId: data.report.id, agent: data.fix.agent })
168
+ );
169
+ return;
170
+ }
124
171
  const repoUrl = cfg.repoUrl ?? "";
125
172
  if (!repoUrl) {
126
173
  console.warn("[cursor-cloud] fix.requested skipped: repoUrl not configured.");
@@ -130,7 +177,9 @@ function createCursorCloudPlugin(cfg) {
130
177
  resolvedCfg,
131
178
  {
132
179
  repoUrl,
133
- prompt: buildPromptFromFixRequested(e, data)
180
+ prompt: buildPromptFromFixRequested(e, data),
181
+ agentId: await deterministicAgentId(`fix.requested:${e.projectId}:${data.fix.id}`),
182
+ name: `Mushi fix ${data.fix.id.slice(0, 8)}`
134
183
  },
135
184
  f
136
185
  );
@@ -148,7 +197,9 @@ function createCursorCloudPlugin(cfg) {
148
197
  resolvedCfg,
149
198
  {
150
199
  repoUrl,
151
- prompt: buildPromptFromQaStoryFailed(e, data)
200
+ prompt: buildPromptFromQaStoryFailed(e, data),
201
+ agentId: await deterministicAgentId(`qa_story.failed:${e.projectId}:${data.storyId ?? e.deliveryId}`),
202
+ name: `Mushi QA story ${(data.storyId ?? "unknown").slice(0, 8)}`
152
203
  },
153
204
  f
154
205
  );
@@ -179,7 +230,9 @@ function createCursorCloudPlugin(cfg) {
179
230
  resolvedCfg,
180
231
  {
181
232
  repoUrl,
182
- prompt: buildPromptFromSkillStep(data)
233
+ prompt: buildPromptFromSkillStep(data),
234
+ agentId: void 0,
235
+ name: `Mushi skill ${data.skillSlug.slice(0, 40)} #${data.stepIndex + 1}`
183
236
  },
184
237
  f
185
238
  ),
@@ -259,12 +312,18 @@ function buildPromptFromSkillStep(data) {
259
312
  ` status: "passed" (or "failed" if you could not complete it)`,
260
313
  ` pr_url: <your PR URL if you opened one>`,
261
314
  ``,
315
+ `Push your work to a branch named \`mushi/skill-${data.skillSlug.slice(0, 24)}-${data.runId.slice(0, 8)}\` and ${DRAFT_PR_INSTRUCTION}`,
316
+ ``,
262
317
  `\u2500\u2500\u2500 Context Packet \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
263
318
  ``,
264
319
  data.contextPacket.slice(0, 32e3)
265
320
  ].join("\n");
266
321
  }
267
322
  export {
268
- createCursorCloudPlugin
323
+ CURSOR_API_BASE,
324
+ CURSOR_DEFAULT_MODEL,
325
+ buildCreateAgentBody,
326
+ createCursorCloudPlugin,
327
+ deterministicAgentId
269
328
  };
270
329
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n// Copyright (c) 2024–2026 Kenji Sakuramoto (kensaurus) — Mushi Mushi\n/**\n * Cursor Cloud Agent plugin for Mushi Mushi.\n *\n * Path A: Marketplace plugin — subscribes to `report.classified`,\n * `qa_story.failed`, and `fix.requested`. When a qualifying event fires,\n * this plugin calls Cursor's HTTP REST API directly (no @cursor/sdk —\n * runs from Deno edge functions and Node alike) to create a Cloud Agent\n * run, and logs the resulting `agentId` + `prUrl` to the Mushi console.\n *\n * This is the *opt-in* path: teams install it from the Marketplace and\n * configure their Cursor API key + workspace ID in the plugin settings.\n * For the project-wide *default* Cursor agent path, see Path B in\n * packages/agents/src/adapters/cursor-cloud.ts.\n *\n * Cursor REST API docs: https://cursor.com/docs/cloud-agent/api/v0.md\n */\n\nimport {\n createPluginHandler,\n withRetry,\n type MushiEventEnvelope,\n type MushiReportClassifiedEvent,\n type MushiFixEvent,\n} from '@mushi-mushi/plugin-sdk'\n\nconst CURSOR_API_BASE = 'https://api.cursor.com/v0'\n\nconst SEVERITY_RANK: Record<string, number> = {\n low: 1,\n medium: 2,\n high: 3,\n critical: 4,\n}\n\nexport interface CursorCloudPluginConfig {\n /** Cursor API key (crsr_…). */\n apiKey: string\n /** @deprecated No longer required by Cursor Cloud Agents API. */\n workspaceId?: string\n /** Cursor model slug. Defaults to `composer-2.5`. */\n model?: string\n /** Whether the agent should automatically open a draft PR. Defaults to true. */\n autoCreatePR?: boolean\n /** Maximum iterations for the agent. Defaults to 1. */\n maxIterations?: number\n /** Lowest severity that triggers a Cursor agent run. Defaults to `critical`. */\n severityThreshold?: 'low' | 'medium' | 'high' | 'critical'\n /**\n * URL of the target repository the Cursor agent will work in.\n * If not set, the plugin will attempt to read it from the event payload.\n */\n repoUrl?: string\n /**\n * Standard Webhooks secret for inbound Mushi events. Required for\n * self-hosted installs — never derive this from workspaceId.\n */\n webhookSecret?: string\n /** Override `fetch` for tests. */\n fetchImpl?: typeof fetch\n}\n\nexport interface CursorAgentRunResponse {\n agentId: string\n status: string\n prUrl?: string\n}\n\nexport interface CursorDispatchResult {\n agentId: string\n reportId: string\n model: string\n}\n\n/**\n * Call Cursor's REST API to create a Cloud Agent run.\n * Returns the agentId so the caller can track the run in Mushi console.\n *\n * On non-2xx the raw Response is thrown so `withRetry` can inspect\n * `.status` and `Retry-After` and apply the documented retry policy:\n * - 429 / 5xx → retry with exponential back-off\n * - 4xx (not 429) → throw immediately (no money burned on bad keys)\n * - network error → retry\n */\nasync function createCursorAgentRun(\n cfg: Required<Pick<CursorCloudPluginConfig, 'apiKey' | 'model' | 'autoCreatePR' | 'maxIterations'>>,\n opts: { repoUrl: string; prompt: string; branchName?: string },\n f: typeof fetch,\n): Promise<CursorAgentRunResponse> {\n return withRetry(async () => {\n const res = await f(`${CURSOR_API_BASE}/agents`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${cfg.apiKey}`,\n },\n body: JSON.stringify({\n prompt: { text: opts.prompt },\n model: cfg.model || 'default',\n source: {\n repository: opts.repoUrl,\n ref: 'main',\n },\n target: {\n autoCreatePr: cfg.autoCreatePR,\n branchName: opts.branchName ?? `mushi/cursor-${Date.now()}`,\n skipReviewerRequest: true,\n },\n }),\n })\n if (!res.ok) {\n // Throw the Response itself so withRetry can read status / Retry-After.\n // This is the contract documented in @mushi-mushi/plugin-sdk/retry.ts.\n throw res\n }\n const body = (await res.json()) as { agentId?: string; id?: string; status?: string; prUrl?: string }\n return {\n agentId: body.agentId ?? body.id ?? '',\n status: body.status ?? 'queued',\n prUrl: body.prUrl,\n }\n })\n}\n\nfunction buildPromptFromClassifiedReport(\n envelope: MushiEventEnvelope,\n data: MushiReportClassifiedEvent,\n): string {\n const lines = [\n `A bug has been reported in your project and classified as ${data.classification.severity?.toUpperCase()}.`,\n ``,\n `Report ID: ${data.report.id}`,\n `Title: ${data.report.title ?? '(untitled)'}`,\n `Category: ${data.report.category ?? data.classification.category}`,\n `Severity: ${data.classification.severity}`,\n `Project: ${envelope.projectId}`,\n ]\n\n if (data.classification.tags?.length) {\n lines.push(`Tags: ${data.classification.tags.join(', ')}`)\n }\n\n lines.push(\n ``,\n `Please investigate this bug, identify the root cause in the codebase, ` +\n `and open a draft PR with a minimal fix. Do not refactor unrelated code.`,\n )\n\n return lines.join('\\n')\n}\n\nfunction buildPromptFromFixRequested(envelope: MushiEventEnvelope, fixData: MushiFixEvent): string {\n return [\n `A fix was requested for report ${fixData.report.id} in project ${envelope.projectId}.`,\n ``,\n `Report title: ${fixData.report.title ?? '(untitled)'}`,\n `Fix ID: ${fixData.fix.id}`,\n ``,\n `Please investigate the reported issue, identify the root cause, ` +\n `and open a draft PR with a minimal fix. Do not refactor unrelated code.`,\n ].join('\\n')\n}\n\nfunction buildPromptFromQaStoryFailed(\n envelope: MushiEventEnvelope,\n data: { storyId?: string; title?: string; failureReason?: string },\n): string {\n return [\n `A QA story failed in project ${envelope.projectId}.`,\n ``,\n `Story ID: ${data.storyId ?? 'unknown'}`,\n `Title: ${data.title ?? '(untitled)'}`,\n data.failureReason ? `Failure: ${data.failureReason}` : '',\n ``,\n `Please investigate the failing assertion, identify the root cause in the codebase, ` +\n `and open a draft PR with a minimal fix. Do not refactor unrelated code.`,\n ].filter((line) => line !== '').join('\\n')\n}\n\n/** Build the plugin handler and return a callable function. */\nexport function createCursorCloudPlugin(cfg: CursorCloudPluginConfig) {\n const minRank = SEVERITY_RANK[cfg.severityThreshold ?? 'critical']!\n const model = cfg.model ?? 'composer-2.5'\n const autoCreatePR = cfg.autoCreatePR ?? true\n const maxIterations = cfg.maxIterations ?? 1\n const f = cfg.fetchImpl ?? fetch\n\n const resolvedCfg = { apiKey: cfg.apiKey, model, autoCreatePR, maxIterations }\n\n const webhookSecret =\n cfg.webhookSecret ??\n (typeof process !== 'undefined' ? process.env.MUSHI_PLUGIN_WEBHOOK_SECRET : undefined)\n if (!webhookSecret) {\n throw new Error(\n 'Cursor Cloud plugin requires webhookSecret (or MUSHI_PLUGIN_WEBHOOK_SECRET) for inbound event verification.',\n )\n }\n\n return createPluginHandler({\n secret: webhookSecret,\n on: {\n 'report.classified': async (e) => {\n const data = e.data as MushiReportClassifiedEvent\n const rank = SEVERITY_RANK[data.classification.severity ?? ''] ?? 0\n if (rank < minRank) return\n\n const repoUrl = cfg.repoUrl ?? ''\n if (!repoUrl) {\n console.warn('[cursor-cloud] report.classified skipped: repoUrl not configured. ' +\n 'Set repoUrl in the plugin config so the agent knows which codebase to fix.')\n return\n }\n\n const run = await createCursorAgentRun(\n resolvedCfg,\n {\n repoUrl,\n prompt: buildPromptFromClassifiedReport(e, data),\n },\n f,\n )\n\n // Emit audit log — handler return type is void.\n const result: CursorDispatchResult = { agentId: run.agentId, reportId: data.report.id, model }\n console.warn('[cursor-cloud] dispatched', JSON.stringify(result))\n },\n\n 'fix.requested': async (e) => {\n const data = e.data as MushiFixEvent\n\n const repoUrl = cfg.repoUrl ?? ''\n if (!repoUrl) {\n console.warn('[cursor-cloud] fix.requested skipped: repoUrl not configured.')\n return\n }\n\n const run = await createCursorAgentRun(\n resolvedCfg,\n {\n repoUrl,\n prompt: buildPromptFromFixRequested(e, data),\n },\n f,\n )\n\n // Emit audit log — handler return type is void.\n const result: CursorDispatchResult = { agentId: run.agentId, reportId: data.report.id, model }\n console.warn('[cursor-cloud] fix.requested dispatched', JSON.stringify(result))\n },\n\n 'qa_story.failed': async (e) => {\n const data = e.data as { storyId?: string; title?: string; failureReason?: string }\n\n const repoUrl = cfg.repoUrl ?? ''\n if (!repoUrl) {\n console.warn('[cursor-cloud] qa_story.failed skipped: repoUrl not configured.')\n return\n }\n\n const run = await createCursorAgentRun(\n resolvedCfg,\n {\n repoUrl,\n prompt: buildPromptFromQaStoryFailed(e, data),\n },\n f,\n )\n\n const result: CursorDispatchResult = {\n agentId: run.agentId,\n reportId: data.storyId ?? 'unknown',\n model,\n }\n console.warn('[cursor-cloud] qa_story.failed dispatched', JSON.stringify(result))\n },\n\n // ── Skill pipeline step dispatch (Phase 5) ───────────────────────────\n // When a pipeline run is started in 'cloud' mode, the skills.ts route\n // fires this event for each step. The plugin creates a Cursor Cloud\n // agent run whose prompt is the pre-composed context_packet for that\n // step, stores the agentId on the step row, and reports back to Mushi\n // via the check-in endpoint.\n 'skill_pipeline.step.dispatched': async (e) => {\n const data = e.data as {\n runId: string\n stepIndex: number\n skillSlug: string\n contextPacket: string\n projectId: string\n }\n\n const repoUrl = cfg.repoUrl ?? ''\n if (!repoUrl) {\n console.warn('[cursor-cloud] skill_pipeline.step.dispatched skipped: repoUrl not configured.')\n return\n }\n\n let run: CursorAgentRunResponse\n try {\n run = await withRetry(\n () =>\n createCursorAgentRun(\n resolvedCfg,\n {\n repoUrl,\n prompt: buildPromptFromSkillStep(data),\n },\n f,\n ),\n { maxAttempts: 3 },\n )\n } catch (err) {\n console.error('[cursor-cloud] skill_pipeline step dispatch failed', String(err))\n const mushiUrl =\n (typeof process !== 'undefined' ? process.env.MUSHI_API_URL : undefined) ?? 'https://api.mushi.ai'\n const mushiKey =\n (typeof process !== 'undefined' ? process.env.MUSHI_API_KEY : undefined) ?? ''\n if (mushiKey) {\n try {\n await f(`${mushiUrl}/v1/admin/skills/pipelines/${data.runId}/steps/${data.stepIndex}/checkin`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Mushi-Api-Key': mushiKey,\n },\n body: JSON.stringify({\n status: 'failed',\n notes: `Cursor Cloud dispatch failed: ${String(err).slice(0, 400)}`,\n }),\n })\n } catch { /* best-effort */ }\n } else {\n console.warn(\n `[cursor-cloud] MUSHI_API_KEY unset — cannot report the failed dispatch for run ${data.runId} step ${data.stepIndex}. ` +\n `The step will remain 'running' server-side until it is manually checked in.`,\n )\n }\n return\n }\n\n console.log('[cursor-cloud] skill_pipeline step dispatched', {\n runId: data.runId,\n stepIndex: data.stepIndex,\n agentId: run.agentId,\n })\n\n // Report back to Mushi: update the step row with the agentId.\n // The console React Flow canvas will update via Realtime.\n // Use a best-effort POST to the check-in endpoint.\n const mushiUrl =\n (typeof process !== 'undefined' ? process.env.MUSHI_API_URL : undefined) ?? 'https://api.mushi.ai'\n const mushiKey =\n (typeof process !== 'undefined' ? process.env.MUSHI_API_KEY : undefined) ?? ''\n\n if (mushiKey) {\n try {\n await f(`${mushiUrl}/v1/admin/skills/pipelines/${data.runId}/steps/${data.stepIndex}/checkin`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Mushi-Api-Key': mushiKey,\n },\n body: JSON.stringify({\n status: 'running',\n agent_ref: run.agentId,\n notes: `Cursor Cloud agent dispatched (model: ${model})`,\n }),\n })\n } catch (err) {\n console.warn('[cursor-cloud] checkin failed (best-effort)', String(err))\n }\n } else {\n console.warn(\n `[cursor-cloud] MUSHI_API_KEY unset — agent ${run.agentId} was dispatched for run ${data.runId} step ${data.stepIndex} ` +\n `but its agent_ref/running status cannot be reported back. The console flow will not reflect this step until it is manually checked in.`,\n )\n }\n },\n },\n })\n}\n\nfunction buildPromptFromSkillStep(data: {\n runId: string\n stepIndex: number\n skillSlug: string\n contextPacket: string\n}): string {\n return [\n `# Mushi Skill Pipeline — Step ${data.stepIndex + 1}`,\n ``,\n `Skill: \\`${data.skillSlug}\\``,\n `Pipeline run: ${data.runId}`,\n ``,\n `You are executing step ${data.stepIndex + 1} of a Mushi skill pipeline. ` +\n `The full context packet below contains your instructions plus the complete report context.`,\n ``,\n `When you have completed this step, call the Mushi MCP tool \\`checkin_pipeline_step\\` with:`,\n ` run_id: \"${data.runId}\"`,\n ` step_index: ${data.stepIndex}`,\n ` status: \"passed\" (or \"failed\" if you could not complete it)`,\n ` pr_url: <your PR URL if you opened one>`,\n ``,\n `─── Context Packet ────────────────────────────────────────────────────`,\n ``,\n data.contextPacket.slice(0, 32_000),\n ].join('\\n')\n}\n"],"mappings":"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","names":["mushiUrl","mushiKey"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n// Copyright (c) 2024–2026 Kenji Sakuramoto (kensaurus) — Mushi Mushi\n/**\n * Cursor Cloud Agent plugin for Mushi Mushi.\n *\n * Path A: Marketplace plugin — subscribes to `report.classified`,\n * `qa_story.failed`, `fix.requested` and `skill_pipeline.step.dispatched`.\n * When a qualifying event fires, this plugin calls Cursor's HTTP REST API\n * directly (no @cursor/sdk — runs from Deno edge functions and Node alike)\n * to create a Cloud Agent, and logs the resulting `agentId` + `prUrl` to the\n * Mushi console.\n *\n * This is the *opt-in* path: teams install it from the Marketplace and\n * configure their Cursor API key + repo URL in the plugin settings. The\n * project-wide *default* Cursor path is `autofix_agent = 'cursor_cloud'`,\n * dispatched by Mushi's own fix-worker; when THAT path emits\n * `fix.requested` the payload carries `data.fix.externalAgentId` and this\n * plugin deliberately does not start a second agent.\n *\n * API: Cursor Cloud Agents **v1** — https://cursor.com/docs/cloud-agent/api/endpoints\n * POST https://api.cursor.com/v1/agents\n * { prompt: { text }, repos: [{ url, startingRef? }], autoCreatePR (top-level),\n * agentId?: 'bc-<uuid>' (idempotency; re-use ⇒ 409 agent_id_conflict),\n * name?, model?: { id }, skipReviewerRequest? }\n * → { agent: { id, status, url }, run: { id, status, git: { branches: [{ repoUrl, branch, prUrl }] } } }\n * v0 (`source` / `target.autoCreatePr` / `webhook`) is still served by\n * Cursor but no longer used here — see the README section \"v0 vs v1\".\n */\n\nimport {\n createPluginHandler,\n withRetry,\n type MushiEventEnvelope,\n type MushiReportClassifiedEvent,\n type MushiFixEvent,\n} from '@mushi-mushi/plugin-sdk'\n\nexport const CURSOR_API_BASE = 'https://api.cursor.com'\nexport const CURSOR_DEFAULT_MODEL = 'composer-2.5'\n\nconst SEVERITY_RANK: Record<string, number> = {\n low: 1,\n medium: 2,\n high: 3,\n critical: 4,\n}\n\nexport interface CursorCloudPluginConfig {\n /** Cursor API key (crsr_…). */\n apiKey: string\n /** @deprecated Not used by the Cursor Cloud Agents v1 API; accepted for back-compat. */\n workspaceId?: string\n /** Cursor model id (`model.id`). Defaults to `composer-2.5`. */\n model?: string\n /** Whether Cursor should automatically open a (draft) PR. Defaults to true. */\n autoCreatePR?: boolean\n /** @deprecated v1 has no iteration cap; kept so existing configs still type-check. */\n maxIterations?: number\n /** Lowest severity that triggers a Cursor agent run. Defaults to `critical`. */\n severityThreshold?: 'low' | 'medium' | 'high' | 'critical'\n /**\n * URL of the target repository the Cursor agent will work in\n * (`https://github.com/org/repo`). If not set, the plugin no-ops.\n */\n repoUrl?: string\n /** Branch/ref the agent starts from (`repos[].startingRef`). Defaults to the repo default branch. */\n startingRef?: string\n /**\n * Standard Webhooks secret for inbound Mushi events. Required for\n * self-hosted installs — never derive this from workspaceId.\n */\n webhookSecret?: string\n /** Override `fetch` for tests. */\n fetchImpl?: typeof fetch\n /** Override the API base (tests / proxies). */\n apiBase?: string\n}\n\nexport interface CursorAgentRunResponse {\n agentId: string\n runId?: string\n status: string\n agentUrl?: string\n prUrl?: string\n}\n\nexport interface CursorDispatchResult {\n agentId: string\n reportId: string\n model: string\n}\n\n/** Wire body for POST /v1/agents — exported so tests can pin the exact shape. */\nexport function buildCreateAgentBody(input: {\n prompt: string\n repoUrl: string\n startingRef?: string\n model: string\n autoCreatePR: boolean\n agentId?: string\n name?: string\n}): Record<string, unknown> {\n const body: Record<string, unknown> = {\n prompt: { text: input.prompt },\n repos: [{ url: input.repoUrl, ...(input.startingRef ? { startingRef: input.startingRef } : {}) }],\n autoCreatePR: input.autoCreatePR,\n skipReviewerRequest: true,\n model: { id: input.model },\n }\n if (input.agentId) body.agentId = input.agentId\n if (input.name) body.name = input.name.slice(0, 100)\n return body\n}\n\n/**\n * `bc-<uuid>` derived from a stable seed (the Mushi delivery id) so a retried\n * delivery re-uses the agent id and Cursor answers 409 `agent_id_conflict`\n * instead of starting a second agent. RFC 9562 name-based layout over\n * SHA-256 (WebCrypto — available in Node ≥ 20 and Deno).\n */\nexport async function deterministicAgentId(seed: string): Promise<string> {\n const data = new TextEncoder().encode(`mushi-mushi:plugin-cursor-cloud:v1\u0000${seed}`)\n const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', data))\n const b = digest.slice(0, 16)\n b[6] = (b[6] & 0x0f) | 0x50\n b[8] = (b[8] & 0x3f) | 0x80\n let hex = ''\n for (let i = 0; i < b.length; i++) hex += b[i].toString(16).padStart(2, '0')\n return `bc-${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n}\n\ninterface CreateAgentV1Response {\n agent?: { id?: string; status?: string; url?: string }\n run?: { id?: string; status?: string; git?: { branches?: Array<{ prUrl?: string }> } }\n}\n\n/**\n * Call Cursor's REST API (v1) to create a Cloud Agent.\n * Returns the agentId (and run id) so the caller can track the run in the\n * Mushi console.\n *\n * On non-2xx the raw Response is thrown so `withRetry` can inspect\n * `.status` and `Retry-After` and apply the documented retry policy:\n * - 429 / 5xx → retry with exponential back-off\n * - 4xx (not 429) → throw immediately (no money burned on bad keys)\n * - 409 agent_id_conflict → NOT an error: the agent already exists for\n * this delivery (retry / duplicate webhook)\n * - network error → retry\n */\nasync function createCursorAgentRun(\n cfg: Required<Pick<CursorCloudPluginConfig, 'apiKey' | 'model' | 'autoCreatePR'>> &\n Pick<CursorCloudPluginConfig, 'startingRef' | 'apiBase'>,\n opts: { repoUrl: string; prompt: string; agentId?: string; name?: string },\n f: typeof fetch,\n): Promise<CursorAgentRunResponse> {\n return withRetry(async () => {\n const res = await f(`${cfg.apiBase ?? CURSOR_API_BASE}/v1/agents`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${cfg.apiKey}`,\n },\n body: JSON.stringify(\n buildCreateAgentBody({\n prompt: opts.prompt,\n repoUrl: opts.repoUrl,\n startingRef: cfg.startingRef,\n model: cfg.model || CURSOR_DEFAULT_MODEL,\n autoCreatePR: cfg.autoCreatePR,\n agentId: opts.agentId,\n name: opts.name,\n }),\n ),\n })\n if (res.status === 409 && opts.agentId) {\n // Re-delivered event: Cursor already holds this agent. Idempotent success.\n const errBody = (await res.json().catch(() => null)) as { error?: { code?: string } } | null\n if (errBody?.error?.code === 'agent_id_conflict' || !errBody) {\n return { agentId: opts.agentId, status: 'EXISTS' }\n }\n throw res\n }\n if (!res.ok) {\n // Throw the Response itself so withRetry can read status / Retry-After.\n // This is the contract documented in @mushi-mushi/plugin-sdk/retry.ts.\n throw res\n }\n const body = (await res.json()) as CreateAgentV1Response\n const prUrl = body.run?.git?.branches?.find((b) => typeof b.prUrl === 'string')?.prUrl\n return {\n agentId: body.agent?.id ?? '',\n runId: body.run?.id,\n status: body.run?.status ?? body.agent?.status ?? 'CREATING',\n agentUrl: body.agent?.url,\n prUrl,\n }\n })\n}\n\nconst DRAFT_PR_INSTRUCTION =\n 'Open the pull request as a DRAFT (Cursor opens drafts by default; keep it that way). ' +\n 'Do not refactor unrelated code and never merge.'\n\nfunction buildPromptFromClassifiedReport(\n envelope: MushiEventEnvelope,\n data: MushiReportClassifiedEvent,\n): string {\n const lines = [\n `A bug has been reported in your project and classified as ${data.classification.severity?.toUpperCase()}.`,\n ``,\n `Report ID: ${data.report.id}`,\n `Title: ${data.report.title ?? '(untitled)'}`,\n `Category: ${data.report.category ?? data.classification.category}`,\n `Severity: ${data.classification.severity}`,\n `Project: ${envelope.projectId}`,\n ]\n\n if (data.classification.tags?.length) {\n lines.push(`Tags: ${data.classification.tags.join(', ')}`)\n }\n\n lines.push(\n ``,\n `Please investigate this bug, identify the root cause in the codebase, ` +\n `and open a draft PR with a minimal fix titled \"fix: <summary> (MUSHI-${data.report.id})\". ${DRAFT_PR_INSTRUCTION}`,\n )\n\n return lines.join('\\n')\n}\n\nfunction buildPromptFromFixRequested(envelope: MushiEventEnvelope, fixData: MushiFixEvent): string {\n return [\n `A fix was requested for report ${fixData.report.id} in project ${envelope.projectId}.`,\n ``,\n `Report title: ${fixData.report.title ?? '(untitled)'}`,\n `Fix ID: ${fixData.fix.id}`,\n fixData.fix.branch ? `Requested branch: ${fixData.fix.branch}` : '',\n ``,\n `Please investigate the reported issue, identify the root cause, ` +\n `and open a draft PR with a minimal fix titled \"fix: <summary> (MUSHI-${fixData.report.id})\". ${DRAFT_PR_INSTRUCTION}`,\n ]\n .filter((line) => line !== '')\n .join('\\n')\n}\n\nfunction buildPromptFromQaStoryFailed(\n envelope: MushiEventEnvelope,\n data: { storyId?: string; title?: string; failureReason?: string },\n): string {\n return [\n `A QA story failed in project ${envelope.projectId}.`,\n ``,\n `Story ID: ${data.storyId ?? 'unknown'}`,\n `Title: ${data.title ?? '(untitled)'}`,\n data.failureReason ? `Failure: ${data.failureReason}` : '',\n ``,\n `Please investigate the failing assertion, identify the root cause in the codebase, ` +\n `and open a draft PR with a minimal fix. ${DRAFT_PR_INSTRUCTION}`,\n ]\n .filter((line) => line !== '')\n .join('\\n')\n}\n\n/** Build the plugin handler and return a callable function. */\nexport function createCursorCloudPlugin(cfg: CursorCloudPluginConfig) {\n const minRank = SEVERITY_RANK[cfg.severityThreshold ?? 'critical']!\n const model = cfg.model ?? CURSOR_DEFAULT_MODEL\n const autoCreatePR = cfg.autoCreatePR ?? true\n const f = cfg.fetchImpl ?? fetch\n\n const resolvedCfg = {\n apiKey: cfg.apiKey,\n model,\n autoCreatePR,\n startingRef: cfg.startingRef,\n apiBase: cfg.apiBase,\n }\n\n const webhookSecret =\n cfg.webhookSecret ??\n (typeof process !== 'undefined' ? process.env.MUSHI_PLUGIN_WEBHOOK_SECRET : undefined)\n if (!webhookSecret) {\n throw new Error(\n 'Cursor Cloud plugin requires webhookSecret (or MUSHI_PLUGIN_WEBHOOK_SECRET) for inbound event verification.',\n )\n }\n\n return createPluginHandler({\n secret: webhookSecret,\n on: {\n 'report.classified': async (e) => {\n const data = e.data as MushiReportClassifiedEvent\n const rank = SEVERITY_RANK[data.classification.severity ?? ''] ?? 0\n if (rank < minRank) return\n\n const repoUrl = cfg.repoUrl ?? ''\n if (!repoUrl) {\n console.warn('[cursor-cloud] report.classified skipped: repoUrl not configured. ' +\n 'Set repoUrl in the plugin config so the agent knows which codebase to fix.')\n return\n }\n\n const run = await createCursorAgentRun(\n resolvedCfg,\n {\n repoUrl,\n prompt: buildPromptFromClassifiedReport(e, data),\n agentId: await deterministicAgentId(`report.classified:${e.projectId}:${data.report.id}`),\n name: `Mushi report ${data.report.id.slice(0, 8)}`,\n },\n f,\n )\n\n // Emit audit log — handler return type is void.\n const result: CursorDispatchResult = { agentId: run.agentId, reportId: data.report.id, model }\n console.warn('[cursor-cloud] dispatched', JSON.stringify(result))\n },\n\n 'fix.requested': async (e) => {\n const data = e.data as MushiFixEvent & { fix: { externalAgentId?: string; agent?: string } }\n\n // Mushi's first-class cursor_cloud path already created the agent —\n // the event tells us which one. Starting another would double the\n // spend and open two PRs for one report.\n if (typeof data.fix?.externalAgentId === 'string' && data.fix.externalAgentId.length > 0) {\n console.warn(\n '[cursor-cloud] fix.requested already dispatched by Mushi fix-worker',\n JSON.stringify({ agentId: data.fix.externalAgentId, reportId: data.report.id, agent: data.fix.agent }),\n )\n return\n }\n\n const repoUrl = cfg.repoUrl ?? ''\n if (!repoUrl) {\n console.warn('[cursor-cloud] fix.requested skipped: repoUrl not configured.')\n return\n }\n\n const run = await createCursorAgentRun(\n resolvedCfg,\n {\n repoUrl,\n prompt: buildPromptFromFixRequested(e, data),\n agentId: await deterministicAgentId(`fix.requested:${e.projectId}:${data.fix.id}`),\n name: `Mushi fix ${data.fix.id.slice(0, 8)}`,\n },\n f,\n )\n\n // Emit audit log — handler return type is void.\n const result: CursorDispatchResult = { agentId: run.agentId, reportId: data.report.id, model }\n console.warn('[cursor-cloud] fix.requested dispatched', JSON.stringify(result))\n },\n\n 'qa_story.failed': async (e) => {\n const data = e.data as { storyId?: string; title?: string; failureReason?: string }\n\n const repoUrl = cfg.repoUrl ?? ''\n if (!repoUrl) {\n console.warn('[cursor-cloud] qa_story.failed skipped: repoUrl not configured.')\n return\n }\n\n const run = await createCursorAgentRun(\n resolvedCfg,\n {\n repoUrl,\n prompt: buildPromptFromQaStoryFailed(e, data),\n agentId: await deterministicAgentId(`qa_story.failed:${e.projectId}:${data.storyId ?? e.deliveryId}`),\n name: `Mushi QA story ${(data.storyId ?? 'unknown').slice(0, 8)}`,\n },\n f,\n )\n\n const result: CursorDispatchResult = {\n agentId: run.agentId,\n reportId: data.storyId ?? 'unknown',\n model,\n }\n console.warn('[cursor-cloud] qa_story.failed dispatched', JSON.stringify(result))\n },\n\n // ── Skill pipeline step dispatch (Phase 5) ───────────────────────────\n // When a pipeline run is started in 'cloud' mode, the skills.ts route\n // fires this event for each step. The plugin creates a Cursor Cloud\n // agent run whose prompt is the pre-composed context_packet for that\n // step, stores the agentId on the step row, and reports back to Mushi\n // via the check-in endpoint.\n 'skill_pipeline.step.dispatched': async (e) => {\n const data = e.data as {\n runId: string\n stepIndex: number\n skillSlug: string\n contextPacket: string\n projectId: string\n }\n\n const repoUrl = cfg.repoUrl ?? ''\n if (!repoUrl) {\n console.warn('[cursor-cloud] skill_pipeline.step.dispatched skipped: repoUrl not configured.')\n return\n }\n\n let run: CursorAgentRunResponse\n try {\n run = await withRetry(\n () =>\n createCursorAgentRun(\n resolvedCfg,\n {\n repoUrl,\n prompt: buildPromptFromSkillStep(data),\n agentId: undefined,\n name: `Mushi skill ${data.skillSlug.slice(0, 40)} #${data.stepIndex + 1}`,\n },\n f,\n ),\n { maxAttempts: 3 },\n )\n } catch (err) {\n console.error('[cursor-cloud] skill_pipeline step dispatch failed', String(err))\n const mushiUrl =\n (typeof process !== 'undefined' ? process.env.MUSHI_API_URL : undefined) ?? 'https://api.mushi.ai'\n const mushiKey =\n (typeof process !== 'undefined' ? process.env.MUSHI_API_KEY : undefined) ?? ''\n if (mushiKey) {\n try {\n await f(`${mushiUrl}/v1/admin/skills/pipelines/${data.runId}/steps/${data.stepIndex}/checkin`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Mushi-Api-Key': mushiKey,\n },\n body: JSON.stringify({\n status: 'failed',\n notes: `Cursor Cloud dispatch failed: ${String(err).slice(0, 400)}`,\n }),\n })\n } catch { /* best-effort */ }\n } else {\n console.warn(\n `[cursor-cloud] MUSHI_API_KEY unset — cannot report the failed dispatch for run ${data.runId} step ${data.stepIndex}. ` +\n `The step will remain 'running' server-side until it is manually checked in.`,\n )\n }\n return\n }\n\n console.log('[cursor-cloud] skill_pipeline step dispatched', {\n runId: data.runId,\n stepIndex: data.stepIndex,\n agentId: run.agentId,\n })\n\n // Report back to Mushi: update the step row with the agentId.\n // The console React Flow canvas will update via Realtime.\n // Use a best-effort POST to the check-in endpoint.\n const mushiUrl =\n (typeof process !== 'undefined' ? process.env.MUSHI_API_URL : undefined) ?? 'https://api.mushi.ai'\n const mushiKey =\n (typeof process !== 'undefined' ? process.env.MUSHI_API_KEY : undefined) ?? ''\n\n if (mushiKey) {\n try {\n await f(`${mushiUrl}/v1/admin/skills/pipelines/${data.runId}/steps/${data.stepIndex}/checkin`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Mushi-Api-Key': mushiKey,\n },\n body: JSON.stringify({\n status: 'running',\n agent_ref: run.agentId,\n notes: `Cursor Cloud agent dispatched (model: ${model})`,\n }),\n })\n } catch (err) {\n console.warn('[cursor-cloud] checkin failed (best-effort)', String(err))\n }\n } else {\n console.warn(\n `[cursor-cloud] MUSHI_API_KEY unset — agent ${run.agentId} was dispatched for run ${data.runId} step ${data.stepIndex} ` +\n `but its agent_ref/running status cannot be reported back. The console flow will not reflect this step until it is manually checked in.`,\n )\n }\n },\n },\n })\n}\n\nfunction buildPromptFromSkillStep(data: {\n runId: string\n stepIndex: number\n skillSlug: string\n contextPacket: string\n}): string {\n return [\n `# Mushi Skill Pipeline — Step ${data.stepIndex + 1}`,\n ``,\n `Skill: \\`${data.skillSlug}\\``,\n `Pipeline run: ${data.runId}`,\n ``,\n `You are executing step ${data.stepIndex + 1} of a Mushi skill pipeline. ` +\n `The full context packet below contains your instructions plus the complete report context.`,\n ``,\n `When you have completed this step, call the Mushi MCP tool \\`checkin_pipeline_step\\` with:`,\n ` run_id: \"${data.runId}\"`,\n ` step_index: ${data.stepIndex}`,\n ` status: \"passed\" (or \"failed\" if you could not complete it)`,\n ` pr_url: <your PR URL if you opened one>`,\n ``,\n `Push your work to a branch named \\`mushi/skill-${data.skillSlug.slice(0, 24)}-${data.runId.slice(0, 8)}\\` and ${DRAFT_PR_INSTRUCTION}`,\n ``,\n `─── Context Packet ────────────────────────────────────────────────────`,\n ``,\n data.contextPacket.slice(0, 32_000),\n ].join('\\n')\n}\n"],"mappings":"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","names":["mushiUrl","mushiKey"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/plugin-cursor-cloud",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "license": "MIT",
5
5
  "description": "Mushi Mushi plugin: dispatch a Cursor Cloud Agent when a critical bug is classified or a QA story fails — opens a signed draft PR automatically.",
6
6
  "type": "module",
@@ -18,14 +18,14 @@
18
18
  "LICENSE"
19
19
  ],
20
20
  "dependencies": {
21
- "@mushi-mushi/plugin-sdk": "^0.7.0"
21
+ "@mushi-mushi/plugin-sdk": "^0.8.1"
22
22
  },
23
23
  "devDependencies": {
24
- "@types/node": "^22.19.21",
25
- "eslint": "^10.4.1",
24
+ "@types/node": "^22.20.0",
25
+ "eslint": "^10.6.0",
26
26
  "tsup": "^8.5.1",
27
27
  "typescript": "^6.0.3",
28
- "vitest": "^4.1.8",
28
+ "vitest": "^4.1.9",
29
29
  "@mushi-mushi/eslint-config": "0.0.0"
30
30
  },
31
31
  "repository": {
@@ -33,7 +33,7 @@
33
33
  "url": "https://github.com/kensaurus/mushi-mushi.git",
34
34
  "directory": "packages/plugin-cursor-cloud"
35
35
  },
36
- "author": "Kenji Sakuramoto (https://bsky.app/profile/mushimushi.dev)",
36
+ "author": "Kenji Sakuramoto <kensaurus@gmail.com> (https://github.com/kensaurus)",
37
37
  "publishConfig": {
38
38
  "access": "public",
39
39
  "provenance": true
@@ -50,15 +50,11 @@
50
50
  "integration"
51
51
  ],
52
52
  "engines": {
53
- "node": ">=20"
53
+ "node": ">=20.19.0"
54
54
  },
55
55
  "bugs": {
56
56
  "url": "https://github.com/kensaurus/mushi-mushi/issues"
57
57
  },
58
- "funding": {
59
- "type": "github",
60
- "url": "https://github.com/sponsors/kensaurus"
61
- },
62
58
  "homepage": "https://kensaur.us/mushi-mushi",
63
59
  "scripts": {
64
60
  "build": "tsup",