@cursor/july 0.1.2 → 0.1.3
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/AGENTS.md +16 -5
- package/README.md +8 -9
- package/dist/bin/agent-serve.d.ts +3 -1
- package/dist/bin/agent-serve.d.ts.map +1 -1
- package/dist/bin/agent-serve.js +389 -152
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.Oje4vhlk.js → app.BR0RbIdx.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DtIOQzGj.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.H5XZ2zCB.js → VPLocalSearchBox.qjsVTneI.js} +1 -1
- package/dist/docs/assets/chunks/{theme.CTR_TuaE.js → theme.2Kg8jIp_.js} +2 -2
- package/dist/docs/assets/{quickstart.md.CfU8_uTC.js → quickstart.md.BU6Iwi_9.js} +18 -6
- package/dist/docs/assets/{reference_cli.md.DA730zCu.js → reference_cli.md.Bv6pOxcF.js} +11 -6
- package/dist/docs/assets/{reference_cli.md.DA730zCu.lean.js → reference_cli.md.Bv6pOxcF.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.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/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.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 +22 -10
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +14 -9
- 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 +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 +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/internal/chat-client.d.ts +29 -3
- package/dist/internal/chat-client.d.ts.map +1 -1
- package/dist/internal/chat-client.js +180 -27
- package/dist/internal/cli-ax.d.ts +75 -2
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +600 -52
- package/dist/internal/cli-cursor.d.ts.map +1 -1
- package/dist/internal/cli-cursor.js +8 -36
- package/dist/internal/cli-deploy.d.ts +59 -3
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +316 -38
- package/dist/internal/cursor/credentials.d.ts +9 -0
- package/dist/internal/cursor/credentials.d.ts.map +1 -1
- package/dist/internal/cursor/credentials.js +12 -0
- package/dist/internal/deploy-client.d.ts +9 -1
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +23 -3
- package/dist/internal/deploy-source.d.ts +35 -0
- package/dist/internal/deploy-source.d.ts.map +1 -0
- package/dist/internal/deploy-source.js +118 -0
- package/dist/internal/discovery.d.ts +11 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +43 -14
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +2 -1
- package/dist/internal/eval-runner.d.ts +2 -0
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +2 -0
- package/dist/internal/event-mapper.d.ts +54 -1
- package/dist/internal/event-mapper.d.ts.map +1 -1
- package/dist/internal/event-mapper.js +151 -41
- package/dist/internal/init-project.d.ts +88 -1
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +221 -31
- package/dist/internal/install-cursor-skills.d.ts +64 -0
- package/dist/internal/install-cursor-skills.d.ts.map +1 -0
- package/dist/internal/install-cursor-skills.js +274 -0
- package/dist/internal/logs-client.d.ts +60 -0
- package/dist/internal/logs-client.d.ts.map +1 -0
- package/dist/internal/logs-client.js +311 -0
- package/dist/internal/open-browser.d.ts +5 -0
- package/dist/internal/open-browser.d.ts.map +1 -0
- package/dist/internal/open-browser.js +34 -0
- package/dist/internal/playground/toolchain.d.ts.map +1 -1
- package/dist/internal/playground/toolchain.js +16 -11
- package/dist/internal/playground-proxy.d.ts +69 -0
- package/dist/internal/playground-proxy.d.ts.map +1 -0
- package/dist/internal/playground-proxy.js +468 -0
- package/dist/internal/prompt-context.d.ts +35 -0
- package/dist/internal/prompt-context.d.ts.map +1 -0
- package/dist/internal/prompt-context.js +71 -0
- package/dist/internal/request-headers.d.ts +11 -0
- package/dist/internal/request-headers.d.ts.map +1 -0
- package/dist/internal/request-headers.js +14 -0
- package/dist/internal/resolve-prod-target.d.ts +42 -0
- package/dist/internal/resolve-prod-target.d.ts.map +1 -0
- package/dist/internal/resolve-prod-target.js +111 -0
- package/dist/internal/run-client.d.ts +2 -2
- package/dist/internal/run-client.d.ts.map +1 -1
- package/dist/internal/run-client.js +38 -23
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +10 -4
- package/dist/internal/session-engine.d.ts +1 -1
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +21 -6
- package/dist/internal/sessions-client.d.ts +21 -0
- package/dist/internal/sessions-client.d.ts.map +1 -0
- package/dist/internal/sessions-client.js +168 -0
- package/dist/internal/stream-progress.d.ts.map +1 -1
- package/dist/internal/stream-progress.js +31 -3
- package/dist/internal/terminal-style.d.ts +22 -0
- package/dist/internal/terminal-style.d.ts.map +1 -0
- package/dist/internal/terminal-style.js +49 -0
- package/dist/internal/trajectory.d.ts +10 -5
- package/dist/internal/trajectory.d.ts.map +1 -1
- package/dist/internal/trajectory.js +82 -10
- package/dist/internal/workspace.d.ts +11 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +38 -4
- package/dist/playground/assets/index-D-vo2lV_.css +1 -0
- package/dist/playground/assets/index-x60b9q2j.js +312 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +26 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/quickstart.md +22 -7
- package/docs/reference/cli.md +51 -3
- package/package.json +3 -1
- package/skills/ab/SKILL.md +8 -8
- package/skills/create-agent/SKILL.md +28 -22
- package/skills/debug/SKILL.md +11 -11
- package/skills/evals/SKILL.md +16 -16
- package/skills/framework-map/SKILL.md +6 -6
- package/skills/github/SKILL.md +13 -13
- package/skills/hillclimb/SKILL.md +10 -10
- package/skills/setup-slack/SKILL.md +13 -13
- package/src/bin/agent-serve.ts +422 -188
- package/src/internal/chat-client.ts +252 -37
- package/src/internal/cli-ax.ts +722 -72
- package/src/internal/cli-cursor.ts +14 -42
- package/src/internal/cli-deploy.ts +428 -49
- package/src/internal/cursor/credentials.ts +14 -0
- package/src/internal/deploy-client.ts +45 -4
- package/src/internal/deploy-source.ts +133 -0
- package/src/internal/discovery.ts +53 -16
- package/src/internal/eval-run-store.ts +2 -1
- package/src/internal/eval-runner.ts +5 -0
- package/src/internal/event-mapper.ts +222 -42
- package/src/internal/init-project.ts +333 -51
- package/src/internal/install-cursor-skills.ts +327 -0
- package/src/internal/logs-client.ts +442 -0
- package/src/internal/open-browser.ts +41 -0
- package/src/internal/playground/toolchain.ts +15 -13
- package/src/internal/playground-proxy.ts +576 -0
- package/src/internal/prompt-context.ts +95 -0
- package/src/internal/request-headers.ts +23 -0
- package/src/internal/resolve-prod-target.ts +150 -0
- package/src/internal/run-client.ts +39 -36
- package/src/internal/server.ts +12 -3
- package/src/internal/session-engine.ts +22 -3
- package/src/internal/sessions-client.ts +182 -0
- package/src/internal/stream-progress.ts +36 -2
- package/src/internal/terminal-style.ts +74 -0
- package/src/internal/trajectory.ts +91 -13
- package/src/internal/workspace.ts +42 -4
- package/src/types.ts +42 -6
- package/dist/docs/assets/chunks/@localSearchIndexroot.zwQ9RCQ7.js +0 -1
- package/dist/playground/assets/index-B1Qc9h2u.css +0 -1
- package/dist/playground/assets/index-CpDYCj8W.js +0 -79
- /package/dist/docs/assets/{quickstart.md.CfU8_uTC.lean.js → quickstart.md.BU6Iwi_9.lean.js} +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a hosted (managed) agent-serve deployment into a base URL +
|
|
3
|
+
* request headers for chat/run/call/eval `--prod`.
|
|
4
|
+
*
|
|
5
|
+
* Uses short-lived `engineAccess` from the deployment status route
|
|
6
|
+
* (Cursor API key on the management API; `X-Anyrun-Port-Token` on the
|
|
7
|
+
* engine). Alias tokens are out of scope here.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { basename, resolve } from "node:path";
|
|
11
|
+
import { resolveDeployAuth } from "./cli-deploy.js";
|
|
12
|
+
import {
|
|
13
|
+
DeployApiError,
|
|
14
|
+
type DeploymentDetail,
|
|
15
|
+
slugFromDirectoryName,
|
|
16
|
+
validateDeploymentSlug,
|
|
17
|
+
} from "./deploy-client.js";
|
|
18
|
+
import { CLI_COMMAND_NAME as CLI } from "./distribution.js";
|
|
19
|
+
import { stderrPalette } from "./terminal-style.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Interactive CLI keep-alive for hosted engines. Without these, a hibernated
|
|
23
|
+
* Anyrun pod returns 417 `pod_hibernated` and never wakes. Matches the
|
|
24
|
+
* cloud-agent interactive keep-alive shape (lower ≤ upper, seconds).
|
|
25
|
+
*/
|
|
26
|
+
export const PROD_ENGINE_WAKE_HEADERS: Readonly<Record<string, string>> = {
|
|
27
|
+
"x-anyrun-hibernation-lower-bound-s": "1800",
|
|
28
|
+
"x-anyrun-hibernation-upper-bound-s": "3600",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface ResolveProdTargetOptions {
|
|
32
|
+
dir: string;
|
|
33
|
+
cwd?: string;
|
|
34
|
+
/** Explicit deployment slug; otherwise derived from `--dir` basename. */
|
|
35
|
+
slug?: string;
|
|
36
|
+
team?: string;
|
|
37
|
+
apiKey?: string;
|
|
38
|
+
fetchImpl?: typeof fetch;
|
|
39
|
+
err?: (text: string) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ResolvedProdTarget {
|
|
43
|
+
agentUrl: string;
|
|
44
|
+
headers: Record<string, string>;
|
|
45
|
+
slug: string;
|
|
46
|
+
expiresAt: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Look up a running deployment and return its engineAccess URL/headers.
|
|
51
|
+
* Throws {@link Error} with a CLI-ready message on failure.
|
|
52
|
+
*/
|
|
53
|
+
export async function resolveProdTarget(
|
|
54
|
+
options: ResolveProdTargetOptions
|
|
55
|
+
): Promise<ResolvedProdTarget> {
|
|
56
|
+
const err =
|
|
57
|
+
options.err ?? ((text: string) => void process.stderr.write(text));
|
|
58
|
+
const slug = resolveProdSlug(options);
|
|
59
|
+
const auth = await resolveDeployAuth({
|
|
60
|
+
team: options.team,
|
|
61
|
+
apiKey: options.apiKey,
|
|
62
|
+
fetchImpl: options.fetchImpl,
|
|
63
|
+
err,
|
|
64
|
+
});
|
|
65
|
+
if (typeof auth === "number") {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Could not resolve Cursor credentials/team for --prod (see above).`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let detail: DeploymentDetail;
|
|
72
|
+
try {
|
|
73
|
+
detail = await auth.client.getDeployment({
|
|
74
|
+
teamId: auth.teamId,
|
|
75
|
+
slug,
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof DeployApiError) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Deployment "${slug}" (team ${auth.teamId}): ${error.message}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// `degraded` still serves the previous ACTIVE engine; allow runtime access
|
|
87
|
+
// when the control plane minted engineAccess for it.
|
|
88
|
+
const access = detail.engineAccess;
|
|
89
|
+
const statusOk = detail.status === "running" || detail.status === "degraded";
|
|
90
|
+
if (!statusOk) {
|
|
91
|
+
const lastError =
|
|
92
|
+
detail.lastError === null || detail.lastError === undefined
|
|
93
|
+
? ""
|
|
94
|
+
: ` ${sanitizeCustomerFacingDetail(detail.lastError)}`;
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Deployment "${slug}" is ${detail.status}, not running.${lastError}\n` +
|
|
97
|
+
`Deploy or wait: \`${CLI} deploy --slug ${slug}\` / \`${CLI} deployment ${slug}\``
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (access === null || access === undefined) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Deployment "${slug}" is ${detail.status} but is not reachable yet. ` +
|
|
103
|
+
`Try \`${CLI} deployment ${slug}\` or redeploy.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
err(`${stderrPalette().dim(`Using hosted deployment "${slug}".`)}\n`);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
agentUrl: access.url.replace(/\/$/, ""),
|
|
111
|
+
headers: {
|
|
112
|
+
...access.headers,
|
|
113
|
+
...PROD_ENGINE_WAKE_HEADERS,
|
|
114
|
+
},
|
|
115
|
+
slug,
|
|
116
|
+
expiresAt: access.expiresAt,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Strip obvious hosting internals from customer-facing error fragments. */
|
|
121
|
+
function sanitizeCustomerFacingDetail(message: string): string {
|
|
122
|
+
return message
|
|
123
|
+
.replace(/https?:\/\/\S+/gi, "[url]")
|
|
124
|
+
.replace(/\bpod-[a-z0-9]+\b/gi, "[runtime]")
|
|
125
|
+
.replace(/\banyrun\b/gi, "runtime")
|
|
126
|
+
.replace(/\bcursorvm\.com\b/gi, "host");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Resolve the deployment slug the same way `deploy` does for a single dir. */
|
|
130
|
+
export function resolveProdSlug(options: {
|
|
131
|
+
dir: string;
|
|
132
|
+
cwd?: string;
|
|
133
|
+
slug?: string;
|
|
134
|
+
}): string {
|
|
135
|
+
if (options.slug !== undefined && options.slug !== "") {
|
|
136
|
+
const slugError = validateDeploymentSlug(options.slug);
|
|
137
|
+
if (slugError !== undefined) {
|
|
138
|
+
throw new Error(slugError);
|
|
139
|
+
}
|
|
140
|
+
return options.slug;
|
|
141
|
+
}
|
|
142
|
+
const dir = resolve(options.cwd ?? process.cwd(), options.dir);
|
|
143
|
+
const derived = slugFromDirectoryName(basename(dir));
|
|
144
|
+
if (derived === undefined) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Could not derive a deployment slug from the directory name ${JSON.stringify(basename(dir))}; pass --slug.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return derived;
|
|
150
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { SessionEvent, SessionPurpose } from "../types.js";
|
|
10
|
+
import { mergeRequestHeaders, type RequestAuth } from "./request-headers.js";
|
|
10
11
|
import { buildTrajectory, type Trajectory } from "./trajectory.js";
|
|
11
12
|
|
|
12
13
|
/** Thrown when a turn stream exceeds `timeoutMs`; carries the session id. */
|
|
@@ -19,9 +20,8 @@ export class RunSessionTimeoutError extends Error {
|
|
|
19
20
|
this.name = "RunSessionTimeoutError";
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
export interface RunClientOptions {
|
|
23
|
+
export interface RunClientOptions extends RequestAuth {
|
|
23
24
|
baseUrl: string;
|
|
24
|
-
bearerToken?: string;
|
|
25
25
|
/** Abort a hung turn. */
|
|
26
26
|
timeoutMs?: number;
|
|
27
27
|
/** Called for each stream event as it arrives (for CLI progress). */
|
|
@@ -72,6 +72,11 @@ export async function runSession(
|
|
|
72
72
|
const allEvents: SessionEvent[] = [];
|
|
73
73
|
let nextEventIndex = 0;
|
|
74
74
|
|
|
75
|
+
const auth: RequestAuth = {
|
|
76
|
+
bearerToken: options.bearerToken,
|
|
77
|
+
headers: options.headers,
|
|
78
|
+
};
|
|
79
|
+
|
|
75
80
|
for (const message of options.messages) {
|
|
76
81
|
const turn = await runOneTurn({
|
|
77
82
|
baseUrl,
|
|
@@ -79,18 +84,13 @@ export async function runSession(
|
|
|
79
84
|
sessionId,
|
|
80
85
|
continuationToken,
|
|
81
86
|
startIndex: sessionId === undefined ? 0 : nextEventIndex,
|
|
82
|
-
|
|
87
|
+
auth,
|
|
83
88
|
timeoutMs: options.timeoutMs,
|
|
84
89
|
onEvent: options.onEvent,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
? { workspaceDir: options.workspaceDir }
|
|
90
|
-
: {}),
|
|
91
|
-
...(sessionId === undefined && options.workspaceFiles !== undefined
|
|
92
|
-
? { workspaceFiles: options.workspaceFiles }
|
|
93
|
-
: {}),
|
|
90
|
+
purpose: sessionId === undefined ? options.purpose : undefined,
|
|
91
|
+
workspaceDir: sessionId === undefined ? options.workspaceDir : undefined,
|
|
92
|
+
workspaceFiles:
|
|
93
|
+
sessionId === undefined ? options.workspaceFiles : undefined,
|
|
94
94
|
});
|
|
95
95
|
sessionId = turn.sessionId;
|
|
96
96
|
continuationToken = turn.continuationToken;
|
|
@@ -128,7 +128,7 @@ interface OneTurnArgs {
|
|
|
128
128
|
workspaceDir?: string;
|
|
129
129
|
workspaceFiles?: Record<string, string>;
|
|
130
130
|
startIndex: number;
|
|
131
|
-
|
|
131
|
+
auth?: RequestAuth;
|
|
132
132
|
timeoutMs?: number;
|
|
133
133
|
onEvent?: (event: SessionEvent) => void;
|
|
134
134
|
}
|
|
@@ -145,20 +145,21 @@ async function runOneTurn(args: OneTurnArgs): Promise<OneTurnResult> {
|
|
|
145
145
|
let continuationToken = args.continuationToken;
|
|
146
146
|
|
|
147
147
|
if (sessionId === undefined) {
|
|
148
|
+
const body: Record<string, unknown> = { message: args.message };
|
|
149
|
+
if (args.purpose !== undefined) {
|
|
150
|
+
body.purpose = args.purpose;
|
|
151
|
+
}
|
|
152
|
+
if (args.workspaceDir !== undefined) {
|
|
153
|
+
body.workspaceDir = args.workspaceDir;
|
|
154
|
+
}
|
|
155
|
+
if (args.workspaceFiles !== undefined) {
|
|
156
|
+
body.workspaceFiles = args.workspaceFiles;
|
|
157
|
+
}
|
|
148
158
|
const created = await postJson(
|
|
149
159
|
args.baseUrl,
|
|
150
160
|
"/v1/session",
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
...(args.purpose === undefined ? {} : { purpose: args.purpose }),
|
|
154
|
-
...(args.workspaceDir === undefined
|
|
155
|
-
? {}
|
|
156
|
-
: { workspaceDir: args.workspaceDir }),
|
|
157
|
-
...(args.workspaceFiles === undefined
|
|
158
|
-
? {}
|
|
159
|
-
: { workspaceFiles: args.workspaceFiles }),
|
|
160
|
-
},
|
|
161
|
-
args.bearerToken
|
|
161
|
+
body,
|
|
162
|
+
args.auth
|
|
162
163
|
);
|
|
163
164
|
if (created.status >= 400 || created.body.ok === false) {
|
|
164
165
|
throw new Error(
|
|
@@ -172,7 +173,7 @@ async function runOneTurn(args: OneTurnArgs): Promise<OneTurnResult> {
|
|
|
172
173
|
args.baseUrl,
|
|
173
174
|
`/v1/session/${sessionId}`,
|
|
174
175
|
{ message: args.message, continuationToken },
|
|
175
|
-
args.
|
|
176
|
+
args.auth
|
|
176
177
|
);
|
|
177
178
|
if (resp.status >= 400 || resp.body.ok === false) {
|
|
178
179
|
throw new Error(
|
|
@@ -190,7 +191,7 @@ async function runOneTurn(args: OneTurnArgs): Promise<OneTurnResult> {
|
|
|
190
191
|
baseUrl: args.baseUrl,
|
|
191
192
|
sessionId,
|
|
192
193
|
startIndex: args.startIndex,
|
|
193
|
-
|
|
194
|
+
auth: args.auth,
|
|
194
195
|
timeoutMs: args.timeoutMs,
|
|
195
196
|
onEvent: args.onEvent,
|
|
196
197
|
});
|
|
@@ -209,7 +210,7 @@ async function collectStream(args: {
|
|
|
209
210
|
baseUrl: string;
|
|
210
211
|
sessionId: string;
|
|
211
212
|
startIndex: number;
|
|
212
|
-
|
|
213
|
+
auth?: RequestAuth;
|
|
213
214
|
timeoutMs?: number;
|
|
214
215
|
onEvent?: (event: SessionEvent) => void;
|
|
215
216
|
}): Promise<SessionEvent[]> {
|
|
@@ -223,7 +224,7 @@ async function collectStream(args: {
|
|
|
223
224
|
const resp = await fetch(
|
|
224
225
|
`${args.baseUrl}/v1/session/${args.sessionId}/stream?startIndex=${args.startIndex}`,
|
|
225
226
|
{
|
|
226
|
-
headers:
|
|
227
|
+
headers: mergeRequestHeaders(args.auth),
|
|
227
228
|
signal: controller.signal,
|
|
228
229
|
}
|
|
229
230
|
);
|
|
@@ -283,23 +284,17 @@ async function collectStream(args: {
|
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
|
|
286
|
-
function authHeaders(bearerToken: string | undefined): Record<string, string> {
|
|
287
|
-
return bearerToken === undefined
|
|
288
|
-
? {}
|
|
289
|
-
: { authorization: `Bearer ${bearerToken}` };
|
|
290
|
-
}
|
|
291
|
-
|
|
292
287
|
async function postJson(
|
|
293
288
|
baseUrl: string,
|
|
294
289
|
path: string,
|
|
295
290
|
body: unknown,
|
|
296
|
-
|
|
291
|
+
auth?: RequestAuth
|
|
297
292
|
): Promise<{ status: number; body: Record<string, unknown> }> {
|
|
298
293
|
const resp = await fetch(`${baseUrl}${path}`, {
|
|
299
294
|
method: "POST",
|
|
300
295
|
headers: {
|
|
301
296
|
"content-type": "application/json",
|
|
302
|
-
...
|
|
297
|
+
...mergeRequestHeaders(auth),
|
|
303
298
|
},
|
|
304
299
|
body: JSON.stringify(body),
|
|
305
300
|
});
|
|
@@ -312,6 +307,14 @@ async function postJson(
|
|
|
312
307
|
parsed = { message: text };
|
|
313
308
|
}
|
|
314
309
|
}
|
|
310
|
+
const anyrunReason = resp.headers.get("x-anyrun-failure-reason");
|
|
311
|
+
if (
|
|
312
|
+
anyrunReason !== null &&
|
|
313
|
+
anyrunReason !== "" &&
|
|
314
|
+
(parsed.message === undefined || parsed.message === "")
|
|
315
|
+
) {
|
|
316
|
+
parsed.message = anyrunReason;
|
|
317
|
+
}
|
|
315
318
|
return { status: resp.status, body: parsed };
|
|
316
319
|
}
|
|
317
320
|
|
package/src/internal/server.ts
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
import { CursorBackendClient } from "./cursor/backend-client.js";
|
|
51
51
|
import {
|
|
52
52
|
cursorBackendUrl,
|
|
53
|
+
cursorExternalApiUrl,
|
|
53
54
|
type ResolvedApiKey,
|
|
54
55
|
resolveApiKey,
|
|
55
56
|
SIGN_IN_HINT,
|
|
@@ -227,8 +228,13 @@ export async function startServer(
|
|
|
227
228
|
);
|
|
228
229
|
}
|
|
229
230
|
if (options.allowAnonymous === true) {
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
if (options.allowAnonymousCursorGithub !== true) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
"githubChannel({ cursorAccount: true }) cannot be served with --allow-anonymous because admitted callers could use the signed-in account's GitHub access. If every admitted caller is trusted (e.g. behind an SSO proxy), opt in with --allow-anonymous-cursor-github."
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
logger(
|
|
237
|
+
"[agent-serve] warning: --allow-anonymous-cursor-github — every admitted HTTP caller can drive sessions holding the signed-in account's repo-scoped GitHub credential; serve this only behind a trusted boundary (e.g. an SSO proxy)"
|
|
232
238
|
);
|
|
233
239
|
}
|
|
234
240
|
if (cursorGithubConfig.scope.permissions === "contents-write") {
|
|
@@ -1330,7 +1336,10 @@ function requireCursorEventsAuth(
|
|
|
1330
1336
|
}
|
|
1331
1337
|
return {
|
|
1332
1338
|
apiKey,
|
|
1333
|
-
|
|
1339
|
+
// `/v0/scm-events/*` lives on the external API (api.cursor.com), not
|
|
1340
|
+
// api2. Prefer that default over the account key's backendUrl, which
|
|
1341
|
+
// follows cursorBackendUrl() (api2) when CURSOR_API_KEY is set.
|
|
1342
|
+
baseUrl: config.baseUrl ?? cursorExternalApiUrl(),
|
|
1334
1343
|
};
|
|
1335
1344
|
}
|
|
1336
1345
|
|
|
@@ -54,6 +54,7 @@ import { type ABSnapshot, buildABSnapshot } from "./ab-snapshot.js";
|
|
|
54
54
|
import { ApprovalGate } from "./approval-gate.js";
|
|
55
55
|
import { samePrincipal } from "./auth.js";
|
|
56
56
|
import { mergeCloudOptions, resolveSessionRuntime } from "./cloud-merge.js";
|
|
57
|
+
import { isDevMode } from "./dev-mode.js";
|
|
57
58
|
import { EventLogStore } from "./event-log.js";
|
|
58
59
|
import { TurnEventMapper } from "./event-mapper.js";
|
|
59
60
|
import { createHostPlatforms, type HostPlatforms } from "./host-platforms.js";
|
|
@@ -65,6 +66,11 @@ import {
|
|
|
65
66
|
} from "./ids.js";
|
|
66
67
|
import { validateToolInput } from "./json-schema.js";
|
|
67
68
|
import { McpHost } from "./mcp-host.js";
|
|
69
|
+
import {
|
|
70
|
+
buildIdentitySection,
|
|
71
|
+
buildSessionContextBlock,
|
|
72
|
+
SCHEDULE_CHANNEL_PREFIX,
|
|
73
|
+
} from "./prompt-context.js";
|
|
68
74
|
import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections.js";
|
|
69
75
|
import type { AgentRunner } from "./sdk-runner.js";
|
|
70
76
|
import { SessionStore } from "./session-store.js";
|
|
@@ -77,7 +83,7 @@ import {
|
|
|
77
83
|
} from "./workspace.js";
|
|
78
84
|
|
|
79
85
|
export const HTTP_CHANNEL_ID = "http";
|
|
80
|
-
export
|
|
86
|
+
export { SCHEDULE_CHANNEL_PREFIX } from "./prompt-context.js";
|
|
81
87
|
|
|
82
88
|
export class SessionBusyError extends Error {
|
|
83
89
|
constructor(sessionId: string) {
|
|
@@ -744,7 +750,10 @@ export class SessionEngine {
|
|
|
744
750
|
// events while sibling parallel tools are still executing.
|
|
745
751
|
if (
|
|
746
752
|
update.type === "tool-call-started" ||
|
|
747
|
-
update.type === "tool-call-completed"
|
|
753
|
+
update.type === "tool-call-completed" ||
|
|
754
|
+
// Nested subagent progress (newer SDKs); string check keeps
|
|
755
|
+
// typecheck green against older published `@cursor/sdk` pins.
|
|
756
|
+
(update as { type: string }).type === "tool-call-delta"
|
|
748
757
|
) {
|
|
749
758
|
await yieldEventLoop();
|
|
750
759
|
}
|
|
@@ -847,14 +856,24 @@ export class SessionEngine {
|
|
|
847
856
|
})
|
|
848
857
|
: undefined;
|
|
849
858
|
let turnPrompt = prompt;
|
|
859
|
+
if (isFirstTurn) {
|
|
860
|
+
const contextBlock = buildSessionContextBlock({
|
|
861
|
+
sessionId: record.sessionId,
|
|
862
|
+
channelId: record.channelId,
|
|
863
|
+
channelKind: this.channelsById.get(record.channelId)?.meta?.kind,
|
|
864
|
+
environment: isDevMode() ? "local dev server" : "hosted deployment",
|
|
865
|
+
});
|
|
866
|
+
turnPrompt = `${contextBlock}\n\n${prompt}`;
|
|
867
|
+
}
|
|
850
868
|
if (runtime === "cloud" && isFirstTurn) {
|
|
851
869
|
const preamble = [
|
|
852
870
|
agent.instructions?.trim() ?? "",
|
|
871
|
+
buildIdentitySection(agent),
|
|
853
872
|
buildAgentToolsCatalog(agent.tools, { includeScripts: true }),
|
|
854
873
|
]
|
|
855
874
|
.filter((part) => part !== "")
|
|
856
875
|
.join("\n\n");
|
|
857
|
-
turnPrompt =
|
|
876
|
+
turnPrompt = `${preamble}\n\n${turnPrompt}`;
|
|
858
877
|
}
|
|
859
878
|
return {
|
|
860
879
|
sessionId: record.sessionId,
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session list / event fetch for CLI (`sessions`, `session`, chat reattach).
|
|
3
|
+
*
|
|
4
|
+
* Customer-facing errors never include hosting URLs or infra jargon.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SessionEvent, SessionSummary } from "../types.js";
|
|
8
|
+
import { sanitizeCustomerError } from "./logs-client.js";
|
|
9
|
+
import { mergeRequestHeaders, type RequestAuth } from "./request-headers.js";
|
|
10
|
+
|
|
11
|
+
export async function listSessions(args: {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
auth?: RequestAuth;
|
|
14
|
+
fetchImpl?: typeof fetch;
|
|
15
|
+
}): Promise<SessionSummary[]> {
|
|
16
|
+
const baseUrl = args.baseUrl.replace(/\/$/, "");
|
|
17
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
18
|
+
let resp: Response;
|
|
19
|
+
try {
|
|
20
|
+
resp = await fetchImpl(`${baseUrl}/v1/sessions`, {
|
|
21
|
+
headers: mergeRequestHeaders(args.auth),
|
|
22
|
+
});
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
sanitizeCustomerError(
|
|
26
|
+
error instanceof Error ? error.message : "Could not list sessions."
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (!resp.ok) {
|
|
31
|
+
throw new Error(`Could not list sessions (HTTP ${resp.status}).`);
|
|
32
|
+
}
|
|
33
|
+
const body = (await resp.json()) as { sessions?: unknown };
|
|
34
|
+
if (!Array.isArray(body.sessions)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const out: SessionSummary[] = [];
|
|
38
|
+
for (const row of body.sessions) {
|
|
39
|
+
const parsed = parseSessionSummary(row);
|
|
40
|
+
if (parsed !== undefined) {
|
|
41
|
+
out.push(parsed);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function fetchSessionEvents(args: {
|
|
48
|
+
baseUrl: string;
|
|
49
|
+
sessionId: string;
|
|
50
|
+
auth?: RequestAuth;
|
|
51
|
+
fetchImpl?: typeof fetch;
|
|
52
|
+
}): Promise<SessionEvent[]> {
|
|
53
|
+
const baseUrl = args.baseUrl.replace(/\/$/, "");
|
|
54
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
55
|
+
const path = `/v1/session/${encodeURIComponent(args.sessionId)}/events`;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const jsonResp = await fetchImpl(`${baseUrl}${path}?format=json`, {
|
|
59
|
+
headers: mergeRequestHeaders(args.auth),
|
|
60
|
+
});
|
|
61
|
+
if (jsonResp.ok) {
|
|
62
|
+
const body = (await jsonResp.json()) as { events?: unknown };
|
|
63
|
+
if (Array.isArray(body.events)) {
|
|
64
|
+
return body.events as SessionEvent[];
|
|
65
|
+
}
|
|
66
|
+
} else if (jsonResp.status === 404) {
|
|
67
|
+
throw new Error(`Session ${args.sessionId} not found.`);
|
|
68
|
+
} else if (jsonResp.status === 403) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Session ${args.sessionId} is not readable by this caller.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof Error && error.message.startsWith("Session ")) {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
// Fall through to NDJSON.
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const resp = await fetchImpl(`${baseUrl}${path}`, {
|
|
82
|
+
headers: mergeRequestHeaders(args.auth),
|
|
83
|
+
});
|
|
84
|
+
if (resp.status === 404) {
|
|
85
|
+
throw new Error(`Session ${args.sessionId} not found.`);
|
|
86
|
+
}
|
|
87
|
+
if (resp.status === 403) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Session ${args.sessionId} is not readable by this caller.`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (!resp.ok) {
|
|
93
|
+
throw new Error(`Could not fetch session events (HTTP ${resp.status}).`);
|
|
94
|
+
}
|
|
95
|
+
const text = await resp.text();
|
|
96
|
+
const events: SessionEvent[] = [];
|
|
97
|
+
for (const line of text.split("\n")) {
|
|
98
|
+
if (line.trim() === "") {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
events.push(JSON.parse(line) as SessionEvent);
|
|
103
|
+
} catch {
|
|
104
|
+
// skip malformed lines
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return events;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error instanceof Error && error.message.startsWith("Session ")) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
if (error instanceof Error && error.message.startsWith("Could not fetch")) {
|
|
113
|
+
throw new Error(sanitizeCustomerError(error.message));
|
|
114
|
+
}
|
|
115
|
+
throw new Error(
|
|
116
|
+
sanitizeCustomerError(
|
|
117
|
+
error instanceof Error
|
|
118
|
+
? error.message
|
|
119
|
+
: "Could not fetch session events."
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Lenient parse so partial stubs / older servers still list by sessionId. */
|
|
126
|
+
function parseSessionSummary(value: unknown): SessionSummary | undefined {
|
|
127
|
+
if (typeof value !== "object" || value === null) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
const row = value as Record<string, unknown>;
|
|
131
|
+
if (typeof row.sessionId !== "string" || row.sessionId === "") {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
const continuationToken =
|
|
135
|
+
typeof row.continuationToken === "string"
|
|
136
|
+
? row.continuationToken
|
|
137
|
+
: row.continuationToken === null
|
|
138
|
+
? null
|
|
139
|
+
: null;
|
|
140
|
+
return {
|
|
141
|
+
sessionId: row.sessionId,
|
|
142
|
+
channelId: typeof row.channelId === "string" ? row.channelId : "",
|
|
143
|
+
mode: row.mode === "task" ? "task" : "chat",
|
|
144
|
+
title: typeof row.title === "string" ? row.title : undefined,
|
|
145
|
+
turnCount: typeof row.turnCount === "number" ? row.turnCount : 0,
|
|
146
|
+
running: row.running === true,
|
|
147
|
+
createdAt: typeof row.createdAt === "string" ? row.createdAt : "",
|
|
148
|
+
updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : "",
|
|
149
|
+
continuationToken,
|
|
150
|
+
externalUrl:
|
|
151
|
+
typeof row.externalUrl === "string" ? row.externalUrl : undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Format a sessions table (id, channel, mode, turns, running, updated). */
|
|
156
|
+
export function formatSessionsTable(sessions: SessionSummary[]): string {
|
|
157
|
+
if (sessions.length === 0) {
|
|
158
|
+
return "No sessions.\n";
|
|
159
|
+
}
|
|
160
|
+
const header = ["SESSION", "CHANNEL", "MODE", "TURNS", "RUNNING", "UPDATED"];
|
|
161
|
+
const rows = sessions.map((s) => [
|
|
162
|
+
s.sessionId,
|
|
163
|
+
s.channelId,
|
|
164
|
+
s.mode,
|
|
165
|
+
String(s.turnCount),
|
|
166
|
+
s.running ? "yes" : "no",
|
|
167
|
+
s.updatedAt,
|
|
168
|
+
]);
|
|
169
|
+
return `${renderTable(header, rows)}\n`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function renderTable(header: string[], rows: string[][]): string {
|
|
173
|
+
const widths = header.map((h, i) =>
|
|
174
|
+
Math.max(h.length, ...rows.map((row) => (row[i] ?? "").length))
|
|
175
|
+
);
|
|
176
|
+
const renderRow = (row: string[]): string =>
|
|
177
|
+
row
|
|
178
|
+
.map((cell, i) => cell.padEnd(widths[i] ?? 0))
|
|
179
|
+
.join(" ")
|
|
180
|
+
.trimEnd();
|
|
181
|
+
return [renderRow(header), ...rows.map(renderRow)].join("\n");
|
|
182
|
+
}
|
|
@@ -64,6 +64,9 @@ export function createStreamProgress(options?: {
|
|
|
64
64
|
break;
|
|
65
65
|
}
|
|
66
66
|
case "reasoning.appended": {
|
|
67
|
+
if (event.data.parentCallId !== undefined) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
67
70
|
if (!thinkingShown) {
|
|
68
71
|
thinkingShown = true;
|
|
69
72
|
write(c.dim(" · thinking…\n"));
|
|
@@ -71,6 +74,11 @@ export function createStreamProgress(options?: {
|
|
|
71
74
|
break;
|
|
72
75
|
}
|
|
73
76
|
case "message.appended": {
|
|
77
|
+
// Nested subagent deltas stay inside the task chip/indent, not
|
|
78
|
+
// the parent agent stream.
|
|
79
|
+
if (event.data.parentCallId !== undefined) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
74
82
|
if (!assistantOpen) {
|
|
75
83
|
write(`${c.green("‹ agent")} `);
|
|
76
84
|
assistantOpen = true;
|
|
@@ -79,6 +87,9 @@ export function createStreamProgress(options?: {
|
|
|
79
87
|
break;
|
|
80
88
|
}
|
|
81
89
|
case "message.completed": {
|
|
90
|
+
if (event.data.parentCallId !== undefined) {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
82
93
|
closeAssistant();
|
|
83
94
|
break;
|
|
84
95
|
}
|
|
@@ -98,9 +109,18 @@ export function createStreamProgress(options?: {
|
|
|
98
109
|
closeAssistant();
|
|
99
110
|
const now = Date.now();
|
|
100
111
|
for (const call of event.data.calls) {
|
|
112
|
+
if (call.toolName === "task" && call.parentCallId === undefined) {
|
|
113
|
+
// Covered by subagent.called; avoid a duplicate bare `task` line.
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
101
116
|
toolStartedAt.set(call.callId, now);
|
|
117
|
+
const indent =
|
|
118
|
+
call.parentCallId !== undefined ||
|
|
119
|
+
event.data.parentCallId !== undefined
|
|
120
|
+
? " "
|
|
121
|
+
: " ";
|
|
102
122
|
write(
|
|
103
|
-
|
|
123
|
+
`${indent}${c.magenta("⚙")} ${c.bold(call.toolName)}${c.dim(
|
|
104
124
|
formatArgs(call.args)
|
|
105
125
|
)}\n`
|
|
106
126
|
);
|
|
@@ -127,13 +147,21 @@ export function createStreamProgress(options?: {
|
|
|
127
147
|
break;
|
|
128
148
|
}
|
|
129
149
|
case "action.result": {
|
|
150
|
+
if (
|
|
151
|
+
event.data.toolName === "task" &&
|
|
152
|
+
event.data.parentCallId === undefined
|
|
153
|
+
) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
130
156
|
const started = toolStartedAt.get(event.data.callId);
|
|
131
157
|
toolStartedAt.delete(event.data.callId);
|
|
132
158
|
const dur =
|
|
133
159
|
started === undefined ? "" : c.dim(` ${elapsed(started)}`);
|
|
134
160
|
const mark = event.data.isError ? c.red("✗") : c.magenta("✓");
|
|
161
|
+
const indent =
|
|
162
|
+
event.data.parentCallId !== undefined ? " " : " ";
|
|
135
163
|
write(
|
|
136
|
-
|
|
164
|
+
`${indent}${mark} ${c.bold(event.data.toolName)}${dur} ${c.dim(
|
|
137
165
|
truncate(stringify(event.data.output), 90)
|
|
138
166
|
)}\n`
|
|
139
167
|
);
|
|
@@ -149,6 +177,12 @@ export function createStreamProgress(options?: {
|
|
|
149
177
|
);
|
|
150
178
|
break;
|
|
151
179
|
}
|
|
180
|
+
case "subagent.completed": {
|
|
181
|
+
write(
|
|
182
|
+
` ${c.magenta("✓")} ${c.bold(event.data.name ?? "subagent")} ${c.dim("done")}\n`
|
|
183
|
+
);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
152
186
|
case "turn.completed": {
|
|
153
187
|
closeAssistant();
|
|
154
188
|
const usage = event.data.usage;
|