Skip to content

Commit af79347

Browse files
feat(cursor): refresh plugin contract coverage against the 2026-09-02 public docs (#189)
Validate and project Cursor subagentStart/subagentStop through their documented subagent_* envelopes and output channels (permission deny + user_message; followup_message) in event routes, config-hook wrappers, playground codecs, and lifecycle starters. Add the cursor.* config extension for schema-admitted manifest metadata (author, homepage, repository, license, keywords, publisher, category, tags, minClientVersions) with cursor.manifest.* diagnostics. Pin the complete 21-event Cursor hook inventory with cloud availability, hook options, plugin formats, discovery, variables, marketplace limits, distribution and local-install surfaces, canvases, and the G5-gated agents component as dated capability rows mirrored through the unified bundle. Cursor adapterRevision 1.9.0, plugin 1.22.0.
1 parent 9fe8a35 commit af79347

21 files changed

Lines changed: 1427 additions & 116 deletions
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"agent-bundle": minor
3+
---
4+
5+
Refresh the Cursor plugin contract against the public docs retrieved 2026-09-02
6+
(#189). Cursor `subagentStart`/`subagentStop` now validate their documented
7+
`subagent_*` envelopes and project through their real output channels
8+
(`permission: "deny"` + `user_message`; `followup_message`), with wrapper,
9+
playground, and lifecycle-replay fixtures. A new `cursor.*` config extension
10+
emits schema-admitted manifest metadata (`author`, `homepage`, `repository`,
11+
`license`, `keywords`, `publisher`, `category`, `tags`, `minClientVersions`)
12+
into `.cursor-plugin/plugin.json` with `cursor.manifest.*` diagnostics. The
13+
Cursor capability table records every documented hook event (21) with cloud
14+
availability, hook options (`failClosed`, `loop_limit`, prompt hooks), plugin
15+
formats, component discovery, variables, marketplace manifest limits, team
16+
distribution and local-install surfaces, canvases, and the G5-gated agents
17+
component as dated `supported`/`unavailable` rows; the unified bundle mirrors
18+
each row and now intersects Cursor manifest metadata for real. Cursor
19+
`adapterRevision` 1.8.0 → 1.9.0, unified plugin 1.22.0 → 1.23.0.

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ Claude Code language servers are declared under `claude.lspServers`; the `claude
5555

5656
Claude Code plugin defaults are declared under `claude.settings` and emitted as plugin-root `settings.json`, which Claude Code applies when the plugin is enabled. The pinned contract supports only `agent` and `subagentStatusLine`; Agent Bundle rejects any other key rather than shipping a default Claude Code would silently ignore, and it expands no path tokens here because `settings.json` is absent from the host's placeholder-substitution table. Because the plugin `agents/` component is still deferred, declaring `agent` also raises a warning: the referenced agent has to reach the plugin root some other way, such as a prebuilt payload.
5757

58+
Cursor Plugin manifest metadata is declared under `cursor.*` and emitted verbatim into `.cursor-plugin/plugin.json` by the `cursor` target and the Cursor half of `plugin`: `author` (`name`, optional `email`), `homepage`, `repository`, `license`, `keywords`, plus the schema-admitted `publisher`, `category`, `tags`, and `minClientVersions` (for example `{ cursor: '3.13.0' }`). Every field is validated against the pinned `cursor/plugins` manifest schema before emission — `author.url`, non-HTTP URLs, empty strings, and loose semver are rejected with `cursor.manifest.*` errors, and an invalid block emits no partial metadata. The Cursor artifact never mixes Agent Plugin (`plugin.json`, `${PLUGIN_ROOT}`) paths or tokens into the Cursor Plugin format; the portable target owns that format. The full documented-surface contract matrix (every Cursor hook event, cloud availability, hook options, marketplace and team-distribution surfaces, canvases, agents) lives as dated `supported` / `unavailable` rows in `packages/agent-bundle/src/adapters/capabilities/cursor-2026-08-28.json`.
59+
5860
The same config also owns the npm package build — no second bundler config, bin shims, or hand-rolled stdio lifecycles. `bin` and `lib` entries (or the conventions `src/cli.ts`, `src/index.ts`, and `src/mcp/<server-id>.ts`) emit executable `dist/bin/<name>.js` bundles and a library output alongside the host artifacts; an MCP entry that default-exports a server factory runs under a framework-owned stdio lifecycle; `tools.rsbuild` / `tools.rspack` is the one bundler escape hatch. [Entry conventions](docs/entry-conventions.md) is the full contract, and [Framework mode](docs/framework-mode.md) is the whole authoring model on one screen: structure in config and conventions (`src/skills/<name>/SKILL.md` ships with no declaration at all), JSX only where something is rendered.
5961

6062
## Commands

‎docs/canvases/agent-bundle-walkthrough.canvas.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -742,8 +742,8 @@ else try { output = await requestEventRuntime({ ... }); }
742742
[<Text as="span" style={{ fontFamily: MONO }} size="small">tool/before</Text>, "PreToolUse", "PreToolUse", "preToolUse", "—"],
743743
[<Text as="span" style={{ fontFamily: MONO }} size="small">tool/after</Text>, "PostToolUse", "PostToolUse", "postToolUse", "—"],
744744
[<Text as="span" style={{ fontFamily: MONO }} size="small">stop</Text>, "Stop", "Stop", "stop", "—"],
745-
[<Text as="span" style={{ fontFamily: MONO }} size="small">agent/start</Text>, "SubagentStart", "SubagentStart (adds turn_id, model, permission_mode)", "subagentStart", "—"],
746-
[<Text as="span" style={{ fontFamily: MONO }} size="small">agent/stop</Text>, "SubagentStop", "SubagentStop", "subagentStop", "—"],
745+
[<Text as="span" style={{ fontFamily: MONO }} size="small">agent/start</Text>, "SubagentStart", "SubagentStart (adds turn_id, model, permission_mode)", "subagentStart (subagent_id/subagent_type envelope; permission deny)", "—"],
746+
[<Text as="span" style={{ fontFamily: MONO }} size="small">agent/stop</Text>, "SubagentStop", "SubagentStop", "subagentStop (status/loop_count envelope; followup_message)", "—"],
747747
[<Text as="span" style={{ fontFamily: MONO }} size="small">workspace/open</Text>, "unavailable: no such event", "unavailable: no such event", "workspaceOpen (observe-only; optional pluginPaths return not modeled)", "unavailable: spec defines no hooks"],
748748
]}
749749
/>
@@ -758,8 +758,8 @@ else try { output = await requestEventRuntime({ ... }); }
758758
["tool/before", "yes, reason required", "yes (when not denying)", "Claude + Codex; dropped on Cursor"],
759759
["tool/after", "—", "—", "all hosts"],
760760
["stop", "yes — keeps the agent working (Cursor: followup_message)", "—", "—"],
761-
["agent/start", "no host can block subagent creation", "—", "all hosts"],
762-
["agent/stop", "Claude + Codex (keeps subagent running); Cursor cannot", "—", "Claude + Cursor; Codex schema rejects it"],
761+
["agent/start", "Cursor only (permission: deny + user_message); Claude + Codex cannot", "—", "Claude + Codex; Cursor documents no channel"],
762+
["agent/stop", "Claude + Codex (decision: block keeps subagent running); Cursor (followup_message auto-continue)", "—", "Claude only; Codex schema and Cursor docs reject it"],
763763
["workspace/open", "—", "—", "no channel — observation only (Cursor)"],
764764
]}
765765
/>

‎examples/rsc-agent-runtime/README.md‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ Host/Origin allowlists mitigate DNS rebinding and cross-origin requests, but the
246246
| `task/create` | Unavailable | `TaskCreated` (deny) | Unavailable |
247247
| `task/complete` | Unavailable | `TaskCompleted` (observe-only; blocking is exit-code-only) | Unavailable |
248248
| `agent/idle` | Unavailable | `TeammateIdle` (deny via continue:false) | Unavailable |
249-
| `agent/start` | `subagentStart` | `SubagentStart` | `SubagentStart` |
250-
| `agent/stop` | `subagentStop` | `SubagentStop` | `SubagentStop` |
249+
| `agent/start` | `subagentStart` (deny via `permission: "deny"`; no context channel) | `SubagentStart` (context) | `SubagentStart` (context) |
250+
| `agent/stop` | `subagentStop` (deny via `followup_message`; no context channel) | `SubagentStop` (deny + context) | `SubagentStop` (deny) |
251251
| `workspace/open` | Supported (observe-only; native `pluginPaths` return not modeled) | Unavailable | Unavailable |
252252

253253
Cursor's native `workspaceOpen` is sessionless and its optional `pluginPaths`
@@ -260,7 +260,15 @@ block subagent creation. Their `agent/stop` routes can continue the subagent
260260
with the native `decision: "block"` plus `reason` contract. Codex
261261
`SubagentStop` exit-0 output is always JSON; its generated 0.147.0 output
262262
schema has no `additionalContext` field, so the route projection rejects that
263-
unsupported effect rather than silently fabricating one.
263+
unsupported effect rather than silently fabricating one. Cursor inverts the
264+
pair: its `subagentStart` envelope (`subagent_id`, `subagent_type`, `task`,
265+
`parent_conversation_id`, `tool_call_id`, `subagent_model`,
266+
`is_parallel_worker`) accepts `permission: "deny"` plus `user_message`, and its
267+
`subagentStop` envelope (`status`, `loop_count`, `summary`, `modified_files`,
268+
`agent_transcript_path`) accepts only `followup_message`, which Cursor consumes
269+
when `status` is `completed` and caps with `loop_limit` (default 5). Neither
270+
Cursor event documents an additional-context channel, so `Agent.Context` fails
271+
closed on Cursor for both.
264272
`session/end` and `prompt/submit` are event-route-only families and do not add
265273
`config.hooks.sessionEnd` or `config.hooks.promptSubmit` handler keys.
266274
`session/end` rejects every result effect because each native event is

‎packages/agent-bundle/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ observed host version (`observedVersion`) and target, while adapters carry a mon
574574
`adapterRevision`. Git already versions repository-owned content; hashing it again inside the
575575
repository is self-referential and causes churn on every table edit.
576576

577-
`AgentBundleConfig` merges bundled portable, Codex, and Claude declarations
577+
`AgentBundleConfig` merges bundled portable, Codex, Claude, and Cursor declarations
578578
through `AgentBundleConfigExtensions`. `TargetRegistry` owns the unique
579579
extension descriptor and adapter for each target. Ordinary projects need no
580580
extension key; extension values are strict finite JSON and host-specific values

0 commit comments

Comments
 (0)