@cursor/july 0.1.100 → 0.1.101

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.
Files changed (254) hide show
  1. package/dist/bin/agent-serve.js +3 -2
  2. package/dist/channels/deployments/deployments-channel.d.ts +41 -5
  3. package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
  4. package/dist/channels/deployments/deployments-channel.js +88 -8
  5. package/dist/channels/deployments/types.d.ts +4 -3
  6. package/dist/channels/deployments/types.d.ts.map +1 -1
  7. package/dist/channels/origin/defaults.d.ts +43 -0
  8. package/dist/channels/origin/defaults.d.ts.map +1 -0
  9. package/dist/channels/origin/defaults.js +111 -0
  10. package/dist/channels/origin/index.d.ts +2 -0
  11. package/dist/channels/origin/index.d.ts.map +1 -1
  12. package/dist/channels/origin/index.js +2 -0
  13. package/dist/channels/origin/origin-channel.d.ts +2 -1
  14. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  15. package/dist/channels/origin/origin-channel.js +24 -3
  16. package/dist/channels/origin/state.d.ts +32 -0
  17. package/dist/channels/origin/state.d.ts.map +1 -0
  18. package/dist/channels/origin/state.js +23 -0
  19. package/dist/channels/origin/types.d.ts +23 -1
  20. package/dist/channels/origin/types.d.ts.map +1 -1
  21. package/dist/channels/slack/api.d.ts.map +1 -1
  22. package/dist/channels/slack/api.js +2 -1
  23. package/dist/channels/slack/code-fences.d.ts +17 -0
  24. package/dist/channels/slack/code-fences.d.ts.map +1 -0
  25. package/dist/channels/slack/code-fences.js +168 -0
  26. package/dist/channels/slack/index.d.ts +1 -0
  27. package/dist/channels/slack/index.d.ts.map +1 -1
  28. package/dist/channels/slack/index.js +1 -0
  29. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  30. package/dist/channels/slack/live-delivery.js +11 -4
  31. package/dist/connections.d.ts +18 -2
  32. package/dist/connections.d.ts.map +1 -1
  33. package/dist/connections.js +18 -2
  34. package/dist/docs/404.html +2 -2
  35. package/dist/docs/ab.html +4 -4
  36. package/dist/docs/assets/{app.DZ1e0Ycq.js → app.BASO9Kih.js} +1 -1
  37. package/dist/docs/assets/chunks/@localSearchIndexroot.DbFXINq2.js +1 -0
  38. package/dist/docs/assets/chunks/{VPLocalSearchBox.l8omwc6D.js → VPLocalSearchBox.DwkqxoE0.js} +1 -1
  39. package/dist/docs/assets/chunks/{theme.JVcD6gel.js → theme.CCil_13e.js} +2 -2
  40. package/dist/docs/assets/{reference_connections.md.BiGoBAk2.js → reference_connections.md.Di6jJAXF.js} +12 -2
  41. package/dist/docs/assets/{reference_connections.md.BiGoBAk2.lean.js → reference_connections.md.Di6jJAXF.lean.js} +1 -1
  42. package/dist/docs/assets/{reference_http-api.md.BEJx9XVj.js → reference_http-api.md.oySXBO8o.js} +1 -1
  43. package/dist/docs/building-with-agents.html +4 -4
  44. package/dist/docs/concepts.html +4 -4
  45. package/dist/docs/deployment.html +4 -4
  46. package/dist/docs/evals.html +4 -4
  47. package/dist/docs/guides/agent-to-agent.html +4 -4
  48. package/dist/docs/guides/cloud-runtime.html +4 -4
  49. package/dist/docs/guides/convert-automation.html +4 -4
  50. package/dist/docs/guides/github.html +4 -4
  51. package/dist/docs/guides/human-in-the-loop.html +4 -4
  52. package/dist/docs/guides/mcp-oauth.html +4 -4
  53. package/dist/docs/guides/opentelemetry.html +4 -4
  54. package/dist/docs/guides/slack.html +4 -4
  55. package/dist/docs/guides/webhooks.html +4 -4
  56. package/dist/docs/hashmap.json +1 -1
  57. package/dist/docs/hillclimbing.html +4 -4
  58. package/dist/docs/index.html +4 -4
  59. package/dist/docs/llms-full.txt +32 -1
  60. package/dist/docs/quickstart.html +4 -4
  61. package/dist/docs/reference/agent-config.html +4 -4
  62. package/dist/docs/reference/artifacts.html +4 -4
  63. package/dist/docs/reference/channels.html +4 -4
  64. package/dist/docs/reference/cli.html +4 -4
  65. package/dist/docs/reference/connections.html +16 -6
  66. package/dist/docs/reference/connections.md +31 -0
  67. package/dist/docs/reference/hooks.html +4 -4
  68. package/dist/docs/reference/http-api.html +6 -6
  69. package/dist/docs/reference/http-api.md +1 -1
  70. package/dist/docs/reference/instructions.html +4 -4
  71. package/dist/docs/reference/playground.html +4 -4
  72. package/dist/docs/reference/project-layout.html +4 -4
  73. package/dist/docs/reference/prompt.html +4 -4
  74. package/dist/docs/reference/schedules.html +4 -4
  75. package/dist/docs/reference/sessions.html +4 -4
  76. package/dist/docs/reference/skills.html +4 -4
  77. package/dist/docs/reference/subagents.html +4 -4
  78. package/dist/docs/reference/tools.html +4 -4
  79. package/dist/docs/scaffolding-agents.html +4 -4
  80. package/dist/docs/skills/ab.html +4 -4
  81. package/dist/docs/skills/create-agent.html +4 -4
  82. package/dist/docs/skills/debug.html +4 -4
  83. package/dist/docs/skills/evals.html +4 -4
  84. package/dist/docs/skills/framework-map.html +4 -4
  85. package/dist/docs/skills/github.html +4 -4
  86. package/dist/docs/skills/hillclimb.html +4 -4
  87. package/dist/docs/skills/index.html +4 -4
  88. package/dist/docs/skills/mcp-auth.html +4 -4
  89. package/dist/docs/skills/otel.html +4 -4
  90. package/dist/docs/skills/setup-slack.html +4 -4
  91. package/dist/docs/storage.html +4 -4
  92. package/dist/docs/templates/agentic-owners.html +4 -4
  93. package/dist/docs/templates/agents-md.html +4 -4
  94. package/dist/docs/templates/code-wiki.html +4 -4
  95. package/dist/docs/templates/demo.html +4 -4
  96. package/dist/docs/templates/pr-autofixer.html +4 -4
  97. package/dist/docs/templates/security-help.html +4 -4
  98. package/dist/docs/templates/security-reviewer.html +4 -4
  99. package/dist/docs/templates/triage.html +4 -4
  100. package/dist/docs/troubleshooting.html +4 -4
  101. package/dist/extensions/cursor-cloud-agents/extension.d.ts +55 -0
  102. package/dist/extensions/cursor-cloud-agents/extension.d.ts.map +1 -0
  103. package/dist/extensions/cursor-cloud-agents/extension.js +64 -0
  104. package/dist/extensions/cursor-cloud-agents/lib/cloud.d.ts +30 -0
  105. package/dist/extensions/cursor-cloud-agents/lib/cloud.d.ts.map +1 -0
  106. package/dist/extensions/cursor-cloud-agents/lib/cloud.js +45 -0
  107. package/dist/extensions/cursor-cloud-agents/lib/fleet.d.ts +25 -0
  108. package/dist/extensions/cursor-cloud-agents/lib/fleet.d.ts.map +1 -0
  109. package/dist/extensions/cursor-cloud-agents/lib/fleet.js +85 -0
  110. package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts +15 -0
  111. package/dist/extensions/cursor-cloud-agents/lib/repos.d.ts.map +1 -0
  112. package/dist/extensions/cursor-cloud-agents/lib/repos.js +55 -0
  113. package/dist/extensions/cursor-cloud-agents/skills/handoff.md +27 -0
  114. package/dist/extensions/cursor-cloud-agents/tools/follow_up.d.ts +4 -0
  115. package/dist/extensions/cursor-cloud-agents/tools/follow_up.d.ts.map +1 -0
  116. package/dist/extensions/cursor-cloud-agents/tools/follow_up.js +37 -0
  117. package/dist/extensions/cursor-cloud-agents/tools/launch.d.ts +4 -0
  118. package/dist/extensions/cursor-cloud-agents/tools/launch.d.ts.map +1 -0
  119. package/dist/extensions/cursor-cloud-agents/tools/launch.js +83 -0
  120. package/dist/extensions/cursor-cloud-agents/tools/list.d.ts +4 -0
  121. package/dist/extensions/cursor-cloud-agents/tools/list.d.ts.map +1 -0
  122. package/dist/extensions/cursor-cloud-agents/tools/list.js +36 -0
  123. package/dist/extensions/cursor-cloud-agents/tools/status.d.ts +4 -0
  124. package/dist/extensions/cursor-cloud-agents/tools/status.d.ts.map +1 -0
  125. package/dist/extensions/cursor-cloud-agents/tools/status.js +41 -0
  126. package/dist/extensions.d.ts +49 -0
  127. package/dist/extensions.d.ts.map +1 -0
  128. package/dist/extensions.js +75 -0
  129. package/dist/index.d.ts +2 -1
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +2 -1
  132. package/dist/internal/advertise-tools.d.ts +4 -1
  133. package/dist/internal/advertise-tools.d.ts.map +1 -1
  134. package/dist/internal/advertise-tools.js +20 -1
  135. package/dist/internal/as-of.d.ts +18 -0
  136. package/dist/internal/as-of.d.ts.map +1 -1
  137. package/dist/internal/as-of.js +131 -0
  138. package/dist/internal/cli-deploy.d.ts.map +1 -1
  139. package/dist/internal/cli-deploy.js +64 -19
  140. package/dist/internal/continuation-identity.d.ts.map +1 -1
  141. package/dist/internal/continuation-identity.js +3 -1
  142. package/dist/internal/cursor/hosted-deployment-secrets.d.ts +16 -0
  143. package/dist/internal/cursor/hosted-deployment-secrets.d.ts.map +1 -0
  144. package/dist/internal/cursor/hosted-deployment-secrets.js +61 -0
  145. package/dist/internal/cursor-deployment-relay.d.ts +15 -1
  146. package/dist/internal/cursor-deployment-relay.d.ts.map +1 -1
  147. package/dist/internal/cursor-deployment-relay.js +46 -7
  148. package/dist/internal/deploy-client.d.ts +23 -0
  149. package/dist/internal/deploy-client.d.ts.map +1 -1
  150. package/dist/internal/deploy-client.js +54 -12
  151. package/dist/internal/discovery.d.ts.map +1 -1
  152. package/dist/internal/discovery.js +415 -120
  153. package/dist/internal/distribution.d.ts.map +1 -1
  154. package/dist/internal/distribution.js +5 -0
  155. package/dist/internal/framework-file-storage.d.ts +3 -3
  156. package/dist/internal/framework-file-storage.d.ts.map +1 -1
  157. package/dist/internal/framework-file-storage.js +34 -5
  158. package/dist/internal/hosted-delivery.d.ts +3 -1
  159. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  160. package/dist/internal/hosted-delivery.js +19 -32
  161. package/dist/internal/hosted-execution-diag.d.ts +5 -0
  162. package/dist/internal/hosted-execution-diag.d.ts.map +1 -1
  163. package/dist/internal/hosted-execution-diag.js +5 -0
  164. package/dist/internal/hosted-execution-flush.d.ts +46 -0
  165. package/dist/internal/hosted-execution-flush.d.ts.map +1 -0
  166. package/dist/internal/hosted-execution-flush.js +81 -0
  167. package/dist/internal/mcp-endpoint.js +1 -0
  168. package/dist/internal/mcp-host.d.ts +2 -1
  169. package/dist/internal/mcp-host.d.ts.map +1 -1
  170. package/dist/internal/mcp-host.js +35 -15
  171. package/dist/internal/review-comments.d.ts +1 -1
  172. package/dist/internal/review-comments.d.ts.map +1 -1
  173. package/dist/internal/review-comments.js +4 -4
  174. package/dist/internal/server.d.ts +3 -0
  175. package/dist/internal/server.d.ts.map +1 -1
  176. package/dist/internal/server.js +79 -104
  177. package/dist/internal/session-engine.d.ts +9 -7
  178. package/dist/internal/session-engine.d.ts.map +1 -1
  179. package/dist/internal/session-engine.js +45 -34
  180. package/dist/internal/session-run-log.d.ts +2 -1
  181. package/dist/internal/session-run-log.d.ts.map +1 -1
  182. package/dist/internal/session-run-log.js +2 -1
  183. package/dist/internal/start-log-store.d.ts +60 -0
  184. package/dist/internal/start-log-store.d.ts.map +1 -0
  185. package/dist/internal/start-log-store.js +151 -0
  186. package/dist/internal/tool-policy.d.ts +8 -0
  187. package/dist/internal/tool-policy.d.ts.map +1 -1
  188. package/dist/internal/tool-policy.js +19 -7
  189. package/dist/playground/assets/{index-DVs98vPL.js → index-BnNN1-EK.js} +45 -45
  190. package/dist/playground/assets/index-DBQCwFp0.css +1 -0
  191. package/dist/playground/index.html +2 -2
  192. package/dist/storage-protocol.d.ts +24 -0
  193. package/dist/storage-protocol.d.ts.map +1 -1
  194. package/dist/storage-protocol.js +33 -0
  195. package/dist/storage.d.ts +1 -1
  196. package/dist/storage.d.ts.map +1 -1
  197. package/dist/storage.js +1 -1
  198. package/dist/types.d.ts +118 -6
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/types.js +29 -1
  201. package/docs/reference/connections.md +31 -0
  202. package/docs/reference/http-api.md +1 -1
  203. package/package.json +18 -4
  204. package/src/bin/agent-serve.ts +11 -2
  205. package/src/channels/deployments/deployments-channel.ts +117 -8
  206. package/src/channels/deployments/types.ts +4 -3
  207. package/src/channels/origin/defaults.ts +143 -0
  208. package/src/channels/origin/index.ts +2 -0
  209. package/src/channels/origin/origin-channel.ts +37 -1
  210. package/src/channels/origin/state.ts +54 -0
  211. package/src/channels/origin/types.ts +24 -0
  212. package/src/channels/slack/api.ts +2 -1
  213. package/src/channels/slack/code-fences.ts +183 -0
  214. package/src/channels/slack/index.ts +1 -0
  215. package/src/channels/slack/live-delivery.ts +16 -13
  216. package/src/connections.ts +18 -1
  217. package/src/extensions/cursor-cloud-agents/extension.ts +118 -0
  218. package/src/extensions/cursor-cloud-agents/lib/cloud.ts +79 -0
  219. package/src/extensions/cursor-cloud-agents/lib/fleet.ts +109 -0
  220. package/src/extensions/cursor-cloud-agents/lib/repos.ts +81 -0
  221. package/src/extensions/cursor-cloud-agents/skills/handoff.md +27 -0
  222. package/src/extensions/cursor-cloud-agents/tools/follow_up.ts +29 -0
  223. package/src/extensions/cursor-cloud-agents/tools/launch.ts +84 -0
  224. package/src/extensions/cursor-cloud-agents/tools/list.ts +31 -0
  225. package/src/extensions/cursor-cloud-agents/tools/status.ts +39 -0
  226. package/src/extensions.ts +98 -0
  227. package/src/index.ts +2 -0
  228. package/src/internal/advertise-tools.ts +37 -12
  229. package/src/internal/as-of.ts +163 -0
  230. package/src/internal/cli-deploy.ts +86 -23
  231. package/src/internal/continuation-identity.ts +3 -1
  232. package/src/internal/cursor/hosted-deployment-secrets.ts +72 -0
  233. package/src/internal/cursor-deployment-relay.ts +65 -8
  234. package/src/internal/deploy-client.ts +73 -18
  235. package/src/internal/discovery.ts +470 -113
  236. package/src/internal/distribution.ts +5 -0
  237. package/src/internal/framework-file-storage.ts +39 -5
  238. package/src/internal/hosted-delivery.ts +38 -63
  239. package/src/internal/hosted-execution-diag.ts +6 -0
  240. package/src/internal/hosted-execution-flush.ts +142 -0
  241. package/src/internal/mcp-endpoint.ts +1 -0
  242. package/src/internal/mcp-host.ts +40 -14
  243. package/src/internal/review-comments.ts +5 -2
  244. package/src/internal/server.ts +81 -100
  245. package/src/internal/session-engine.ts +56 -28
  246. package/src/internal/session-run-log.ts +2 -1
  247. package/src/internal/start-log-store.ts +177 -0
  248. package/src/internal/tool-policy.ts +39 -9
  249. package/src/storage-protocol.ts +37 -0
  250. package/src/storage.ts +1 -0
  251. package/src/types.ts +172 -14
  252. package/dist/docs/assets/chunks/@localSearchIndexroot.j70vvPL4.js +0 -1
  253. package/dist/playground/assets/index-BszoQDc6.css +0 -1
  254. /package/dist/docs/assets/{reference_http-api.md.BEJx9XVj.lean.js → reference_http-api.md.oySXBO8o.lean.js} +0 -0
package/dist/types.js CHANGED
@@ -92,6 +92,32 @@ export function normalizeConnectionEffects(effects) {
92
92
  }
93
93
  return unique;
94
94
  }
95
+ /**
96
+ * Trim, drop blanks, and unique-preserve authored
97
+ * {@link ConnectionConfig.tools} name lists. Invalid shapes are left
98
+ * for discovery.
99
+ */
100
+ export function normalizeConnectionTools(tools) {
101
+ const unique = [];
102
+ const seen = new Set();
103
+ for (const raw of tools) {
104
+ const name = raw.trim();
105
+ if (name === "" || seen.has(name)) {
106
+ continue;
107
+ }
108
+ seen.add(name);
109
+ unique.push(name);
110
+ }
111
+ return unique;
112
+ }
113
+ /** True when `toolName` passes the connection's `tools` / `disallowedTools` filter. */
114
+ export function connectionAllowsTool(filter, toolName) {
115
+ if (filter.tools !== undefined && !filter.tools.includes(toolName)) {
116
+ return false;
117
+ }
118
+ return (filter.disallowedTools === undefined ||
119
+ !filter.disallowedTools.includes(toolName));
120
+ }
95
121
  /** True for the peer-agent transport variant (`{ agent: "<slug>" }`). */
96
122
  export function isPeerConnectionTransport(transport) {
97
123
  return "agent" in transport;
@@ -146,7 +172,9 @@ export function isSymbolicConnectionTransport(transport) {
146
172
  }
147
173
  /** True when the connection authors tools instead of a spawn/URL/peer. */
148
174
  export function isInlineStdioConnection(connection) {
149
- return "tools" in connection && connection.tools !== undefined;
175
+ return ("tools" in connection &&
176
+ connection.tools !== undefined &&
177
+ !Array.isArray(connection.tools));
150
178
  }
151
179
  // ============================================================================
152
180
  // Direct tool calls (deterministic invocation, no model turn)
@@ -124,6 +124,37 @@ be called through the direct tool API.
124
124
  In a dry-run session, MCP tools marked read-only run normally. Tools marked
125
125
  as writes are stubbed. Tools without effect annotations are unavailable.
126
126
 
127
+ ## Restrict which tools a connection serves
128
+
129
+ Use `tools` the same way you allowlist harness tools on the agent. When
130
+ set, the connection serves only those names. Use `disallowedTools` to
131
+ drop names instead. The two combine as deny-wins, same as the Cursor
132
+ SDK. The model and `host.mcp` only see what remains. On a model-visible
133
+ connection, set `advertiseTools: true` so the raw server is not attached.
134
+
135
+ ```ts
136
+ export default defineConnection({
137
+ url: "https://mcp.example.com/inventory",
138
+ advertiseTools: true,
139
+ tools: ["search_skus", "get_stock"],
140
+ });
141
+
142
+ export default defineConnection({
143
+ url: "https://mcp.example.com/inventory",
144
+ advertiseTools: true,
145
+ disallowedTools: ["place_order", "delete_sku"],
146
+ });
147
+ ```
148
+
149
+ Names are the server's `tools/list` names. Unknown names are omitted. A
150
+ filter that matches nothing on the server fails the turn. Combine with
151
+ `effects: "read"` to keep only the listed tools the server classifies as
152
+ reads.
153
+
154
+ A list of names is an allowlist. An object of handlers authors TypeScript
155
+ tools. On `host-connections/`, a name list restricts `host.mcp` without
156
+ advertising.
157
+
127
158
  ## Local stdio MCP server
128
159
 
129
160
  Run a local MCP server as a child process with `command`.
@@ -53,7 +53,7 @@ and `playgroundUrl` deep-links the session in the playground.
53
53
  | `message` | Required user message |
54
54
  | `title` | Session title |
55
55
  | `dryRun` | Run read tools and stub write tools |
56
- | `asOf` | ISO-8601 instant with a timezone, frozen at create; the prompt states it, `ctx.now()` returns it, and tools declaring `timeArgs` refuse calls not bounded at or before it. `400` when unusable |
56
+ | `asOf` | ISO-8601 instant with a timezone, frozen at create; the prompt states it, `ctx.now()` returns it, and tool calls with relative, later-than-`asOf`, or omitted schema-declared time bounds are refused. `400` when unusable |
57
57
  | `workspaceFiles` | UTF-8 files written into the session workspace |
58
58
  | `cloud` | Per-session cloud options merged over the agent defaults |
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.100",
3
+ "version": "0.1.101",
4
4
  "description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -95,6 +95,20 @@
95
95
  "import": "./dist/skills.js",
96
96
  "default": "./dist/skills.js"
97
97
  },
98
+ "./extensions": {
99
+ "anysphere-source": "./src/extensions.ts",
100
+ "bun": "./dist/extensions.js",
101
+ "types": "./dist/extensions.d.ts",
102
+ "import": "./dist/extensions.js",
103
+ "default": "./dist/extensions.js"
104
+ },
105
+ "./extensions/cursor-cloud-agents": {
106
+ "anysphere-source": "./src/extensions/cursor-cloud-agents/extension.ts",
107
+ "bun": "./dist/extensions/cursor-cloud-agents/extension.js",
108
+ "types": "./dist/extensions/cursor-cloud-agents/extension.d.ts",
109
+ "import": "./dist/extensions/cursor-cloud-agents/extension.js",
110
+ "default": "./dist/extensions/cursor-cloud-agents/extension.js"
111
+ },
98
112
  "./schedules": {
99
113
  "anysphere-source": "./src/schedules.ts",
100
114
  "types": "./dist/schedules.d.ts",
@@ -215,7 +229,7 @@
215
229
  }
216
230
  },
217
231
  "scripts": {
218
- "build": "tsgo -p tsconfig.build.json && node scripts/bundle-publish-shims.mjs && pnpm run build:playground && pnpm run docs:build",
232
+ "build": "tsgo -p tsconfig.build.json && node scripts/bundle-publish-shims.mjs && node scripts/copy-extension-assets.mjs && pnpm run build:playground && pnpm run docs:build",
219
233
  "build:dist": "node scripts/build-hosted-dist.mjs",
220
234
  "postinstall": "node -e \"try{const p='./scripts/install-cursor-skills.mjs';if(require('fs').existsSync(p))require('child_process').execFileSync(process.execPath,[p],{stdio:'inherit'})}catch{}\"",
221
235
  "build:playground": "vite build --config playground/vite.config.ts",
@@ -240,7 +254,7 @@
240
254
  },
241
255
  "dependencies": {
242
256
  "@bazel/runfiles": "6.5.0",
243
- "@cursor/sdk": "1.0.30",
257
+ "@cursor/sdk": "1.0.31",
244
258
  "@modelcontextprotocol/sdk": "1.25.1",
245
259
  "@octokit/auth-app": "^7.2.0",
246
260
  "@octokit/rest": "^21.1.1",
@@ -303,4 +317,4 @@
303
317
  "yaml": "^2.4.2",
304
318
  "zustand": "^5.0.9"
305
319
  }
306
- }
320
+ }
@@ -426,8 +426,8 @@ Options:
426
426
  --all deploy: every child agent under a multi-agent --dir
427
427
  (skips the interactive picker; mutually exclusive with
428
428
  --slug).
429
- --cursor-events-repo deploy: repo (owner/name) whose SCM events the
430
- deployed engine pulls (repeatable).
429
+ --cursor-events-repo deploy: repo (owner/name) the deployed agent
430
+ watches (repeatable).
431
431
  --allow-domain deploy: egress domain the engine pod may reach
432
432
  (repeatable; unioned with hosting.egressDomains from the
433
433
  agent manifest). Repo-backed deploys only.
@@ -1160,6 +1160,15 @@ async function main(argv: string[]): Promise<number> {
1160
1160
  info.skills.map((s) => `${s.name} — ${s.description}`)
1161
1161
  )
1162
1162
  );
1163
+ lines.push(
1164
+ section(
1165
+ "extensions",
1166
+ info.extensions.map(
1167
+ (e) =>
1168
+ `${e.name} — ${[...e.tools, ...e.skills].join(", ") || "(nothing contributed)"}`
1169
+ )
1170
+ )
1171
+ );
1163
1172
  lines.push(
1164
1173
  section(
1165
1174
  "MCP connections",
@@ -1,7 +1,11 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { defineChannel, localDevStrict, POST } from "../../channels.js";
4
- import { resolveDeploymentSubscriptions } from "../../internal/cursor-deployment-relay.js";
4
+ import {
5
+ deploymentEventMatches,
6
+ resolveDeploymentSubscriptions,
7
+ } from "../../internal/cursor-deployment-relay.js";
8
+ import { attachHostedAdmissionAdapter } from "../../internal/hosted-admission-adapter.js";
5
9
  import { bindLocalIngressSend } from "../../internal/local-ingress.js";
6
10
  import type { ChannelDefinition, ChannelHandlerArgs } from "../../types.js";
7
11
  import {
@@ -43,14 +47,28 @@ export function getDeploymentsChannelOptions(
43
47
  /**
44
48
  * Events a {@link deploymentsChannel} dispatches on, unioned from its
45
49
  * subscriptions. Published as `meta.deploymentEventTypes` so the deploy
46
- * manifest can extract `eventTypes`. An empty deploy-source list is an
47
- * inert channel and yields no events.
50
+ * manifest can extract `eventTypes`.
51
+ *
52
+ * A channel with no resolved subscriptions declares the full event set:
53
+ * deploy sources usually come from env vars read at module load, and the
54
+ * hosted freeze bakes hermetically (no customer env/secrets), so deriving
55
+ * `[]` there would leave the manifest without event types and no
56
+ * subscription edge could ever materialize (invariant 26). Capability is
57
+ * the channel's code shape — `onEvent` handles any deployment event —
58
+ * while sources/environments/event narrowing are runtime subscription
59
+ * config; the hosted-admission adapter re-applies them before authored
60
+ * code runs. Inertness is still guaranteed by scope: a deploy that names
61
+ * no deploy sources binds no edges.
48
62
  */
49
63
  export function deriveDeploymentEventTypes(
50
64
  options: Pick<DeploymentsChannelOptions, "deploySourceUris" | "events">
51
65
  ): string[] {
66
+ const subscriptions = resolveDeploymentSubscriptions(options);
67
+ if (subscriptions.length === 0) {
68
+ return [...DEPLOYMENT_EVENT_TYPES].sort();
69
+ }
52
70
  const events = new Set<string>();
53
- for (const subscription of resolveDeploymentSubscriptions(options)) {
71
+ for (const subscription of subscriptions) {
54
72
  for (const event of subscription.events) {
55
73
  events.add(event);
56
74
  }
@@ -59,9 +77,9 @@ export function deriveDeploymentEventTypes(
59
77
  }
60
78
 
61
79
  /**
62
- * Core deployment-event dispatch, used by the channel's replay route and by
63
- * the serve host's relay fan-out. Whether the event matches this channel's
64
- * subscriptions is the caller's decision.
80
+ * Core deployment-event dispatch, used by the channel's replay route, the
81
+ * serve host's relay fan-out, and the hosted-admission adapter. Whether the
82
+ * event matches this channel's subscriptions is the caller's decision.
65
83
  */
66
84
  export async function dispatchDeploymentEvent(input: {
67
85
  options: DeploymentsChannelOptions;
@@ -77,7 +95,61 @@ export async function dispatchDeploymentEvent(input: {
77
95
  payload: input.event,
78
96
  })
79
97
  );
80
- return Response.json({ ok: true });
98
+ // "task": authored `onEvent` ran to completion without a captured send.
99
+ // Hosted admission only consults this when no send was captured.
100
+ return withDispatchOutcome(Response.json({ ok: true }), "task");
101
+ }
102
+
103
+ export type DeploymentsDispatchOutcome = "skip" | "task";
104
+
105
+ const dispatchOutcomeByResponse = new WeakMap<
106
+ Response,
107
+ DeploymentsDispatchOutcome
108
+ >();
109
+
110
+ function withDispatchOutcome(
111
+ response: Response,
112
+ outcome: DeploymentsDispatchOutcome
113
+ ): Response {
114
+ dispatchOutcomeByResponse.set(response, outcome);
115
+ return response;
116
+ }
117
+
118
+ export function getDeploymentsDispatchOutcome(
119
+ response: Response
120
+ ): DeploymentsDispatchOutcome | undefined {
121
+ return dispatchOutcomeByResponse.get(response);
122
+ }
123
+
124
+ /**
125
+ * Whether this channel admits the event on a hosted pod. The hosted
126
+ * control plane fans out on `(deploy_source, eventType, team)` edges only —
127
+ * environments are not edge identity — so this is where the environment
128
+ * filter (and a stale-edge guard for sources/events) lives, mirroring what
129
+ * the v1 relay does before `onEvent`.
130
+ *
131
+ * A channel with NO resolved subscriptions admits everything the control
132
+ * plane delivered: deploy sources usually come from env vars read at
133
+ * module load, and the hosted freeze bakes hermetically (no customer env),
134
+ * so on a restored pod an env-configured channel resolves empty — the
135
+ * deploy-time edges (bound from the watched repositories) already carry
136
+ * the customer's scope, and refusing here would silently drop every
137
+ * delivery. Same posture as {@link deriveDeploymentEventTypes}: authored
138
+ * subscriptions narrow; an unconfigured channel defers to the deploy scope.
139
+ */
140
+ export function deploymentEventMatchesChannel(
141
+ options: DeploymentsChannelOptions,
142
+ event: DeploymentEvent
143
+ ): boolean {
144
+ const subscriptions = resolveDeploymentSubscriptions(options);
145
+ if (subscriptions.length === 0) {
146
+ return true;
147
+ }
148
+ return subscriptions.some(
149
+ (subscription) =>
150
+ subscription.deploySourceUri === event.deploySourceUri.toLowerCase() &&
151
+ deploymentEventMatches(subscription, event)
152
+ );
81
153
  }
82
154
 
83
155
  /**
@@ -102,6 +174,13 @@ export async function dispatchDeploymentEvent(input: {
102
174
  * have. Nothing is pushed to this deployment and no shared secret exists on
103
175
  * either side.
104
176
  *
177
+ * Hosted v2 deployments do not poll: the control plane fans verified factory
178
+ * deployment events out on `deploy_source` subscription edges — derived from
179
+ * the deploy's watched `repositories`, since factory records deploy sources
180
+ * as Origin repo URLs — and delivers through hosted admission. The attached
181
+ * adapter re-applies this channel's subscription filter — environments are
182
+ * not edge identity — and skips before authored code runs.
183
+ *
105
184
  * The route on this channel exists for local replay (`agent-sdk` and tests):
106
185
  * POST an event body and it runs the same `onEvent`.
107
186
  */
@@ -136,5 +215,35 @@ export function deploymentsChannel(
136
215
  ],
137
216
  });
138
217
  deploymentsChannelOptionsByDefinition.set(definition, options);
218
+ // Hosted v2 delivery: the control plane admits a deployments mailbox item
219
+ // (deploy_source edge match) and posts the claim-checked factory envelope
220
+ // here. Environments are not edge identity, so non-matching environments
221
+ // (and stale-edge sources/events) are filtered to `skip` before authored
222
+ // code runs — the same pre-`onEvent` filter the v1 relay applies.
223
+ attachHostedAdmissionAdapter(definition, {
224
+ kind: DEPLOYMENTS_CHANNEL_KIND,
225
+ dispatch: async ({ payload, args }) => {
226
+ const parsed = deploymentEventSchema.safeParse(payload);
227
+ if (!parsed.success) {
228
+ return Response.json(
229
+ { ok: false, error: "invalid_deployment_event" },
230
+ { status: 400 }
231
+ );
232
+ }
233
+ const event = parsed.data as DeploymentEvent;
234
+ if (!deploymentEventMatchesChannel(options, event)) {
235
+ return withDispatchOutcome(
236
+ Response.json({
237
+ ok: true,
238
+ skipped: true,
239
+ reason: "subscription_mismatch",
240
+ }),
241
+ "skip"
242
+ );
243
+ }
244
+ return dispatchDeploymentEvent({ options, event, args });
245
+ },
246
+ getOutcome: (response) => getDeploymentsDispatchOutcome(response),
247
+ });
139
248
  return definition;
140
249
  }
@@ -54,9 +54,10 @@ export interface DeploymentsChannelOptions {
54
54
  * and then receives nothing. A bare string takes the default environment and
55
55
  * event filters.
56
56
  *
57
- * There is no team-wide subscription: every deploy source is named. At least
58
- * one is required — an empty list is how a consumer *deregisters*, so it
59
- * cannot also mean "everything".
57
+ * There is no team-wide subscription: every deploy source is named. An
58
+ * empty list is how a consumer *deregisters*, so it cannot also mean
59
+ * "everything". A live array that starts empty and is filled later is
60
+ * re-read on each registration heartbeat.
60
61
  */
61
62
  deploySourceUris: readonly (string | DeploymentSubscription)[];
62
63
  /**
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Default stream-event handlers for the Origin channel: reply delivery.
3
+ *
4
+ * The GitHub channel posts completed assistant messages back to the PR via
5
+ * `buildDefaultGitHubEvents`; this is the Origin analogue. Outbound
6
+ * credentials resolve exactly like `ctx.origin` in hooks (see
7
+ * ./binding.ts): channel app credentials / `ORIGIN_APP_*` env when
8
+ * configured, otherwise — for `cursorAccount` channels — the Cursor-account
9
+ * session exchange, which on hosted v2 acts as the deployment
10
+ * service-account principal. The `agent-serve` SA scope allowlist includes
11
+ * `repository:pull_requests:reviews:write`, the scope
12
+ * `CreatePullRequestComment` checks, so no extra grant is needed.
13
+ */
14
+
15
+ import type { ChannelEventHandlers } from "../../types.js";
16
+ import { buildOriginBinding, type OriginHandle } from "./binding.js";
17
+ import type { OriginChannelState } from "./state.js";
18
+ import type { OriginAppOptions } from "./types.js";
19
+
20
+ /** Hidden marker on outbound Origin comments so consumers can spot them. */
21
+ export const ORIGIN_OUTBOUND_MARKER = "<!-- agent-serve-origin -->";
22
+
23
+ export function formatOriginOutboundComment(body: string): string {
24
+ if (body.includes(ORIGIN_OUTBOUND_MARKER)) {
25
+ return body;
26
+ }
27
+ return `${body.trimEnd()}\n\n${ORIGIN_OUTBOUND_MARKER}`;
28
+ }
29
+
30
+ export interface BuildDefaultOriginEventsOptions {
31
+ /** Force the signed-in Cursor account / deployment SA credential path. */
32
+ cursorAccount?: boolean | { repos: readonly string[] };
33
+ /** Channel-authored outbound app credentials / API base. */
34
+ app?: OriginAppOptions;
35
+ /**
36
+ * When false, skip posting completed assistant messages (and failure
37
+ * notices) back to the Origin pull request. Defaults to true.
38
+ */
39
+ deliverReplies?: boolean;
40
+ /** Injectable binding builder for tests. */
41
+ buildBinding?: typeof buildOriginBinding;
42
+ }
43
+
44
+ function bindingForState(
45
+ options: BuildDefaultOriginEventsOptions,
46
+ state: OriginChannelState
47
+ ): OriginHandle {
48
+ const build = options.buildBinding ?? buildOriginBinding;
49
+ return build({
50
+ repository: {
51
+ owner: state.owner,
52
+ name: state.repo,
53
+ fullName: `${state.owner}/${state.repo}`,
54
+ },
55
+ ...(state.installationId === null
56
+ ? {}
57
+ : { installationId: state.installationId }),
58
+ cursorAccount:
59
+ options.cursorAccount !== undefined && options.cursorAccount !== false,
60
+ app: options.app,
61
+ });
62
+ }
63
+
64
+ async function postPullRequestComment(
65
+ options: BuildDefaultOriginEventsOptions,
66
+ state: OriginChannelState,
67
+ body: string
68
+ ): Promise<void> {
69
+ if (
70
+ state.owner === "" ||
71
+ state.repo === "" ||
72
+ state.pullRequestNumber === null
73
+ ) {
74
+ return;
75
+ }
76
+ const origin = bindingForState(options, state);
77
+ await origin.request({
78
+ method: "POST",
79
+ path: `/v1/origin/repos/${encodeURIComponent(state.owner)}/${encodeURIComponent(
80
+ state.repo
81
+ )}/pulls/${state.pullRequestNumber}/comments`,
82
+ body: { body: formatOriginOutboundComment(body) },
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Built-in delivery: post assistant text on `message.completed`, and a
88
+ * short error comment on turn/session failure. Every post soft-fails so a
89
+ * missing outbound credential (or an Origin refusal) never crashes the
90
+ * session — the GitHub defaults' posture.
91
+ */
92
+ export function buildDefaultOriginEvents(
93
+ options: BuildDefaultOriginEventsOptions = {}
94
+ ): ChannelEventHandlers<OriginChannelState> {
95
+ const deliverReplies = options.deliverReplies !== false;
96
+
97
+ return {
98
+ async "message.completed"(event, channel) {
99
+ if (!deliverReplies) {
100
+ return;
101
+ }
102
+ if (event.data.finishReason === "tool_call" || event.data.text === "") {
103
+ return;
104
+ }
105
+ try {
106
+ await postPullRequestComment(options, channel.state, event.data.text);
107
+ } catch {
108
+ // Soft-fail delivery so a missing credential does not crash the session.
109
+ }
110
+ },
111
+
112
+ async "turn.failed"(_event, channel) {
113
+ await postFailure(options, channel, deliverReplies);
114
+ },
115
+
116
+ async "session.failed"(_event, channel) {
117
+ await postFailure(options, channel, deliverReplies);
118
+ },
119
+ };
120
+ }
121
+
122
+ async function postFailure(
123
+ options: BuildDefaultOriginEventsOptions,
124
+ channel: { state: OriginChannelState },
125
+ deliverReplies: boolean
126
+ ): Promise<void> {
127
+ if (!deliverReplies) {
128
+ return;
129
+ }
130
+ try {
131
+ await postPullRequestComment(
132
+ options,
133
+ channel.state,
134
+ [
135
+ "I hit an error while handling your request.",
136
+ "",
137
+ "Please try again, or reach out if it keeps failing.",
138
+ ].join("\n")
139
+ );
140
+ } catch {
141
+ // Soft-fail.
142
+ }
143
+ }
@@ -26,8 +26,10 @@ export * from "./api.js";
26
26
  export * from "./binding.js";
27
27
  export * from "./checks.js";
28
28
  export * from "./cursor-account.js";
29
+ export * from "./defaults.js";
29
30
  export * from "./events.js";
30
31
  export * from "./origin-channel.js";
31
32
  export * from "./origin-webhook.js";
32
33
  export * from "./reactions.js";
34
+ export * from "./state.js";
33
35
  export * from "./types.js";
@@ -18,13 +18,16 @@ import type {
18
18
  AgentCloudOptions,
19
19
  AuthContext,
20
20
  ChannelDefinition,
21
+ ChannelEventHandlers,
21
22
  ChannelHandlerArgs,
23
+ JsonValue,
22
24
  } from "../../types.js";
23
25
  import { buildOriginBinding } from "./binding.js";
24
26
  import {
25
27
  parseCursorOriginRepoScope,
26
28
  registerOriginCursorAccountRuntime,
27
29
  } from "./cursor-account.js";
30
+ import { buildDefaultOriginEvents } from "./defaults.js";
28
31
  import { deriveOriginWebhookEvents, ORIGIN_CHANNEL_KIND } from "./events.js";
29
32
  import {
30
33
  DEFAULT_ORIGIN_API_BASE_URL,
@@ -32,6 +35,11 @@ import {
32
35
  type OriginWebhookHeaders,
33
36
  verifyOriginWebhook,
34
37
  } from "./origin-webhook.js";
38
+ import {
39
+ emptyOriginChannelState,
40
+ type OriginChannelState,
41
+ originChannelStateForEvent,
42
+ } from "./state.js";
35
43
  import type {
36
44
  OriginActor,
37
45
  OriginChannelOptions,
@@ -432,6 +440,7 @@ function enrichEventContext(input: {
432
440
  ...input.ctx,
433
441
  host: input.args.host,
434
442
  artifacts: input.args.artifacts,
443
+ send: input.args.send,
435
444
  origin: buildOriginBinding({
436
445
  repository: input.ctx.repository,
437
446
  installationId: input.ctx.installationId,
@@ -504,6 +513,7 @@ async function startTurn(input: {
504
513
  message: string;
505
514
  dispatch: OriginTurnDispatch;
506
515
  cloud?: AgentCloudOptions;
516
+ state: OriginChannelState;
507
517
  }): Promise<Response> {
508
518
  const source = continuationSource(input.options.continuation, "origin");
509
519
  const session = await input.args.send(input.message, {
@@ -513,6 +523,10 @@ async function startTurn(input: {
513
523
  : {}),
514
524
  title: input.dispatch.title ?? defaultOriginTitle(input.ctx),
515
525
  ...(input.cloud === undefined ? {} : { cloud: input.cloud }),
526
+ state: input.state as unknown as JsonValue,
527
+ // Resume must refresh the triggering comment / delivery identifiers so
528
+ // reply delivery targets the latest wake, not the session's first.
529
+ refreshState: input.state as unknown as JsonValue,
516
530
  });
517
531
  if (isDeferredChannelSession(session)) {
518
532
  // Hosted admission captured the send; the control plane allocates the
@@ -569,6 +583,8 @@ async function applyDispatch(input: {
569
583
  extra: Record<string, unknown>;
570
584
  message: string;
571
585
  cloud?: AgentCloudOptions;
586
+ /** Comment id that woke this dispatch (comment events only). */
587
+ triggeringCommentId?: string;
572
588
  }): Promise<Response> {
573
589
  if (input.dispatch === null) {
574
590
  return skipped("not_dispatched", input.extra);
@@ -587,6 +603,14 @@ async function applyDispatch(input: {
587
603
  message,
588
604
  dispatch: input.dispatch,
589
605
  cloud: input.cloud,
606
+ state: originChannelStateForEvent({
607
+ repository: input.ctx.repository,
608
+ pullRequestNumber: input.ctx.pullRequestNumber,
609
+ installationId: input.ctx.installationId,
610
+ deliveryId: input.ctx.deliveryId,
611
+ eventType: input.ctx.eventType,
612
+ triggeringCommentId: input.triggeringCommentId,
613
+ }),
590
614
  });
591
615
  }
592
616
 
@@ -654,6 +678,7 @@ export async function dispatchOriginWebhook(input: {
654
678
  dispatch,
655
679
  extra: { event: eventType },
656
680
  message: `Origin comment on ${ctx.repository.fullName}#${ctx.pullRequestNumber}`,
681
+ triggeringCommentId: commentId,
657
682
  });
658
683
  }
659
684
 
@@ -798,7 +823,7 @@ export function getOriginChannelOptions(
798
823
 
799
824
  export function originChannel(
800
825
  options: OriginChannelOptions = {}
801
- ): ChannelDefinition {
826
+ ): ChannelDefinition<OriginChannelState> {
802
827
  const usesCursorAccount =
803
828
  options.cursorAccount === true || typeof options.cursorAccount === "object";
804
829
  const cursorScope =
@@ -833,8 +858,19 @@ export function originChannel(
833
858
  );
834
859
  }
835
860
 
861
+ const events: ChannelEventHandlers<OriginChannelState> = {
862
+ ...buildDefaultOriginEvents({
863
+ cursorAccount: channelOptions.cursorAccount,
864
+ app: channelOptions.app,
865
+ deliverReplies: channelOptions.deliverReplies,
866
+ }),
867
+ ...options.events,
868
+ };
869
+
836
870
  const definition = defineChannel({
837
871
  auth,
872
+ state: emptyOriginChannelState(),
873
+ events,
838
874
  onStart: options.onStart,
839
875
  onStop: options.onStop,
840
876
  meta: {
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Durable per-session Origin channel state.
3
+ *
4
+ * Persisted with the turn (same mechanism as `GitHubChannelState`) so
5
+ * channel stream-event handlers — reply delivery in ./defaults.ts — can
6
+ * rebuild a repo-bound Origin handle without re-parsing the webhook.
7
+ */
8
+
9
+ export interface OriginChannelState {
10
+ owner: string;
11
+ repo: string;
12
+ pullRequestNumber: number | null;
13
+ /** Origin app installation id from the delivery envelope, when present. */
14
+ installationId: string | null;
15
+ /** Comment id that woke the turn (comment events only). */
16
+ triggeringCommentId: string | null;
17
+ deliveryId: string | null;
18
+ eventType: string | null;
19
+ }
20
+
21
+ export function emptyOriginChannelState(
22
+ overrides: Partial<OriginChannelState> = {}
23
+ ): OriginChannelState {
24
+ return {
25
+ owner: "",
26
+ repo: "",
27
+ pullRequestNumber: null,
28
+ installationId: null,
29
+ triggeringCommentId: null,
30
+ deliveryId: null,
31
+ eventType: null,
32
+ ...overrides,
33
+ };
34
+ }
35
+
36
+ /** Build the per-turn channel state from a dispatched event's context. */
37
+ export function originChannelStateForEvent(input: {
38
+ repository: { owner: string; name: string };
39
+ pullRequestNumber: number;
40
+ installationId?: string;
41
+ deliveryId?: string;
42
+ eventType: string;
43
+ triggeringCommentId?: string;
44
+ }): OriginChannelState {
45
+ return emptyOriginChannelState({
46
+ owner: input.repository.owner,
47
+ repo: input.repository.name,
48
+ pullRequestNumber: input.pullRequestNumber,
49
+ installationId: input.installationId ?? null,
50
+ triggeringCommentId: input.triggeringCommentId ?? null,
51
+ deliveryId: input.deliveryId ?? null,
52
+ eventType: input.eventType,
53
+ });
54
+ }