@cursor/july 0.1.87 → 0.1.89

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 (73) hide show
  1. package/dist/bin/agent-serve.js +8 -1
  2. package/dist/internal/advertise-tools.d.ts.map +1 -1
  3. package/dist/internal/advertise-tools.js +4 -2
  4. package/dist/internal/cli-ax.js +1 -1
  5. package/dist/internal/cli-deploy.d.ts +3 -2
  6. package/dist/internal/cli-deploy.d.ts.map +1 -1
  7. package/dist/internal/cli-deploy.js +84 -9
  8. package/dist/internal/cli-docs.js +11 -0
  9. package/dist/internal/cloud-merge.d.ts +3 -1
  10. package/dist/internal/cloud-merge.d.ts.map +1 -1
  11. package/dist/internal/cloud-merge.js +10 -2
  12. package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
  13. package/dist/internal/cursor/account-mcp.js +8 -4
  14. package/dist/internal/cursor/backend-client.d.ts +12 -0
  15. package/dist/internal/cursor/backend-client.d.ts.map +1 -1
  16. package/dist/internal/cursor/backend-client.js +27 -1
  17. package/dist/internal/deploy-client.d.ts +42 -1
  18. package/dist/internal/deploy-client.d.ts.map +1 -1
  19. package/dist/internal/deploy-client.js +70 -2
  20. package/dist/internal/discovery.d.ts.map +1 -1
  21. package/dist/internal/discovery.js +87 -11
  22. package/dist/internal/docs-site.d.ts +13 -2
  23. package/dist/internal/docs-site.d.ts.map +1 -1
  24. package/dist/internal/docs-site.js +76 -13
  25. package/dist/internal/grokbot/runner.d.ts +61 -0
  26. package/dist/internal/grokbot/runner.d.ts.map +1 -0
  27. package/dist/internal/grokbot/runner.js +278 -0
  28. package/dist/internal/mcp-endpoint.js +6 -3
  29. package/dist/internal/mcp-host.d.ts +14 -1
  30. package/dist/internal/mcp-host.d.ts.map +1 -1
  31. package/dist/internal/mcp-host.js +41 -2
  32. package/dist/internal/runtime-dispatch-runner.d.ts +30 -0
  33. package/dist/internal/runtime-dispatch-runner.d.ts.map +1 -0
  34. package/dist/internal/runtime-dispatch-runner.js +60 -0
  35. package/dist/internal/sdk-runner.d.ts.map +1 -1
  36. package/dist/internal/sdk-runner.js +7 -0
  37. package/dist/internal/server.d.ts.map +1 -1
  38. package/dist/internal/server.js +24 -1
  39. package/dist/internal/session-engine.d.ts +9 -2
  40. package/dist/internal/session-engine.d.ts.map +1 -1
  41. package/dist/internal/session-engine.js +121 -46
  42. package/dist/internal/tool-policy.d.ts +33 -1
  43. package/dist/internal/tool-policy.d.ts.map +1 -1
  44. package/dist/internal/tool-policy.js +53 -0
  45. package/dist/playground/assets/{index-Bqn91tW4.js → index-BmiIjFlM.js} +45 -45
  46. package/dist/playground/assets/index-DQGZnAI0.css +1 -0
  47. package/dist/playground/index.html +2 -2
  48. package/dist/types.d.ts +86 -2
  49. package/dist/types.d.ts.map +1 -1
  50. package/package.json +1 -1
  51. package/skills/debug/SKILL.md +1 -2
  52. package/skills/framework-map/SKILL.md +1 -2
  53. package/src/bin/agent-serve.ts +9 -1
  54. package/src/internal/advertise-tools.ts +7 -0
  55. package/src/internal/cli-ax.ts +1 -1
  56. package/src/internal/cli-deploy.ts +123 -15
  57. package/src/internal/cli-docs.ts +11 -0
  58. package/src/internal/cloud-merge.ts +12 -2
  59. package/src/internal/cursor/account-mcp.ts +15 -1
  60. package/src/internal/cursor/backend-client.ts +37 -0
  61. package/src/internal/deploy-client.ts +122 -2
  62. package/src/internal/discovery.ts +133 -13
  63. package/src/internal/docs-site.ts +83 -13
  64. package/src/internal/grokbot/runner.ts +361 -0
  65. package/src/internal/mcp-endpoint.ts +6 -1
  66. package/src/internal/mcp-host.ts +48 -0
  67. package/src/internal/runtime-dispatch-runner.ts +63 -0
  68. package/src/internal/sdk-runner.ts +9 -0
  69. package/src/internal/server.ts +32 -4
  70. package/src/internal/session-engine.ts +126 -21
  71. package/src/internal/tool-policy.ts +80 -1
  72. package/src/types.ts +84 -2
  73. package/dist/playground/assets/index-C0_5hOsf.css +0 -1
@@ -5,13 +5,15 @@
5
5
  *
6
6
  * All intelligence is server-side (backend/server/src/agentServe/); these
7
7
  * handlers resolve parameters (credentials, team, slug, the project's
8
- * `hosting` manifest block), call the management API through
9
- * `deploy-client.ts`, poll status, and render.
8
+ * `hosting` / `architecture` manifest block), call the v1 agent-serve or
9
+ * v2 agentkit management API through `deploy-client.ts`, poll status, and
10
+ * render.
10
11
  */
11
12
 
12
13
  import { basename, resolve } from "node:path";
13
14
  import { isPublicEndpointPolicy } from "../channels.js";
14
15
  import {
16
+ type AgentArchitecture,
15
17
  type AgentHostingOptions,
16
18
  isCursorAccountTransport,
17
19
  } from "../types.js";
@@ -24,18 +26,21 @@ import {
24
26
  import { authorizeCursorAccountConnectors } from "./cursor-account-mcp-auth.js";
25
27
  import { readDeployTemplate } from "./cursor-agent-template.js";
26
28
  import {
29
+ type AgentkitDeployAccepted,
30
+ type AgentkitDeployStatus,
27
31
  AgentServeDeployClient,
28
32
  type DeleteAccepted,
29
33
  type DeployAccepted,
30
34
  DeployApiError,
31
35
  type Deployment,
32
36
  type DeploymentDetail,
33
- type DeploymentStatus,
37
+ type DeploymentProgress,
34
38
  type DeploySource,
35
39
  deploymentServiceAccountId,
36
40
  type SecretInfo,
37
41
  type StopAccepted,
38
42
  slugFromDirectoryName,
43
+ TERMINAL_AGENTKIT_DEPLOY_STATUSES,
39
44
  TERMINAL_DEPLOY_STATUSES,
40
45
  validateDeploymentSlug,
41
46
  } from "./deploy-client.js";
@@ -648,6 +653,19 @@ async function deployOne(
648
653
  );
649
654
  }
650
655
 
656
+ if (manifest?.architecture === "v2") {
657
+ if (!ctx.json) {
658
+ ctx.err(" architecture: v2\n");
659
+ }
660
+ return deployOneAgentkit(ctx, extras, {
661
+ slug,
662
+ gitRepoUrl,
663
+ gitRef,
664
+ agentPath,
665
+ repositories: options.cursorEventsRepos,
666
+ });
667
+ }
668
+
651
669
  // Today's only source shape: the control plane builds from a git ref.
652
670
  // See DeploySource for the artifact seam.
653
671
  const source: DeploySource = {
@@ -702,7 +720,9 @@ async function deployOne(
702
720
 
703
721
  let detail: DeploymentDetail;
704
722
  try {
705
- detail = await pollUntil(ctx, slug, TERMINAL_DEPLOY_STATUSES);
723
+ detail = await pollUntil(ctx, slug, TERMINAL_DEPLOY_STATUSES, () =>
724
+ ctx.client.getDeployment({ teamId: ctx.teamId, slug })
725
+ );
706
726
  } catch (error) {
707
727
  return renderApiError(error, ctx.err);
708
728
  }
@@ -756,6 +776,85 @@ async function deployOne(
756
776
  return 0;
757
777
  }
758
778
 
779
+ /**
780
+ * `architecture: "v2"` deploy: POST `/internal/agentkit/deployments` and
781
+ * poll the matching GET. The v2 accept path starts freeze bake; it does
782
+ * not mint an engine alias or alias token.
783
+ */
784
+ async function deployOneAgentkit(
785
+ ctx: DeployCliContext,
786
+ extras: { jsonCollect?: unknown[] } | undefined,
787
+ args: {
788
+ slug: string;
789
+ gitRepoUrl: string;
790
+ gitRef: string | undefined;
791
+ agentPath: string | undefined;
792
+ repositories: string[] | undefined;
793
+ }
794
+ ): Promise<number> {
795
+ const { slug } = args;
796
+ let accepted: AgentkitDeployAccepted;
797
+ try {
798
+ accepted = await ctx.client.deployAgentkit({
799
+ teamId: ctx.teamId,
800
+ slug,
801
+ gitRepoUrl: args.gitRepoUrl,
802
+ gitRef: args.gitRef,
803
+ agentPath: args.agentPath,
804
+ repositories: args.repositories,
805
+ });
806
+ } catch (error) {
807
+ return renderApiError(error, ctx.err);
808
+ }
809
+
810
+ if (ctx.noWait) {
811
+ if (extras?.jsonCollect !== undefined) {
812
+ extras.jsonCollect.push(accepted);
813
+ } else if (ctx.json) {
814
+ ctx.out(`${JSON.stringify(accepted, null, 2)}\n`);
815
+ } else {
816
+ ctx.out(
817
+ `Deploy accepted: ${slug} (team ${ctx.teamId}, v2)\n` +
818
+ ` application: ${accepted.applicationId}\n` +
819
+ ` release: ${accepted.releaseId}\n`
820
+ );
821
+ }
822
+ return 0;
823
+ }
824
+
825
+ let detail: AgentkitDeployStatus;
826
+ try {
827
+ detail = await pollUntil(ctx, slug, TERMINAL_AGENTKIT_DEPLOY_STATUSES, () =>
828
+ ctx.client.getAgentkitDeploy({ teamId: ctx.teamId, slug })
829
+ );
830
+ } catch (error) {
831
+ return renderApiError(error, ctx.err);
832
+ }
833
+
834
+ if (extras?.jsonCollect !== undefined) {
835
+ extras.jsonCollect.push(detail);
836
+ return detail.status === "running" ? 0 : 1;
837
+ }
838
+
839
+ if (ctx.json) {
840
+ ctx.out(`${JSON.stringify(detail, null, 2)}\n`);
841
+ return detail.status === "running" ? 0 : 1;
842
+ }
843
+
844
+ if (detail.status !== "running") {
845
+ ctx.err(`Deployment ${slug} ended ${detail.status}\n`);
846
+ return 1;
847
+ }
848
+ ctx.out(
849
+ `Deployed ${slug} (team ${ctx.teamId})\n` +
850
+ ` status: running\n` +
851
+ ` architecture: v2\n` +
852
+ ` application: ${detail.applicationId}\n` +
853
+ ` release: ${detail.releaseId}\n`
854
+ );
855
+ return 0;
856
+ }
857
+
759
858
  /**
760
859
  * Post-deploy connector authorization for `cursorAccount` connections: the
761
860
  * agent's connectors execute with tokens owned by the deployment's service
@@ -845,6 +944,7 @@ async function runPostDeployConnectorAuth(
845
944
 
846
945
  interface DeployManifest {
847
946
  hosting: AgentHostingOptions | undefined;
947
+ architecture: AgentArchitecture | undefined;
848
948
  /** `cursorAccount` connections declared by the agent. */
849
949
  cursorAccountConnections: Array<{
850
950
  name: string;
@@ -872,6 +972,7 @@ async function readDeployManifest(dir: string): Promise<DeployManifest> {
872
972
  const project = await loadAgentProject(dir);
873
973
  return {
874
974
  hosting: project.agent.hosting,
975
+ architecture: project.agent.architecture,
875
976
  cursorAccountConnections: project.agent.connections.flatMap((connection) =>
876
977
  isCursorAccountTransport(connection.transport)
877
978
  ? [{ name: connection.name, servers: connection.transport.servers }]
@@ -1102,7 +1203,9 @@ export async function cmdStop(
1102
1203
  }
1103
1204
  let detail: DeploymentDetail;
1104
1205
  try {
1105
- detail = await pollUntil(ctx, slug, new Set(["stopped"]));
1206
+ detail = await pollUntil(ctx, slug, new Set(["stopped"]), () =>
1207
+ ctx.client.getDeployment({ teamId: ctx.teamId, slug })
1208
+ );
1106
1209
  } catch (error) {
1107
1210
  return renderApiError(error, ctx.err);
1108
1211
  }
@@ -1517,17 +1620,24 @@ export function createLineReader(
1517
1620
  * status so create → checkout → start is visible. Throws DeployApiError
1518
1621
  * on request failures or timeout.
1519
1622
  */
1520
- async function pollUntil(
1623
+ async function pollUntil<
1624
+ T extends {
1625
+ status: string;
1626
+ progress?: DeploymentProgress;
1627
+ statusMessage?: string | null;
1628
+ },
1629
+ >(
1521
1630
  ctx: DeployCliContext,
1522
1631
  slug: string,
1523
- terminal: ReadonlySet<DeploymentStatus>
1524
- ): Promise<DeploymentDetail> {
1632
+ terminal: ReadonlySet<T["status"]>,
1633
+ fetchDetail: () => Promise<T>
1634
+ ): Promise<T> {
1525
1635
  const startedAt = Date.now();
1526
1636
  const tty = process.stderr.isTTY === true;
1527
1637
  let lastLabel: string | undefined;
1528
1638
  let sawProgress = false;
1529
1639
 
1530
- const report = (detail: DeploymentDetail): void => {
1640
+ const report = (detail: T): void => {
1531
1641
  if (ctx.json) {
1532
1642
  return;
1533
1643
  }
@@ -1548,12 +1658,10 @@ async function pollUntil(
1548
1658
  };
1549
1659
 
1550
1660
  for (;;) {
1551
- const detail = await ctx.client
1552
- .getDeployment({ teamId: ctx.teamId, slug })
1553
- .catch((error: unknown) => {
1554
- finishProgressLine();
1555
- throw error;
1556
- });
1661
+ const detail = await fetchDetail().catch((error: unknown) => {
1662
+ finishProgressLine();
1663
+ throw error;
1664
+ });
1557
1665
  report(detail);
1558
1666
  if (terminal.has(detail.status)) {
1559
1667
  finishProgressLine();
@@ -162,6 +162,17 @@ async function handleDocsRequest(
162
162
  res.end();
163
163
  return;
164
164
  }
165
+ if (pathname === "/v1/health" || pathname === "/v1/health/") {
166
+ // Same path the corp ALB / ECS check uses on agent-serve. Static docs
167
+ // has no runtimes; liveness is "the process accepted HTTP".
168
+ res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
169
+ if (method === "HEAD") {
170
+ res.end();
171
+ return;
172
+ }
173
+ res.end(JSON.stringify({ ok: true }));
174
+ return;
175
+ }
165
176
  let requestPath = "";
166
177
  if (pathname === "/docs" || pathname === "/docs/") {
167
178
  requestPath = "";
@@ -31,11 +31,21 @@ export function mergeCloudOptions(args: {
31
31
 
32
32
  /**
33
33
  * Session turn runtime: a per-session `cloud` attach forces cloud even when
34
- * the agent defaults to local (chat server tools + Drive handoff).
34
+ * the agent defaults to local (chat server tools + Drive handoff). A
35
+ * grokbot agent refuses the attach — silently moving its session onto a
36
+ * cloud VM would swap the harness (and the box) out from under it.
35
37
  */
36
38
  export function resolveSessionRuntime(args: {
37
39
  agentRuntime: AgentRuntime;
38
40
  cloudOverride: AgentCloudOptions | undefined;
39
41
  }): AgentRuntime {
40
- return args.cloudOverride !== undefined ? "cloud" : args.agentRuntime;
42
+ if (args.cloudOverride === undefined) {
43
+ return args.agentRuntime;
44
+ }
45
+ if (args.agentRuntime === "grokbot") {
46
+ throw new Error(
47
+ 'Grok Bot sessions cannot attach cloud agents: per-session `cloud` options are not supported on runtime: "grokbot". Refusing the session rather than moving it onto a cloud VM.'
48
+ );
49
+ }
50
+ return "cloud";
41
51
  }
@@ -21,7 +21,11 @@ import {
21
21
  ListToolsRequestSchema,
22
22
  } from "@modelcontextprotocol/sdk/types.js";
23
23
  import { bearerAuth } from "../../channels.js";
24
- import type { AgentProject, AuthPolicy } from "../../types.js";
24
+ import type {
25
+ AgentProject,
26
+ AuthPolicy,
27
+ McpToolAnnotations,
28
+ } from "../../types.js";
25
29
  import { isCursorAccountTransport } from "../../types.js";
26
30
  import { probeConnectorAuthUrls } from "../cursor-account-mcp-auth.js";
27
31
  import { LOG_PREFIX } from "../distribution.js";
@@ -151,6 +155,8 @@ interface CatalogTool {
151
155
  inputSchema: Record<string, unknown>;
152
156
  /** Present only when the connector's server declares one. */
153
157
  outputSchema?: Record<string, unknown>;
158
+ /** MCP spec annotations, present only when the server declared them. */
159
+ annotations?: McpToolAnnotations;
154
160
  }
155
161
 
156
162
  interface Catalog {
@@ -335,6 +341,11 @@ export class CursorAccountMcpBridge {
335
341
  ...(tool.outputSchema === undefined
336
342
  ? {}
337
343
  : { outputSchema: tool.outputSchema }),
344
+ // Already bounded to the MCP-spec fields at the backend-client
345
+ // parse; absent stays absent so classifiers fail closed.
346
+ ...(tool.annotations === undefined
347
+ ? {}
348
+ : { annotations: tool.annotations }),
338
349
  })),
339
350
  };
340
351
  });
@@ -510,6 +521,9 @@ function buildCatalog(servers: AccountMcpServer[]): Catalog {
510
521
  ...(tool.outputSchema === undefined
511
522
  ? {}
512
523
  : { outputSchema: tool.outputSchema }),
524
+ ...(tool.annotations === undefined
525
+ ? {}
526
+ : { annotations: tool.annotations }),
513
527
  };
514
528
  tools.push(entry);
515
529
  byExposedName.set(entry.exposedName, entry);
@@ -15,6 +15,8 @@
15
15
 
16
16
  import { randomUUID } from "node:crypto";
17
17
  import { z } from "zod";
18
+ import type { McpToolAnnotations } from "../../types.js";
19
+ import { boundedToolAnnotations } from "../mcp-host.js";
18
20
 
19
21
  const CLIENT_VERSION = "agent-serve-0.1.0";
20
22
  const EXCHANGE_PATH = "/auth/exchange_user_api_key";
@@ -85,6 +87,8 @@ const mcpToolSchema = z.object({
85
87
  inputSchema: z.unknown().optional(),
86
88
  /** Flat JSON-string output schema (`output_schema_json` on the proto). */
87
89
  outputSchemaJson: z.string().optional(),
90
+ /** Flat JSON-string MCP annotations (`annotations_json` on the proto). */
91
+ annotationsJson: z.string().optional(),
88
92
  });
89
93
 
90
94
  const getAvailableMcpServersResponseSchema = z.object({
@@ -154,6 +158,13 @@ export interface AccountMcpTool {
154
158
  * matching the optional MCP `Tool.outputSchema` field.
155
159
  */
156
160
  outputSchema?: Record<string, unknown>;
161
+ /**
162
+ * MCP spec tool annotations as the connector's server reported them,
163
+ * bounded to the spec fields at parse time. Absent stays absent —
164
+ * untrusted hints, and the fail-closed signal for consumers that classify
165
+ * on them (dry-run effect resolution).
166
+ */
167
+ annotations?: McpToolAnnotations;
157
168
  }
158
169
 
159
170
  export interface AccountMcpServer {
@@ -331,11 +342,13 @@ export class CursorBackendClient {
331
342
  status: server.status,
332
343
  tools: (server.tools ?? []).map((tool) => {
333
344
  const outputSchema = parseOutputSchemaJson(tool.outputSchemaJson);
345
+ const annotations = parseAnnotationsJson(tool.annotationsJson);
334
346
  return {
335
347
  name: tool.name,
336
348
  description: tool.description ?? "",
337
349
  inputSchema: normalizeInputSchema(tool.inputSchema),
338
350
  ...(outputSchema === undefined ? {} : { outputSchema }),
351
+ ...(annotations === undefined ? {} : { annotations }),
339
352
  };
340
353
  }),
341
354
  }));
@@ -507,6 +520,10 @@ export class CursorBackendClient {
507
520
  return parseRpcResponse(method, second);
508
521
  }
509
522
 
523
+ /**
524
+ * Serve a cached access token for the account, exchanging the API key
525
+ * when none is cached or it is near expiry.
526
+ */
510
527
  private getAccessToken(): Promise<string> {
511
528
  if (
512
529
  this.token !== undefined &&
@@ -703,6 +720,26 @@ function parseOutputSchemaJson(
703
720
  : undefined;
704
721
  }
705
722
 
723
+ /**
724
+ * Parse the flat JSON-string annotations off the wire and bound them to the
725
+ * MCP-spec fields. Malformed or non-object payloads behave like absent ones,
726
+ * so readers fail closed.
727
+ */
728
+ function parseAnnotationsJson(
729
+ annotationsJson: string | undefined
730
+ ): McpToolAnnotations | undefined {
731
+ if (annotationsJson === undefined) {
732
+ return undefined;
733
+ }
734
+ let parsed: unknown;
735
+ try {
736
+ parsed = JSON.parse(annotationsJson);
737
+ } catch {
738
+ return undefined;
739
+ }
740
+ return boundedToolAnnotations(parsed);
741
+ }
742
+
706
743
  type McpResultJson = z.infer<typeof mcpResultSchema>;
707
744
 
708
745
  function toCallResult(result: McpResultJson): AccountMcpCallResult {
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Thin client for the Agent Serve deployment management API
3
- * (`/internal/agent-serve/deployments` on the Cursor backend). Pure
3
+ * (`/internal/agent-serve/deployments` on the Cursor backend) and the v2
4
+ * AgentSDK accept path (`/internal/agentkit/deployments`). Pure
4
5
  * request/response + error mapping — no console I/O, no polling loops;
5
6
  * rendering lives in `cli-deploy.ts`.
6
7
  *
@@ -212,6 +213,48 @@ export interface StopAccepted {
212
213
 
213
214
  export type DeleteAccepted = StopAccepted;
214
215
 
216
+ /**
217
+ * `POST /internal/agentkit/deployments` 202 body. Distinct from v1
218
+ * {@link DeployAccepted}: the v2 control plane returns catalog ids, not
219
+ * an engine alias or one-shot alias token.
220
+ */
221
+ export interface AgentkitDeployAccepted {
222
+ applicationId: string;
223
+ releaseId: string;
224
+ status: string;
225
+ }
226
+
227
+ /** Customer-visible v2 deploy status (`GET /internal/agentkit/deployments/:slug`). */
228
+ export type AgentkitCustomerStatus =
229
+ | "pending"
230
+ | "deploying"
231
+ | "running"
232
+ | "failed"
233
+ | "stopped";
234
+
235
+ export type AgentkitDeployProgress =
236
+ | "queued"
237
+ | "creating"
238
+ | "building"
239
+ | "starting"
240
+ | "finalizing"
241
+ | "running"
242
+ | "failed"
243
+ | "stopped";
244
+
245
+ export interface AgentkitDeployStatus {
246
+ applicationId: string;
247
+ releaseId: string;
248
+ slug: string;
249
+ teamId: number;
250
+ status: AgentkitCustomerStatus;
251
+ progress: AgentkitDeployProgress;
252
+ statusMessage: string | null;
253
+ }
254
+
255
+ export const TERMINAL_AGENTKIT_DEPLOY_STATUSES: ReadonlySet<AgentkitCustomerStatus> =
256
+ new Set(["running", "failed", "stopped"]);
257
+
215
258
  /** One secret's metadata from the list endpoint — names only, never values. */
216
259
  export interface SecretInfo {
217
260
  name: string;
@@ -260,6 +303,35 @@ const deployAcceptedSchema = z
260
303
  })
261
304
  .passthrough();
262
305
 
306
+ const agentkitDeployAcceptedSchema = z
307
+ .object({
308
+ applicationId: z.string().min(1),
309
+ releaseId: z.string().min(1),
310
+ status: z.string(),
311
+ })
312
+ .passthrough();
313
+
314
+ const agentkitDeployStatusSchema = z
315
+ .object({
316
+ applicationId: z.string().min(1),
317
+ releaseId: z.string().min(1),
318
+ slug: z.string().min(1),
319
+ teamId: z.number(),
320
+ status: z.enum(["pending", "deploying", "running", "failed", "stopped"]),
321
+ progress: z.enum([
322
+ "queued",
323
+ "creating",
324
+ "building",
325
+ "starting",
326
+ "finalizing",
327
+ "running",
328
+ "failed",
329
+ "stopped",
330
+ ]),
331
+ statusMessage: z.string().nullable(),
332
+ })
333
+ .passthrough();
334
+
263
335
  const deploymentSchema = z.object({
264
336
  id: z.number(),
265
337
  slug: z.string(),
@@ -482,6 +554,54 @@ export class AgentServeDeployClient {
482
554
  return deployAcceptedSchema.parse(raw);
483
555
  }
484
556
 
557
+ /**
558
+ * Create or redeploy onto the v2 control plane
559
+ * (`POST /internal/agentkit/deployments`). 202 means accepted; poll
560
+ * {@link getAgentkitDeploy} until a terminal status.
561
+ */
562
+ async deployAgentkit(args: {
563
+ teamId: number;
564
+ slug: string;
565
+ gitRepoUrl: string;
566
+ gitRef?: string;
567
+ agentPath?: string;
568
+ repositories?: string[];
569
+ }): Promise<AgentkitDeployAccepted> {
570
+ const slugError = validateDeploymentSlug(args.slug);
571
+ if (slugError !== undefined) {
572
+ throw new DeployApiError(slugError, 400);
573
+ }
574
+ const raw = await this.request({
575
+ method: "POST",
576
+ path: "/internal/agentkit/deployments",
577
+ body: {
578
+ teamId: args.teamId,
579
+ slug: args.slug,
580
+ gitRepoUrl: args.gitRepoUrl,
581
+ gitRef: args.gitRef,
582
+ agentPath: args.agentPath,
583
+ repositories:
584
+ args.repositories !== undefined && args.repositories.length > 0
585
+ ? args.repositories
586
+ : undefined,
587
+ },
588
+ context: { verb: "deploy", slug: args.slug },
589
+ });
590
+ return agentkitDeployAcceptedSchema.parse(raw);
591
+ }
592
+
593
+ async getAgentkitDeploy(args: {
594
+ teamId: number;
595
+ slug: string;
596
+ }): Promise<AgentkitDeployStatus> {
597
+ const raw = await this.request({
598
+ method: "GET",
599
+ path: `/internal/agentkit/deployments/${encodeURIComponent(args.slug)}?teamId=${args.teamId}`,
600
+ context: { verb: "get deployment", slug: args.slug },
601
+ });
602
+ return agentkitDeployStatusSchema.parse(raw);
603
+ }
604
+
485
605
  async listDeployments(args: { teamId: number }): Promise<Deployment[]> {
486
606
  const raw = await this.request({
487
607
  method: "GET",
@@ -756,7 +876,7 @@ function mapErrorResponse(
756
876
  // (api2 vs api.cursor.com), not a missing deployment / closed gate.
757
877
  if (/Route \S+ not found/i.test(bodyText)) {
758
878
  return new DeployApiError(
759
- `Agent Serve deploy API not found on this host — use the default api.cursor.com (unset CURSOR_API_BASE_URL), or point CURSOR_API_BASE_URL at a backend that mounts /internal/agent-serve/*.`,
879
+ `Agent Serve deploy API not found on this host — use the default api.cursor.com (unset CURSOR_API_BASE_URL), or point CURSOR_API_BASE_URL at a backend that mounts /internal/agent-serve/* and /internal/agentkit/*.`,
760
880
  404
761
881
  );
762
882
  }