@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
@@ -4,11 +4,14 @@ import type {
4
4
  ArtifactsApi,
5
5
  AuthContext,
6
6
  AuthPolicy,
7
+ ChannelEventHandlers,
7
8
  ChannelStartArgs,
8
9
  HostContext,
10
+ SendMessageFn,
9
11
  } from "../../types.js";
10
12
  import type { OriginTokenProvider } from "./api.js";
11
13
  import type { OriginHandle } from "./binding.js";
14
+ import type { OriginChannelState } from "./state.js";
12
15
 
13
16
  export interface OriginActor {
14
17
  id: string;
@@ -70,6 +73,12 @@ export interface OriginEventContext {
70
73
  */
71
74
  payload: Record<string, unknown>;
72
75
  sender?: OriginActor;
76
+ /**
77
+ * Channel `send`. Set on every delivery path (HTTP webhook, hosted
78
+ * admission, SCM events) so a hook can start a turn itself instead of
79
+ * returning `{ auth, message }` or a deferred `{ task }`.
80
+ */
81
+ send?: SendMessageFn;
73
82
  host: HostContext;
74
83
  artifacts: ArtifactsApi;
75
84
  /**
@@ -242,6 +251,21 @@ export interface OriginChannelOptions {
242
251
  * pull request URL, or a resolver for custom cloud options.
243
252
  */
244
253
  cloud?: boolean | AgentCloudOptions | OriginCloudOptionsResolver;
254
+ /**
255
+ * When false, skip posting completed assistant messages (and failure
256
+ * notices) back to the Origin pull request. Defaults to true — the GitHub
257
+ * channel's reply-delivery posture. Outbound credentials resolve like
258
+ * `ctx.origin`: {@link app} / `ORIGIN_APP_*` env when configured,
259
+ * otherwise the Cursor-account session exchange (hosted v2 deployments
260
+ * post as the deployment service-account principal).
261
+ */
262
+ deliverReplies?: boolean;
263
+ /**
264
+ * Channel stream-event handlers, merged over the built-in reply-delivery
265
+ * defaults (`buildDefaultOriginEvents`) — same override contract as the
266
+ * GitHub channel's `events`.
267
+ */
268
+ events?: ChannelEventHandlers<OriginChannelState>;
245
269
  /** Called once when the server mounts this channel. */
246
270
  onStart?: (args: ChannelStartArgs) => void | Promise<void>;
247
271
  /** Called when the server shuts down. */
@@ -5,6 +5,7 @@
5
5
  import { ErrorCode, type WebAPIPlatformError, WebClient } from "@slack/web-api";
6
6
  import { slackifyMarkdown } from "slackify-markdown";
7
7
  import type { ArtifactsApi, HostContext } from "../../types.js";
8
+ import { highlightCodeFences } from "./code-fences.js";
8
9
  import {
9
10
  SLACK_DEFAULT_LOADING_MESSAGES,
10
11
  SLACK_LOADING_MESSAGE_MAX_LEN,
@@ -495,7 +496,7 @@ export async function postMessage(args: {
495
496
  operation: "chat.postMessage",
496
497
  body: {
497
498
  ...base,
498
- markdown_text: args.text,
499
+ markdown_text: highlightCodeFences(args.text),
499
500
  },
500
501
  });
501
502
  // Older workspaces / scopes may not accept markdown_text — retry with text.
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Slack highlights a fenced code block only when its info string is a
3
+ * language it knows (`ts`, `python`, …). Models on this harness cite code
4
+ * with Cursor's `startLine:endLine:path` form, or a bare path, which Slack
5
+ * shows verbatim as the block's header with no highlighting. The header is
6
+ * the only slot Slack offers, so a block gets colors by moving the reference
7
+ * to a comment on its first line. Files whose language has no comment
8
+ * syntax, or that Slack cannot color anyway, keep the reference as header.
9
+ */
10
+
11
+ const FENCE = /^(\s*)(`{3,}|~{3,})(.*)$/;
12
+ /** A line that may still grow into a fence. */
13
+ const FENCE_START = /^\s*(`{1,2}|~{1,2})$/;
14
+ const CITATION = /^(\d+):(\d+):(\S+)$/;
15
+
16
+ const LANGUAGE_BY_EXTENSION: Readonly<Record<string, string>> = {
17
+ ts: "ts",
18
+ mts: "ts",
19
+ cts: "ts",
20
+ tsx: "tsx",
21
+ js: "js",
22
+ mjs: "js",
23
+ cjs: "js",
24
+ jsx: "jsx",
25
+ py: "python",
26
+ go: "go",
27
+ rs: "rust",
28
+ java: "java",
29
+ kt: "kotlin",
30
+ swift: "swift",
31
+ rb: "ruby",
32
+ php: "php",
33
+ c: "c",
34
+ h: "c",
35
+ cc: "cpp",
36
+ cpp: "cpp",
37
+ hpp: "cpp",
38
+ cs: "csharp",
39
+ json: "json",
40
+ jsonc: "jsonc",
41
+ yaml: "yaml",
42
+ yml: "yaml",
43
+ toml: "toml",
44
+ xml: "xml",
45
+ html: "html",
46
+ css: "css",
47
+ scss: "scss",
48
+ md: "markdown",
49
+ mdx: "mdx",
50
+ sh: "bash",
51
+ bash: "bash",
52
+ zsh: "bash",
53
+ sql: "sql",
54
+ proto: "protobuf",
55
+ tf: "hcl",
56
+ graphql: "graphql",
57
+ dockerfile: "dockerfile",
58
+ ini: "ini",
59
+ };
60
+
61
+ /** `[open, close]` comment delimiters; languages without one (JSON) are absent. */
62
+ const COMMENT_BY_LANGUAGE: Readonly<Record<string, readonly [string, string]>> =
63
+ {
64
+ ts: ["//", ""],
65
+ tsx: ["//", ""],
66
+ js: ["//", ""],
67
+ jsx: ["//", ""],
68
+ go: ["//", ""],
69
+ rust: ["//", ""],
70
+ java: ["//", ""],
71
+ kotlin: ["//", ""],
72
+ swift: ["//", ""],
73
+ php: ["//", ""],
74
+ c: ["//", ""],
75
+ cpp: ["//", ""],
76
+ csharp: ["//", ""],
77
+ jsonc: ["//", ""],
78
+ protobuf: ["//", ""],
79
+ python: ["#", ""],
80
+ ruby: ["#", ""],
81
+ yaml: ["#", ""],
82
+ toml: ["#", ""],
83
+ bash: ["#", ""],
84
+ dockerfile: ["#", ""],
85
+ ini: ["#", ""],
86
+ hcl: ["#", ""],
87
+ graphql: ["#", ""],
88
+ sql: ["--", ""],
89
+ css: ["/*", "*/"],
90
+ scss: ["/*", "*/"],
91
+ html: ["<!--", "-->"],
92
+ xml: ["<!--", "-->"],
93
+ markdown: ["<!--", "-->"],
94
+ mdx: ["<!--", "-->"],
95
+ };
96
+
97
+ function languageForPath(path: string): string | undefined {
98
+ const base = path.slice(path.lastIndexOf("/") + 1).toLowerCase();
99
+ const ext = base.includes(".") ? base.slice(base.lastIndexOf(".") + 1) : base;
100
+ return LANGUAGE_BY_EXTENSION[ext];
101
+ }
102
+
103
+ function looksLikePath(info: string): boolean {
104
+ return info.includes("/") || languageForPath(info) !== undefined;
105
+ }
106
+
107
+ /**
108
+ * Replacement lines for an opening fence whose info string is a citation or
109
+ * a path: the fence tagged with the language, then the reference as a
110
+ * comment. Falls back to the reference as the header when the block cannot
111
+ * carry a comment. `null` when the info string is neither.
112
+ */
113
+ function rewriteOpening(
114
+ indent: string,
115
+ marks: string,
116
+ info: string
117
+ ): string[] | null {
118
+ const token = info.trim().split(/\s+/)[0] ?? "";
119
+ const cited = CITATION.exec(token);
120
+ const path = cited?.[3] ?? (looksLikePath(token) ? token : undefined);
121
+ if (path === undefined) {
122
+ return null;
123
+ }
124
+ const reference = cited === null ? path : `${path}:${cited[1]}-${cited[2]}`;
125
+ const language = languageForPath(path);
126
+ const comment =
127
+ language === undefined ? undefined : COMMENT_BY_LANGUAGE[language];
128
+ if (comment === undefined) {
129
+ return [`${indent}${marks}${reference}`];
130
+ }
131
+ const [open, close] = comment;
132
+ const commented =
133
+ close === "" ? `${open} ${reference}` : `${open} ${reference} ${close}`;
134
+ return [`${indent}${marks}${language}`, `${indent}${commented}`];
135
+ }
136
+
137
+ /**
138
+ * Rewrite fence info strings so Slack highlights the block. Safe to run on
139
+ * a growing stream: the output for a prefix is a prefix of the output for
140
+ * the full text, because an opening fence whose line has not ended yet is
141
+ * held back rather than emitted half-formed.
142
+ */
143
+ export function highlightCodeFences(markdown: string): string {
144
+ const complete = markdown.endsWith("\n");
145
+ const lines = markdown.split("\n");
146
+ if (complete) {
147
+ lines.pop();
148
+ }
149
+ const out: string[] = [];
150
+ let open: { marks: string } | null = null;
151
+ const heldBack = (): string =>
152
+ `${out.join("\n")}${out.length > 0 ? "\n" : ""}`;
153
+ for (const [index, line] of lines.entries()) {
154
+ const last = index === lines.length - 1 && !complete;
155
+ const fence = FENCE.exec(line);
156
+ if (fence === null) {
157
+ if (last && open === null && FENCE_START.test(line)) {
158
+ return heldBack();
159
+ }
160
+ out.push(line);
161
+ continue;
162
+ }
163
+ const [, indent = "", marks = "", info = ""] = fence;
164
+ if (open !== null) {
165
+ if (
166
+ marks[0] === open.marks[0] &&
167
+ marks.length >= open.marks.length &&
168
+ info.trim() === ""
169
+ ) {
170
+ open = null;
171
+ }
172
+ out.push(line);
173
+ continue;
174
+ }
175
+ if (last) {
176
+ // Opening fence still being written: wait for the whole line.
177
+ return heldBack();
178
+ }
179
+ open = { marks };
180
+ out.push(...(rewriteOpening(indent, marks, info) ?? [line]));
181
+ }
182
+ return complete ? `${out.join("\n")}\n` : out.join("\n");
183
+ }
@@ -21,6 +21,7 @@ export * from "./auth.js";
21
21
  export * from "./blocks.js";
22
22
  export * from "./bot-mentions.js";
23
23
  export * from "./channel-watch.js";
24
+ export * from "./code-fences.js";
24
25
  export * from "./constants.js";
25
26
  export * from "./credentials.js";
26
27
  export * from "./defaults.js";
@@ -22,6 +22,7 @@ import {
22
22
  startChatStream,
23
23
  stopChatStream,
24
24
  } from "./api.js";
25
+ import { highlightCodeFences } from "./code-fences.js";
25
26
  import { SLACK_MARKDOWN_TEXT_MAX_LEN } from "./constants.js";
26
27
  import type { SlackLog } from "./log.js";
27
28
  import {
@@ -117,7 +118,7 @@ export function createStreamDeliveryStrategy(
117
118
  const openingText = markdown_text ?? "";
118
119
  const opening: StreamChunk[] = [];
119
120
  if (openingText !== "") {
120
- opening.push(markdownChunk(openingText));
121
+ opening.push(markdownChunk(highlightCodeFences(openingText)));
121
122
  }
122
123
  opening.push(...(chunks ?? []));
123
124
  if (opening.length === 0) {
@@ -181,16 +182,24 @@ export function createStreamDeliveryStrategy(
181
182
  return opened ? activeStreamTs(args.channel) : null;
182
183
  };
183
184
 
185
+ /**
186
+ * What to append for the new cumulative text, as Slack should see it: the
187
+ * block text on both sides goes through the same fence rewrite, so the
188
+ * delta lines up with what the stream already holds.
189
+ */
190
+ const streamDelta = (channel: SlackChannel, text: string): string =>
191
+ computeStreamAppend(
192
+ highlightCodeFences(channel.state.streamBlockText ?? ""),
193
+ highlightCodeFences(text)
194
+ );
195
+
184
196
  const stopWithSuffix = async (args: {
185
197
  channel: SlackChannel;
186
198
  channelId: string;
187
199
  ts: string;
188
200
  text: string;
189
201
  }): Promise<{ ok: boolean; error?: string }> => {
190
- const appendText = computeStreamAppend(
191
- args.channel.state.streamBlockText,
192
- args.text
193
- );
202
+ const appendText = streamDelta(args.channel, args.text);
194
203
  const stopped = await stopChatStream({
195
204
  botToken,
196
205
  channel: args.channelId,
@@ -299,10 +308,7 @@ export function createStreamDeliveryStrategy(
299
308
  if (ts === null) {
300
309
  return;
301
310
  }
302
- const appendText = computeStreamAppend(
303
- channel.state.streamBlockText,
304
- text
305
- );
311
+ const appendText = streamDelta(channel, text);
306
312
  if (appendText === "") {
307
313
  return;
308
314
  }
@@ -374,10 +380,7 @@ export function createStreamDeliveryStrategy(
374
380
  return { delivered: false };
375
381
  }
376
382
  // Measured before the stop attempt mutates stream state.
377
- const remainder = computeStreamAppend(
378
- channel.state.streamBlockText,
379
- text
380
- );
383
+ const remainder = streamDelta(channel, text);
381
384
  const stopped =
382
385
  channel.state.streamFull === true
383
386
  ? { ok: false, error: "msg_too_long" }
@@ -8,7 +8,9 @@
8
8
  * are available to the agent (SDK `mcpServers`), host (`ctx.host.mcp`),
9
9
  * and channel/schedule handlers. Pass `advertiseTools: true` for named
10
10
  * tools on local turns. Pass `effects: "read"` with `advertiseTools` to
11
- * advertise only tools the server classifies as reads. Files under
11
+ * advertise only tools the server classifies as reads. Pass `tools` as
12
+ * a name list to keep a subset, same shape as `defineAgent({ tools })`.
13
+ * Pass `disallowedTools` to drop names. Files under
12
14
  * `host-connections/` stay off the model.
13
15
  *
14
16
  * ```ts
@@ -42,6 +44,20 @@
42
44
  * advertiseTools: true,
43
45
  * effects: "read",
44
46
  * });
47
+ *
48
+ * // named subset: advertise and call only these tools
49
+ * export default defineConnection({
50
+ * url: "https://mcp.example.com/inventory",
51
+ * advertiseTools: true,
52
+ * tools: ["search_skus", "get_stock"],
53
+ * });
54
+ *
55
+ * // drop a few tools from a large server
56
+ * export default defineConnection({
57
+ * url: "https://mcp.example.com/inventory",
58
+ * advertiseTools: true,
59
+ * disallowedTools: ["place_order", "delete_sku"],
60
+ * });
45
61
  * ```
46
62
  *
47
63
  * A **Cursor account MCP connection** exposes the MCP connectors the operator
@@ -129,6 +145,7 @@ export {
129
145
  isInlineStdioConnection,
130
146
  isToolEffect,
131
147
  normalizeConnectionEffects,
148
+ normalizeConnectionTools,
132
149
  normalizeCursorAccountServers,
133
150
  } from "./types.js";
134
151
 
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Cursor cloud agents as an extension. Mount it and the agent can hand
3
+ * coding work to Cursor cloud agents and steer a fleet of them:
4
+ *
5
+ * ```ts
6
+ * // agent/extensions/cloud.ts
7
+ * import cursorCloudAgents from "@cursor/july/extensions/cursor-cloud-agents";
8
+ *
9
+ * export default cursorCloudAgents({
10
+ * cloud: { repos: [{ url: "https://github.com/acme/api" }], autoCreatePR: true },
11
+ * });
12
+ * ```
13
+ *
14
+ * Contributes `cloud__launch`, `cloud__follow_up`, `cloud__status`,
15
+ * `cloud__list` and the `cloud__handoff` skill (the namespace is the mount
16
+ * filename). Calls go through the Cursor SDK with the serve host's Cursor
17
+ * API key (`CURSOR_API_KEY`, key file, or `login`); each launched agent
18
+ * stays bound to the principal that launched it, and launches go to the
19
+ * configured repositories. The tools run on the serve host, so they need
20
+ * the local runtime.
21
+ */
22
+
23
+ import { z } from "zod";
24
+ import { defineExtension } from "../../extensions.js";
25
+ import type {
26
+ AgentCloudOptions,
27
+ Extension,
28
+ ModelSetting,
29
+ } from "../../types.js";
30
+
31
+ /** Mount options; every field is optional. */
32
+ export interface CursorCloudAgentsOptions {
33
+ /**
34
+ * Launch defaults: target `repos`, `env`, `autoCreatePR`, ... in the same
35
+ * vocabulary as `defineAgent({ cloud })`. Configured `repos` are also the
36
+ * allowlist for a launch's own `repo` / `prUrl`; model-chosen targets are
37
+ * refused when no allowlist is configured. With none configured,
38
+ * `needsApproval` defaults to true so prompt-only launches park first.
39
+ */
40
+ cloud?: AgentCloudOptions;
41
+ /** Model for launched agents. The account default when omitted. */
42
+ model?: ModelSetting;
43
+ /**
44
+ * Park launches and follow-ups for a human approval. Defaults to `true`
45
+ * when no `cloud.repos` are configured (prompt-only launches still use
46
+ * the host key); `false` once repositories are configured. Pass an
47
+ * explicit boolean to override. Model-chosen `repo` / `prUrl` still
48
+ * require the allowlist regardless of this flag.
49
+ */
50
+ needsApproval?: boolean;
51
+ /** Cursor API key. Defaults to the serve host's own key. */
52
+ apiKey?: string;
53
+ }
54
+
55
+ /** {@link CursorCloudAgentsOptions} with defaults applied. */
56
+ export interface CursorCloudAgentsConfig {
57
+ cloud: AgentCloudOptions;
58
+ model?: ModelSetting;
59
+ needsApproval: boolean;
60
+ apiKey?: string;
61
+ }
62
+
63
+ const repo = z.object({
64
+ url: z.string().url(),
65
+ startingRef: z.string().optional(),
66
+ prUrl: z.string().url().optional(),
67
+ });
68
+
69
+ const cloud: z.ZodType<AgentCloudOptions> = z.object({
70
+ env: z
71
+ .discriminatedUnion("type", [
72
+ z.object({ type: z.literal("cloud"), name: z.string().optional() }),
73
+ z.object({ type: z.literal("pool"), name: z.string().optional() }),
74
+ z.object({ type: z.literal("machine"), name: z.string().optional() }),
75
+ ])
76
+ .optional(),
77
+ repos: z.array(repo).optional(),
78
+ workOnCurrentBranch: z.boolean().optional(),
79
+ autoCreatePR: z.boolean().optional(),
80
+ skipReviewerRequest: z.boolean().optional(),
81
+ envVars: z.record(z.string()).optional(),
82
+ });
83
+
84
+ const model: z.ZodType<ModelSetting> = z.union([
85
+ z.string().min(1),
86
+ z.object({
87
+ id: z.string().min(1),
88
+ params: z.array(z.object({ id: z.string(), value: z.string() })).optional(),
89
+ }),
90
+ ]);
91
+
92
+ const config: z.ZodType<
93
+ CursorCloudAgentsConfig,
94
+ z.ZodTypeDef,
95
+ CursorCloudAgentsOptions | undefined
96
+ > = z
97
+ .object({
98
+ cloud: cloud.default({}),
99
+ model: model.optional(),
100
+ needsApproval: z.boolean().optional(),
101
+ apiKey: z.string().optional(),
102
+ })
103
+ .default({})
104
+ .transform((options) => ({
105
+ ...options,
106
+ needsApproval: options.needsApproval ?? options.cloud.repos === undefined,
107
+ }));
108
+
109
+ const cursorCloudAgents: Extension<
110
+ CursorCloudAgentsConfig,
111
+ CursorCloudAgentsOptions | undefined
112
+ > = defineExtension({
113
+ root: import.meta.dirname,
114
+ description: "Launch, steer, and monitor Cursor cloud agents.",
115
+ config,
116
+ });
117
+
118
+ export default cursorCloudAgents;
@@ -0,0 +1,79 @@
1
+ /** The Cursor SDK surface the tools share: auth, model, result views. */
2
+
3
+ import type { ModelSelection, Run, RunStatus, SDKAgentInfo } from "@cursor/sdk";
4
+ import { z } from "zod";
5
+ import {
6
+ cursorWebsiteUrl,
7
+ resolveApiKeySync,
8
+ SIGN_IN_HINT,
9
+ } from "../../../internal/cursor/credentials.js";
10
+ import { toModelSelection } from "../../../internal/sdk-runner.js";
11
+ import extension from "../extension.js";
12
+
13
+ export const agentRef: z.ZodObject<{ agentId: z.ZodString }> = z.object({
14
+ agentId: z.string().describe("Agent id (`bc-...`) returned by a launch."),
15
+ });
16
+
17
+ export function apiKey(): string {
18
+ const key = resolveApiKeySync({ apiKey: extension.config.apiKey });
19
+ if (key === undefined) {
20
+ throw new Error(`Cloud agents need a Cursor API key. ${SIGN_IN_HINT}`);
21
+ }
22
+ return key.apiKey;
23
+ }
24
+
25
+ export function modelSelection(): ModelSelection | undefined {
26
+ const { model } = extension.config;
27
+ return model === undefined ? undefined : toModelSelection(model);
28
+ }
29
+
30
+ export function agentUrl(agentId: string): string {
31
+ return `${cursorWebsiteUrl()}/agents/${agentId}`;
32
+ }
33
+
34
+ // Type aliases, not interfaces: views flow into tool results.
35
+ export type AgentView = {
36
+ agentId: string;
37
+ name: string;
38
+ archived: boolean | undefined;
39
+ url: string;
40
+ };
41
+
42
+ export type RunView = {
43
+ id: string;
44
+ status: RunStatus;
45
+ result: string | undefined;
46
+ error: string | undefined;
47
+ durationMs: number | undefined;
48
+ branches:
49
+ | Array<{
50
+ repoUrl: string;
51
+ branch: string | undefined;
52
+ prUrl: string | undefined;
53
+ }>
54
+ | undefined;
55
+ };
56
+
57
+ export function agentView(info: SDKAgentInfo): AgentView {
58
+ return {
59
+ agentId: info.agentId,
60
+ name: info.name,
61
+ archived: info.archived,
62
+ url: agentUrl(info.agentId),
63
+ };
64
+ }
65
+
66
+ export function runView(run: Run): RunView {
67
+ return {
68
+ id: run.id,
69
+ status: run.status,
70
+ result: run.result,
71
+ error: run.error?.message,
72
+ durationMs: run.durationMs,
73
+ branches: run.git?.branches.map((branch) => ({
74
+ repoUrl: branch.repoUrl,
75
+ branch: branch.branch,
76
+ prUrl: branch.prUrl,
77
+ })),
78
+ };
79
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Who launched which cloud agent. The tools authenticate with the serve
3
+ * host's one Cursor API key, so the host itself must keep every session's
4
+ * cloud agents bound to the principal that launched them: status and
5
+ * follow-ups only reach agents recorded here for the same principal, and
6
+ * the fleet listing is that principal's records, by default narrowed to the
7
+ * session that launched them.
8
+ */
9
+
10
+ import { z } from "zod";
11
+ import type { ToolContext } from "../../../types.js";
12
+
13
+ const PREFIX = "cloud-agents";
14
+
15
+ // Type alias, not an interface: fleet entries flow into tool results.
16
+ export type FleetEntry = {
17
+ agentId: string;
18
+ name?: string;
19
+ launchedAt: string;
20
+ /** The session whose turn launched the agent; absent on records older than this field. */
21
+ sessionId?: string;
22
+ };
23
+
24
+ export type FleetScope = "session" | "all";
25
+
26
+ const fleetSchema: z.ZodType<FleetEntry[]> = z.array(
27
+ z.object({
28
+ agentId: z.string(),
29
+ name: z.string().optional(),
30
+ launchedAt: z.string(),
31
+ sessionId: z.string().optional(),
32
+ })
33
+ );
34
+ const ownerSchema = z.object({ principal: z.string() });
35
+
36
+ // `host.kv` has no compare-and-swap, and a model that launches two agents in
37
+ // one turn calls this tool in parallel. Chain the index read-modify-write per
38
+ // principal so neither launch overwrites the other's entry.
39
+ const indexWrites = new Map<string, Promise<void>>();
40
+
41
+ function withIndexLock(key: string, task: () => Promise<void>): Promise<void> {
42
+ const run = (indexWrites.get(key) ?? Promise.resolve()).then(task, task);
43
+ indexWrites.set(key, run);
44
+ return run.finally(() => {
45
+ if (indexWrites.get(key) === run) {
46
+ indexWrites.delete(key);
47
+ }
48
+ });
49
+ }
50
+
51
+ /** Unauthenticated sessions (local dev, direct calls) share one principal. */
52
+ export function principalOf(ctx: ToolContext): string {
53
+ const auth = ctx.session.auth;
54
+ return auth === null
55
+ ? "anonymous"
56
+ : `${auth.principalType}:${auth.principalId}`;
57
+ }
58
+
59
+ export async function recordLaunch(
60
+ ctx: ToolContext,
61
+ agentId: string,
62
+ name: string | undefined
63
+ ): Promise<void> {
64
+ const principal = principalOf(ctx);
65
+ const entry: FleetEntry = {
66
+ agentId,
67
+ name,
68
+ launchedAt: new Date().toISOString(),
69
+ sessionId: ctx.session.id,
70
+ };
71
+ await ctx.host.kv.put(`${PREFIX}/agents/${agentId}`, { principal });
72
+ await withIndexLock(fleetKey(principal), async () => {
73
+ const fleet = await listFleet(ctx);
74
+ await ctx.host.kv.put(fleetKey(principal), [entry, ...fleet]);
75
+ });
76
+ }
77
+
78
+ /** Refuse agents launched by another principal, or not through the extension. */
79
+ export async function assertOwned(
80
+ ctx: ToolContext,
81
+ agentId: string
82
+ ): Promise<void> {
83
+ const owner = ownerSchema.safeParse(
84
+ await ctx.host.kv.get(`${PREFIX}/agents/${agentId}`)
85
+ );
86
+ if (!owner.success || owner.data.principal !== principalOf(ctx)) {
87
+ throw new Error(
88
+ `Agent ${agentId} was not launched through this extension by this principal; only your own launches can be read or steered.`
89
+ );
90
+ }
91
+ }
92
+
93
+ /** The caller's launches, newest first: this session's, or every session's. */
94
+ export async function listFleet(
95
+ ctx: ToolContext,
96
+ scope: FleetScope = "all"
97
+ ): Promise<FleetEntry[]> {
98
+ const fleet = fleetSchema.safeParse(
99
+ await ctx.host.kv.get(fleetKey(principalOf(ctx)))
100
+ );
101
+ const entries = fleet.success ? fleet.data : [];
102
+ return scope === "all"
103
+ ? entries
104
+ : entries.filter((entry) => entry.sessionId === ctx.session.id);
105
+ }
106
+
107
+ function fleetKey(principal: string): string {
108
+ return `${PREFIX}/principals/${encodeURIComponent(principal)}`;
109
+ }