@cursor/july 0.1.88 → 0.1.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -94
- package/dist/ab.d.ts +1 -1
- package/dist/ab.js +1 -1
- package/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +1 -1
- package/dist/bin/agent-serve.js +21 -8
- package/dist/channels.d.ts +1 -1
- package/dist/channels.js +1 -1
- package/dist/connections.d.ts +1 -1
- package/dist/connections.js +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.DUOPbN18.js → app.wiNkt6G7.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.FV0R6kOb.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CmWbGcGk.js → VPLocalSearchBox.ByhUGu47.js} +1 -1
- package/dist/docs/assets/chunks/{theme.S57OeOLA.js → theme.Dx7j_-0n.js} +2 -2
- package/dist/docs/assets/{concepts.md.BXAm6G-C.js → concepts.md.2NfkGjqM.js} +1 -1
- package/dist/docs/assets/{reference_project-layout.md.D3MdHM2z.js → reference_project-layout.md.CueaKpjr.js} +5 -20
- package/dist/docs/assets/reference_project-layout.md.CueaKpjr.lean.js +1 -0
- package/dist/docs/assets/{reference_tools.md.DTg_kEsx.js → reference_tools.md.XmeFP_3d.js} +23 -2
- package/dist/docs/assets/{reference_tools.md.DTg_kEsx.lean.js → reference_tools.md.XmeFP_3d.lean.js} +1 -1
- package/dist/docs/assets/{scaffolding-agents.md.CiGsJ1aw.js → scaffolding-agents.md.BpMFXv2J.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +5 -5
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/convert-automation.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +8 -23
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +27 -6
- package/dist/docs/scaffolding-agents.html +5 -5
- package/dist/docs/storage.html +3 -3
- package/dist/docs/templates/agentic-owners.html +3 -3
- package/dist/docs/templates/demo.html +3 -3
- package/dist/docs/templates/pr-autofixer.html +3 -3
- package/dist/docs/templates/security-reviewer.html +3 -3
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/evals.d.ts +1 -1
- package/dist/evals.js +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/internal/advertise-tools.d.ts.map +1 -1
- package/dist/internal/advertise-tools.js +1 -1
- package/dist/internal/cli-ax.js +1 -1
- package/dist/internal/cli-convert-automation.d.ts +1 -1
- package/dist/internal/cli-convert-automation.js +1 -1
- package/dist/internal/cli-cursor.d.ts +12 -4
- package/dist/internal/cli-cursor.d.ts.map +1 -1
- package/dist/internal/cli-cursor.js +11 -4
- package/dist/internal/cli-deploy.d.ts +3 -2
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +84 -9
- package/dist/internal/convert-automation/convert-workflow.d.ts +1 -1
- package/dist/internal/convert-automation/convert-workflow.js +9 -9
- package/dist/internal/convert-automation/types.d.ts +1 -1
- package/dist/internal/convert-automation/types.js +1 -1
- package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
- package/dist/internal/cursor/account-mcp.js +8 -4
- package/dist/internal/cursor/backend-client.d.ts +17 -1
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +42 -7
- package/dist/internal/cursor/credentials.d.ts +35 -9
- package/dist/internal/cursor/credentials.d.ts.map +1 -1
- package/dist/internal/cursor/credentials.js +92 -39
- package/dist/internal/deploy-client.d.ts +42 -1
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +70 -2
- package/dist/internal/discovery.js +1 -1
- package/dist/internal/distribution.d.ts +18 -0
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +19 -0
- package/dist/internal/eval-runner.js +1 -1
- package/dist/internal/grokbot/runner.d.ts +5 -2
- package/dist/internal/grokbot/runner.d.ts.map +1 -1
- package/dist/internal/grokbot/runner.js +10 -6
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +8 -1
- package/dist/internal/mcp-endpoint.js +2 -1
- package/dist/internal/sdk-runner.d.ts +5 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +10 -5
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +80 -36
- package/dist/internal/session-engine.d.ts +9 -2
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +85 -28
- package/dist/internal/tool-policy.d.ts +33 -1
- package/dist/internal/tool-policy.d.ts.map +1 -1
- package/dist/internal/tool-policy.js +53 -0
- package/dist/playground/assets/index-BpVS-paP.css +1 -0
- package/dist/playground/assets/{index-Bqn91tW4.js → index-DxapiDj_.js} +45 -45
- package/dist/playground/index.html +2 -2
- package/dist/reminders.d.ts +1 -1
- package/dist/reminders.js +1 -1
- package/dist/schedules.d.ts +1 -1
- package/dist/schedules.js +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/dist/types.d.ts +24 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/docs/concepts.md +2 -1
- package/docs/reference/project-layout.md +5 -20
- package/docs/reference/tools.md +53 -3
- package/docs/scaffolding-agents.md +2 -0
- package/package.json +2 -1
- package/skills/debug/SKILL.md +1 -2
- package/skills/framework-map/SKILL.md +1 -2
- package/src/ab.ts +1 -1
- package/src/artifacts.ts +1 -1
- package/src/bin/agent-serve.ts +21 -7
- package/src/channels.ts +1 -1
- package/src/connections.ts +1 -1
- package/src/evals.ts +1 -1
- package/src/hooks.ts +1 -1
- package/src/index.ts +2 -2
- package/src/internal/advertise-tools.ts +1 -0
- package/src/internal/cli-ax.ts +1 -1
- package/src/internal/cli-convert-automation.ts +1 -1
- package/src/internal/cli-cursor.ts +18 -5
- package/src/internal/cli-deploy.ts +123 -15
- package/src/internal/convert-automation/convert-workflow.ts +9 -9
- package/src/internal/convert-automation/types.ts +1 -1
- package/src/internal/cursor/account-mcp.ts +15 -1
- package/src/internal/cursor/backend-client.ts +62 -4
- package/src/internal/cursor/credentials.ts +87 -24
- package/src/internal/deploy-client.ts +122 -2
- package/src/internal/discovery.ts +1 -1
- package/src/internal/distribution.ts +31 -0
- package/src/internal/eval-runner.ts +1 -1
- package/src/internal/grokbot/runner.ts +15 -6
- package/src/internal/init-project.ts +11 -1
- package/src/internal/mcp-endpoint.ts +2 -1
- package/src/internal/sdk-runner.ts +10 -4
- package/src/internal/server.ts +95 -33
- package/src/internal/session-engine.ts +86 -6
- package/src/internal/tool-policy.ts +80 -1
- package/src/reminders.ts +1 -1
- package/src/schedules.ts +1 -1
- package/src/skills.ts +1 -1
- package/src/storage.ts +1 -1
- package/src/tools.ts +1 -1
- package/src/types.ts +21 -2
- package/dist/docs/assets/chunks/@localSearchIndexroot.CxCtxfDE.js +0 -1
- package/dist/docs/assets/reference_project-layout.md.D3MdHM2z.lean.js +0 -1
- package/dist/playground/assets/index-C0_5hOsf.css +0 -1
- /package/dist/docs/assets/{concepts.md.BXAm6G-C.lean.js → concepts.md.2NfkGjqM.lean.js} +0 -0
- /package/dist/docs/assets/{scaffolding-agents.md.CiGsJ1aw.lean.js → scaffolding-agents.md.BpMFXv2J.lean.js} +0 -0
|
@@ -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 {
|
|
@@ -285,6 +296,12 @@ async function parseRpcResponse(
|
|
|
285
296
|
export interface CursorBackendClientOptions {
|
|
286
297
|
backendUrl: string;
|
|
287
298
|
apiKey: string;
|
|
299
|
+
/**
|
|
300
|
+
* Re-read the key on each authenticated call. Hosted freeze listens
|
|
301
|
+
* unsigned and the binder drops the key after restore; a constructor-frozen
|
|
302
|
+
* key would miss that bind.
|
|
303
|
+
*/
|
|
304
|
+
getApiKey?: () => string;
|
|
288
305
|
logger?: (line: string) => void;
|
|
289
306
|
/** Test seam; defaults to global fetch. */
|
|
290
307
|
fetchImpl?: typeof fetch;
|
|
@@ -292,21 +309,26 @@ export interface CursorBackendClientOptions {
|
|
|
292
309
|
|
|
293
310
|
export class CursorBackendClient {
|
|
294
311
|
private readonly backendUrl: string;
|
|
295
|
-
private readonly
|
|
312
|
+
private readonly resolveApiKey: () => string;
|
|
296
313
|
private readonly logger: (line: string) => void;
|
|
297
314
|
private readonly fetchImpl: typeof fetch;
|
|
298
315
|
private token: { value: string; expiresAtMs: number } | undefined;
|
|
299
316
|
private tokenExchange: Promise<string> | undefined;
|
|
317
|
+
private lastExchangedApiKey: string | undefined;
|
|
300
318
|
|
|
301
319
|
constructor(options: CursorBackendClientOptions) {
|
|
302
320
|
this.backendUrl = options.backendUrl.endsWith("/")
|
|
303
321
|
? options.backendUrl.slice(0, -1)
|
|
304
322
|
: options.backendUrl;
|
|
305
|
-
this.
|
|
323
|
+
this.resolveApiKey = options.getApiKey ?? (() => options.apiKey);
|
|
306
324
|
this.logger = options.logger ?? (() => {});
|
|
307
325
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
308
326
|
}
|
|
309
327
|
|
|
328
|
+
private currentApiKey(): string {
|
|
329
|
+
return this.resolveApiKey().trim();
|
|
330
|
+
}
|
|
331
|
+
|
|
310
332
|
/** Who this API key acts as. */
|
|
311
333
|
async getMe(): Promise<CursorIdentity> {
|
|
312
334
|
return getMeResponseSchema.parse(await this.rpc("GetMe", {}));
|
|
@@ -331,11 +353,13 @@ export class CursorBackendClient {
|
|
|
331
353
|
status: server.status,
|
|
332
354
|
tools: (server.tools ?? []).map((tool) => {
|
|
333
355
|
const outputSchema = parseOutputSchemaJson(tool.outputSchemaJson);
|
|
356
|
+
const annotations = parseAnnotationsJson(tool.annotationsJson);
|
|
334
357
|
return {
|
|
335
358
|
name: tool.name,
|
|
336
359
|
description: tool.description ?? "",
|
|
337
360
|
inputSchema: normalizeInputSchema(tool.inputSchema),
|
|
338
361
|
...(outputSchema === undefined ? {} : { outputSchema }),
|
|
362
|
+
...(annotations === undefined ? {} : { annotations }),
|
|
339
363
|
};
|
|
340
364
|
}),
|
|
341
365
|
}));
|
|
@@ -430,7 +454,7 @@ export class CursorBackendClient {
|
|
|
430
454
|
method: "POST",
|
|
431
455
|
headers: {
|
|
432
456
|
"content-type": "application/json",
|
|
433
|
-
authorization: `Bearer ${this.
|
|
457
|
+
authorization: `Bearer ${this.currentApiKey()}`,
|
|
434
458
|
},
|
|
435
459
|
body: JSON.stringify({ repos: [...repos], permissions }),
|
|
436
460
|
signal: AbortSignal.timeout(30_000),
|
|
@@ -512,6 +536,18 @@ export class CursorBackendClient {
|
|
|
512
536
|
* when none is cached or it is near expiry.
|
|
513
537
|
*/
|
|
514
538
|
private getAccessToken(): Promise<string> {
|
|
539
|
+
const apiKey = this.currentApiKey();
|
|
540
|
+
if (apiKey === "") {
|
|
541
|
+
return Promise.reject(
|
|
542
|
+
new CursorBackendError(
|
|
543
|
+
"The Cursor backend client has no API key yet. Bind CURSOR_API_KEY or CURSOR_API_KEY_FILE before authenticated calls."
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
if (this.lastExchangedApiKey !== apiKey) {
|
|
548
|
+
this.token = undefined;
|
|
549
|
+
this.tokenExchange = undefined;
|
|
550
|
+
}
|
|
515
551
|
if (
|
|
516
552
|
this.token !== undefined &&
|
|
517
553
|
Date.now() < this.token.expiresAtMs - TOKEN_EXPIRY_LEEWAY_MS
|
|
@@ -528,13 +564,14 @@ export class CursorBackendClient {
|
|
|
528
564
|
}
|
|
529
565
|
|
|
530
566
|
private async exchangeApiKey(): Promise<string> {
|
|
567
|
+
const apiKey = this.currentApiKey();
|
|
531
568
|
let response: Response;
|
|
532
569
|
try {
|
|
533
570
|
response = await this.fetchImpl(`${this.backendUrl}${EXCHANGE_PATH}`, {
|
|
534
571
|
method: "POST",
|
|
535
572
|
headers: {
|
|
536
573
|
"content-type": "application/json",
|
|
537
|
-
authorization: `Bearer ${
|
|
574
|
+
authorization: `Bearer ${apiKey}`,
|
|
538
575
|
},
|
|
539
576
|
body: "{}",
|
|
540
577
|
signal: AbortSignal.timeout(30_000),
|
|
@@ -563,6 +600,7 @@ export class CursorBackendClient {
|
|
|
563
600
|
value: body.data.accessToken,
|
|
564
601
|
expiresAtMs: jwtExpiryMs(body.data.accessToken),
|
|
565
602
|
};
|
|
603
|
+
this.lastExchangedApiKey = apiKey;
|
|
566
604
|
this.logger("[agent-sdk] cursor account: minted a fresh access token");
|
|
567
605
|
return body.data.accessToken;
|
|
568
606
|
}
|
|
@@ -707,6 +745,26 @@ function parseOutputSchemaJson(
|
|
|
707
745
|
: undefined;
|
|
708
746
|
}
|
|
709
747
|
|
|
748
|
+
/**
|
|
749
|
+
* Parse the flat JSON-string annotations off the wire and bound them to the
|
|
750
|
+
* MCP-spec fields. Malformed or non-object payloads behave like absent ones,
|
|
751
|
+
* so readers fail closed.
|
|
752
|
+
*/
|
|
753
|
+
function parseAnnotationsJson(
|
|
754
|
+
annotationsJson: string | undefined
|
|
755
|
+
): McpToolAnnotations | undefined {
|
|
756
|
+
if (annotationsJson === undefined) {
|
|
757
|
+
return undefined;
|
|
758
|
+
}
|
|
759
|
+
let parsed: unknown;
|
|
760
|
+
try {
|
|
761
|
+
parsed = JSON.parse(annotationsJson);
|
|
762
|
+
} catch {
|
|
763
|
+
return undefined;
|
|
764
|
+
}
|
|
765
|
+
return boundedToolAnnotations(parsed);
|
|
766
|
+
}
|
|
767
|
+
|
|
710
768
|
type McpResultJson = z.infer<typeof mcpResultSchema>;
|
|
711
769
|
|
|
712
770
|
function toCallResult(result: McpResultJson): AccountMcpCallResult {
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* dashboard-revocable user API key — only the key is stored.
|
|
10
10
|
* - `CURSOR_API_KEY` (or `serve({ apiKey })`) wins over the stored key, so
|
|
11
11
|
* CI and hosted deployments stay env-driven.
|
|
12
|
+
* - `CURSOR_API_KEY_FILE` (or the hosted default path) is next: freeze bind
|
|
13
|
+
* drops the key at task time without restarting July.
|
|
14
|
+
* - Stored `july login` is last. Same order for hosted and self-hosted:
|
|
15
|
+
* option → env → file → login.
|
|
12
16
|
*
|
|
13
17
|
* There is deliberately no session-token storage, refresh-token rotation, or
|
|
14
18
|
* cross-tool credential sharing here: short-lived access tokens are minted
|
|
@@ -16,6 +20,7 @@
|
|
|
16
20
|
*/
|
|
17
21
|
|
|
18
22
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
19
24
|
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
20
25
|
import { homedir } from "node:os";
|
|
21
26
|
import { dirname, join } from "node:path";
|
|
@@ -43,10 +48,11 @@ export function cursorBackendUrl(): string {
|
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
50
|
* External API base URL for routes served only by the external-api
|
|
46
|
-
* microlith (`/internal/agent-serve/*`, engine aliases).
|
|
47
|
-
* these are mounted on api.cursor.com and 404 on
|
|
48
|
-
* deploy surface needs its own default.
|
|
49
|
-
* overrides everything for local dev, where
|
|
51
|
+
* microlith (`/internal/agent-serve/*`, `/v0/grokbot/*`, engine aliases).
|
|
52
|
+
* In production these are mounted on api.cursor.com and 404 on
|
|
53
|
+
* api2.cursor.sh, so the deploy surface needs its own default.
|
|
54
|
+
* `CURSOR_API_BASE_URL` still overrides everything for local dev, where
|
|
55
|
+
* one server mounts all routes.
|
|
50
56
|
*/
|
|
51
57
|
export function cursorExternalApiUrl(): string {
|
|
52
58
|
return trimTrailingSlash(
|
|
@@ -100,20 +106,13 @@ export function credentialsFilePath(): string {
|
|
|
100
106
|
return join(configDirPath(), "credentials.json");
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
let raw: string;
|
|
107
|
-
try {
|
|
108
|
-
raw = await readFile(credentialsFilePath(), "utf8");
|
|
109
|
-
} catch {
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
109
|
+
function parseStoredCredentialsJson(
|
|
110
|
+
raw: string
|
|
111
|
+
): StoredCredentials | undefined {
|
|
112
112
|
let parsed: unknown;
|
|
113
113
|
try {
|
|
114
114
|
parsed = JSON.parse(raw) as unknown;
|
|
115
115
|
} catch {
|
|
116
|
-
// Corrupt file — treat as signed out; login rewrites it.
|
|
117
116
|
return undefined;
|
|
118
117
|
}
|
|
119
118
|
const credentials = storedCredentialsSchema.safeParse(parsed);
|
|
@@ -126,6 +125,28 @@ export async function readStoredCredentials(): Promise<
|
|
|
126
125
|
};
|
|
127
126
|
}
|
|
128
127
|
|
|
128
|
+
export function readStoredCredentialsSync(): StoredCredentials | undefined {
|
|
129
|
+
let raw: string;
|
|
130
|
+
try {
|
|
131
|
+
raw = readFileSync(credentialsFilePath(), "utf8");
|
|
132
|
+
} catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return parseStoredCredentialsJson(raw);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function readStoredCredentials(): Promise<
|
|
139
|
+
StoredCredentials | undefined
|
|
140
|
+
> {
|
|
141
|
+
let raw: string;
|
|
142
|
+
try {
|
|
143
|
+
raw = await readFile(credentialsFilePath(), "utf8");
|
|
144
|
+
} catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return parseStoredCredentialsJson(raw);
|
|
148
|
+
}
|
|
149
|
+
|
|
129
150
|
export async function writeStoredCredentials(
|
|
130
151
|
credentials: StoredCredentials
|
|
131
152
|
): Promise<void> {
|
|
@@ -148,10 +169,10 @@ export async function clearStoredCredentials(): Promise<boolean> {
|
|
|
148
169
|
}
|
|
149
170
|
|
|
150
171
|
// ============================================================================
|
|
151
|
-
// Resolution (option > env > stored login)
|
|
172
|
+
// Resolution (option > env > file > stored login)
|
|
152
173
|
// ============================================================================
|
|
153
174
|
|
|
154
|
-
export type ApiKeySource = "option" | "env" | "login";
|
|
175
|
+
export type ApiKeySource = "option" | "env" | "file" | "login";
|
|
155
176
|
|
|
156
177
|
export interface ResolvedApiKey {
|
|
157
178
|
apiKey: string;
|
|
@@ -160,15 +181,43 @@ export interface ResolvedApiKey {
|
|
|
160
181
|
}
|
|
161
182
|
|
|
162
183
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
184
|
+
* Hosted task-bind path. Not a secret: the snapshot may name this path.
|
|
185
|
+
* The binder writes the key here after restore; July re-reads on each use.
|
|
186
|
+
* Override with `CURSOR_API_KEY_FILE`. A missing file is unsigned — self-hosted
|
|
187
|
+
* then uses `CURSOR_API_KEY` or stored login.
|
|
167
188
|
*/
|
|
168
|
-
export
|
|
189
|
+
export const DEFAULT_CURSOR_API_KEY_FILE: string =
|
|
190
|
+
"/run/cursor/secrets/CURSOR_API_KEY";
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Read `CURSOR_API_KEY_FILE` (or {@link DEFAULT_CURSOR_API_KEY_FILE} when
|
|
194
|
+
* unset). Missing/empty files are unsigned, not an error — freeze bake
|
|
195
|
+
* listens before the binder drops the key; self-hosted falls through to
|
|
196
|
+
* env or login.
|
|
197
|
+
*/
|
|
198
|
+
export function readApiKeyFromFile(filePath?: string): string | undefined {
|
|
199
|
+
const configured = filePath ?? process.env.CURSOR_API_KEY_FILE;
|
|
200
|
+
const path =
|
|
201
|
+
configured !== undefined && configured.trim() !== ""
|
|
202
|
+
? configured.trim()
|
|
203
|
+
: DEFAULT_CURSOR_API_KEY_FILE;
|
|
204
|
+
try {
|
|
205
|
+
const raw = readFileSync(path, "utf8").trim();
|
|
206
|
+
return raw === "" ? undefined : raw;
|
|
207
|
+
} catch {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* First present source wins. We define the order; operators do not.
|
|
214
|
+
* `--api-key` / `serve({ apiKey })` → `CURSOR_API_KEY` → `CURSOR_API_KEY_FILE`
|
|
215
|
+
* (hosted default path if unset) → stored `july login`.
|
|
216
|
+
*/
|
|
217
|
+
export function resolveApiKeySync(options?: {
|
|
169
218
|
apiKey?: string;
|
|
170
219
|
backendUrl?: string;
|
|
171
|
-
}):
|
|
220
|
+
}): ResolvedApiKey | undefined {
|
|
172
221
|
const backendUrl = options?.backendUrl ?? cursorBackendUrl();
|
|
173
222
|
if (options?.apiKey !== undefined && options.apiKey.trim() !== "") {
|
|
174
223
|
return { apiKey: options.apiKey.trim(), source: "option", backendUrl };
|
|
@@ -177,15 +226,29 @@ export async function resolveApiKey(options?: {
|
|
|
177
226
|
if (envKey !== undefined && envKey.trim() !== "") {
|
|
178
227
|
return { apiKey: envKey.trim(), source: "env", backendUrl };
|
|
179
228
|
}
|
|
180
|
-
const
|
|
229
|
+
const fileKey = readApiKeyFromFile();
|
|
230
|
+
if (fileKey !== undefined) {
|
|
231
|
+
return { apiKey: fileKey, source: "file", backendUrl };
|
|
232
|
+
}
|
|
233
|
+
const stored = readStoredCredentialsSync();
|
|
181
234
|
if (stored !== undefined && stored.backendUrl === backendUrl) {
|
|
182
235
|
return { apiKey: stored.apiKey, source: "login", backendUrl };
|
|
183
236
|
}
|
|
184
237
|
return undefined;
|
|
185
238
|
}
|
|
186
239
|
|
|
240
|
+
/**
|
|
241
|
+
* {@link resolveApiKeySync} as a Promise for call sites that already await.
|
|
242
|
+
*/
|
|
243
|
+
export async function resolveApiKey(options?: {
|
|
244
|
+
apiKey?: string;
|
|
245
|
+
backendUrl?: string;
|
|
246
|
+
}): Promise<ResolvedApiKey | undefined> {
|
|
247
|
+
return resolveApiKeySync(options);
|
|
248
|
+
}
|
|
249
|
+
|
|
187
250
|
/** One-line hint for "no credentials" errors, shared across surfaces. */
|
|
188
|
-
export const SIGN_IN_HINT: string = `Run \`${CLI_COMMAND_NAME} login\` (or set CURSOR_API_KEY / pass serve({ apiKey })).`;
|
|
251
|
+
export const SIGN_IN_HINT: string = `Run \`${CLI_COMMAND_NAME} login\` (or set CURSOR_API_KEY / CURSOR_API_KEY_FILE / pass serve({ apiKey })).`;
|
|
189
252
|
|
|
190
253
|
// ============================================================================
|
|
191
254
|
// PKCE browser login (mirrors cursor-agent / Sand)
|
|
@@ -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)
|
|
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
|
}
|
|
@@ -2021,7 +2021,7 @@ async function loadABConfig(
|
|
|
2021
2021
|
severity: "error",
|
|
2022
2022
|
path: displayPath(ctx, path),
|
|
2023
2023
|
message:
|
|
2024
|
-
"ab.config.ts must default-export defineABConfig({ ... }) from @
|
|
2024
|
+
"ab.config.ts must default-export defineABConfig({ ... }) from @cursor/july/ab.",
|
|
2025
2025
|
});
|
|
2026
2026
|
return undefined;
|
|
2027
2027
|
}
|
|
@@ -81,6 +81,37 @@ export function packageVersion(): string {
|
|
|
81
81
|
return typeof parsed.version === "string" ? parsed.version : "0.0.0";
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/** Facts `agent-sdk version` / `--version` print. */
|
|
85
|
+
export interface CliVersionInfo {
|
|
86
|
+
/** Published npm name (`@cursor/july`). */
|
|
87
|
+
name: string;
|
|
88
|
+
/** `package.json` version of this install. */
|
|
89
|
+
version: string;
|
|
90
|
+
/** Bin the process was invoked as (`agent-sdk` / `agent-serve`). */
|
|
91
|
+
cli: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function cliVersionInfo(): CliVersionInfo {
|
|
95
|
+
return {
|
|
96
|
+
name: PACKAGE_NAME,
|
|
97
|
+
version: packageVersion(),
|
|
98
|
+
cli: CLI_COMMAND_NAME,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Text or JSON for `version` / `--version` / `-V`.
|
|
104
|
+
* Text is the bare `package.json` version (npm-style). `--json` is the
|
|
105
|
+
* complete `{ name, version, cli }` payload.
|
|
106
|
+
*/
|
|
107
|
+
export function formatCliVersion(options?: { json?: boolean }): string {
|
|
108
|
+
const info = cliVersionInfo();
|
|
109
|
+
if (options?.json === true) {
|
|
110
|
+
return `${JSON.stringify(info, null, 2)}\n`;
|
|
111
|
+
}
|
|
112
|
+
return `${info.version}\n`;
|
|
113
|
+
}
|
|
114
|
+
|
|
84
115
|
/**
|
|
85
116
|
* Authoring entry subpaths and their dist-relative output files — the
|
|
86
117
|
* runtime mirror of the package `exports` map (the source of truth;
|
|
@@ -479,7 +479,7 @@ function resolveEvalMaxConcurrency(config: Partial<EvalConfigFile>): number {
|
|
|
479
479
|
if (config.maxConcurrency === undefined) {
|
|
480
480
|
throw new Error(
|
|
481
481
|
`evals/evals.config.ts must define maxConcurrency (1–${EVAL_MAX_CONCURRENCY_LIMIT}; hard limit is due to model provider request limits). Example:\n` +
|
|
482
|
-
` import { defineEvalConfig } from "@
|
|
482
|
+
` import { defineEvalConfig } from "@cursor/july/evals";\n` +
|
|
483
483
|
` export default defineEvalConfig({ maxConcurrency: 20 });`
|
|
484
484
|
);
|
|
485
485
|
}
|
|
@@ -13,15 +13,21 @@
|
|
|
13
13
|
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
15
|
import type { RunnerTurnRequest, TurnOutcome } from "../../types.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
cursorExternalApiUrl,
|
|
18
|
+
resolveApiKeySync,
|
|
19
|
+
} from "../cursor/credentials.js";
|
|
17
20
|
import type { AgentRunner } from "./../sdk-runner.js";
|
|
18
21
|
|
|
19
22
|
export interface GrokBotRunnerOptions {
|
|
20
|
-
/** Cursor API key.
|
|
23
|
+
/** Cursor API key. Resolved as option → env → file → stored login. */
|
|
21
24
|
apiKey?: string;
|
|
22
25
|
/** Test seam. Defaults to global fetch. */
|
|
23
26
|
fetchImpl?: typeof fetch;
|
|
24
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Test seam. Defaults to {@link cursorExternalApiUrl} (`api.cursor.com`).
|
|
29
|
+
* `/v0/grokbot` is an external-api microlith route — it 404s on api2.
|
|
30
|
+
*/
|
|
25
31
|
backendUrl?: string;
|
|
26
32
|
/** Test seam. Poll interval while a turn runs. */
|
|
27
33
|
pollIntervalMs?: number;
|
|
@@ -89,14 +95,17 @@ class GrokBotApiClient {
|
|
|
89
95
|
|
|
90
96
|
constructor(options: GrokBotRunnerOptions) {
|
|
91
97
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
92
|
-
this.baseUrl = (options.backendUrl ??
|
|
98
|
+
this.baseUrl = (options.backendUrl ?? cursorExternalApiUrl()).replace(
|
|
93
99
|
/\/$/,
|
|
94
100
|
""
|
|
95
101
|
);
|
|
96
|
-
const apiKey =
|
|
102
|
+
const apiKey = resolveApiKeySync({
|
|
103
|
+
apiKey: options.apiKey,
|
|
104
|
+
backendUrl: options.backendUrl,
|
|
105
|
+
})?.apiKey;
|
|
97
106
|
if (apiKey === undefined || apiKey === "") {
|
|
98
107
|
throw new Error(
|
|
99
|
-
'runtime: "grokbot" needs a Cursor API key (CURSOR_API_KEY or agent-sdk login) to reach the hosted Grok Bot harness.'
|
|
108
|
+
'runtime: "grokbot" needs a Cursor API key (CURSOR_API_KEY, CURSOR_API_KEY_FILE, or agent-sdk login) to reach the hosted Grok Bot harness.'
|
|
100
109
|
);
|
|
101
110
|
}
|
|
102
111
|
this.apiKey = apiKey;
|
|
@@ -6,6 +6,7 @@ import { spawn } from "node:child_process";
|
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
8
8
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
9
|
+
import { runfiles } from "@bazel/runfiles";
|
|
9
10
|
import type { ModelSetting } from "../types.js";
|
|
10
11
|
import {
|
|
11
12
|
canonicalTemplateName,
|
|
@@ -103,7 +104,7 @@ export function resolveTemplate(name: string): TemplateResolution {
|
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
106
|
const entry = TEMPLATES[resolved];
|
|
106
|
-
const dir =
|
|
107
|
+
const dir = resolvePackagePath("templates", entry.dir);
|
|
107
108
|
return {
|
|
108
109
|
ok: true,
|
|
109
110
|
name: resolved,
|
|
@@ -115,6 +116,15 @@ export function resolveTemplate(name: string): TemplateResolution {
|
|
|
115
116
|
};
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
function resolvePackagePath(...segments: string[]): string {
|
|
120
|
+
if (runfiles.workspace !== undefined) {
|
|
121
|
+
return runfiles.resolveWorkspaceRelative(
|
|
122
|
+
["packages", "agent-serve", ...segments].join("/")
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return join(packageRootDir(), ...segments);
|
|
126
|
+
}
|
|
127
|
+
|
|
118
128
|
export type InitTargetCheck = { ok: true } | { ok: false; message: string };
|
|
119
129
|
|
|
120
130
|
/**
|
|
@@ -318,8 +318,9 @@ function buildConnectionBridgeMcpServer(
|
|
|
318
318
|
}
|
|
319
319
|
// The one tool path that never reaches `executeServerTool`, so it asks
|
|
320
320
|
// the policy itself.
|
|
321
|
-
const answer = engine.bridgedCallAnswer({
|
|
321
|
+
const answer = await engine.bridgedCallAnswer({
|
|
322
322
|
sessionId: sessionId ?? "",
|
|
323
|
+
connectionName,
|
|
323
324
|
toolName: request.params.name,
|
|
324
325
|
});
|
|
325
326
|
if (answer.answered) {
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type TurnUsage,
|
|
30
30
|
} from "../types.js";
|
|
31
31
|
import { mapSdkTokenUsage, usageTokenSum } from "./cloud-turn-cost.js";
|
|
32
|
+
import { resolveApiKeySync } from "./cursor/credentials.js";
|
|
32
33
|
|
|
33
34
|
/** Internal seam between the session engine and the Cursor SDK harness. */
|
|
34
35
|
export interface AgentRunner {
|
|
@@ -427,14 +428,19 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
427
428
|
(this.options.logger ?? (() => {}))(line);
|
|
428
429
|
}
|
|
429
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Re-resolve on every turn. Hosted freeze snapshots listen unsigned; the
|
|
433
|
+
* binder drops `CURSOR_API_KEY` / `CURSOR_API_KEY_FILE` at task time into
|
|
434
|
+
* the already-running process, which must not have closed over `undefined`.
|
|
435
|
+
*/
|
|
430
436
|
private requireApiKey(): string {
|
|
431
|
-
const
|
|
432
|
-
if (
|
|
437
|
+
const resolved = resolveApiKeySync({ apiKey: this.options.apiKey });
|
|
438
|
+
if (resolved === undefined) {
|
|
433
439
|
throw new Error(
|
|
434
|
-
"The Cursor SDK runner requires a Cursor credential: run `agent-serve login`, set CURSOR_API_KEY, or pass serve({ apiKey })."
|
|
440
|
+
"The Cursor SDK runner requires a Cursor credential: run `agent-serve login`, set CURSOR_API_KEY or CURSOR_API_KEY_FILE, or pass serve({ apiKey })."
|
|
435
441
|
);
|
|
436
442
|
}
|
|
437
|
-
return apiKey;
|
|
443
|
+
return resolved.apiKey;
|
|
438
444
|
}
|
|
439
445
|
|
|
440
446
|
private buildSharedOptions(
|