@cursor/july 0.1.16 → 0.1.18
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 +9 -4
- package/dist/bin/agent-serve.js +60 -2
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +13 -3
- package/dist/channels/slack/auth.d.ts +7 -1
- package/dist/channels/slack/auth.d.ts.map +1 -1
- package/dist/channels/slack/auth.js +14 -0
- package/dist/channels/slack/blocks.d.ts +61 -0
- package/dist/channels/slack/blocks.d.ts.map +1 -0
- package/dist/channels/slack/blocks.js +90 -0
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +1 -1
- package/dist/channels/slack/defaults.d.ts +2 -0
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +29 -3
- package/dist/channels/slack/dispatch.d.ts +3 -17
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +4 -1
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +6 -1
- package/dist/channels/slack/index.d.ts +2 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +2 -0
- package/dist/channels/slack/init.d.ts.map +1 -1
- package/dist/channels/slack/init.js +4 -1
- package/dist/channels/slack/interactive.d.ts +39 -3
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +153 -7
- package/dist/channels/slack/manifest.d.ts +13 -0
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +36 -14
- package/dist/channels/slack/message-body.d.ts +13 -0
- package/dist/channels/slack/message-body.d.ts.map +1 -0
- package/dist/channels/slack/message-body.js +82 -0
- package/dist/channels/slack/setup.d.ts +4 -1
- package/dist/channels/slack/setup.d.ts.map +1 -1
- package/dist/channels/slack/setup.js +16 -2
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +24 -3
- package/dist/channels/slack/thread-context.d.ts +0 -2
- package/dist/channels/slack/thread-context.d.ts.map +1 -1
- package/dist/channels/slack/thread-context.js +1 -65
- package/dist/channels/slack/types.d.ts +93 -1
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/connections.d.ts +4 -1
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +4 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.DjHC-Ggf.js → app.BR5EdVSq.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BOzkeUi8.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DwviwmIq.js → VPLocalSearchBox.CNCzfUKC.js} +1 -1
- package/dist/docs/assets/chunks/{theme.BIpeYSon.js → theme.CwKvMSWv.js} +2 -2
- package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.js → example-agents_security-reviewer.md.Dhj_m7_B.js} +1 -1
- package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.js → guides_mcp-oauth.md.Dd8EgSem.js} +3 -2
- package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.lean.js → guides_mcp-oauth.md.Dd8EgSem.lean.js} +1 -1
- package/dist/docs/assets/{guides_slack.md.FWale6Ip.js → guides_slack.md.D_xFKlnF.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.CsSr3rDz.js → reference_connections.md.B9Q3TOve.js} +12 -5
- package/dist/docs/assets/{reference_connections.md.CsSr3rDz.lean.js → reference_connections.md.B9Q3TOve.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/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 +5 -5
- 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/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +6 -5
- package/dist/docs/guides/slack.html +5 -5
- 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/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +14 -7
- 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/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 +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/evals/assertions.d.ts +190 -0
- package/dist/evals/assertions.d.ts.map +1 -0
- package/dist/evals/assertions.js +461 -0
- package/dist/evals/expect.d.ts +95 -0
- package/dist/evals/expect.d.ts.map +1 -0
- package/dist/evals/expect.js +194 -0
- package/dist/evals/judge.d.ts +133 -0
- package/dist/evals/judge.d.ts.map +1 -0
- package/dist/evals/judge.js +228 -0
- package/dist/evals/loaders.d.ts +51 -0
- package/dist/evals/loaders.d.ts.map +1 -0
- package/dist/evals/loaders.js +124 -0
- package/dist/evals/matchers.d.ts +95 -0
- package/dist/evals/matchers.d.ts.map +1 -0
- package/dist/evals/matchers.js +145 -0
- package/dist/evals/reporters.d.ts +70 -0
- package/dist/evals/reporters.d.ts.map +1 -0
- package/dist/evals/reporters.js +221 -0
- package/dist/evals/results.d.ts +103 -0
- package/dist/evals/results.d.ts.map +1 -0
- package/dist/evals/results.js +28 -0
- package/dist/evals/run-facts.d.ts +77 -0
- package/dist/evals/run-facts.d.ts.map +1 -0
- package/dist/evals/run-facts.js +187 -0
- package/dist/evals.d.ts +118 -31
- package/dist/evals.d.ts.map +1 -1
- package/dist/evals.js +46 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/builtin-tools/index.d.ts +10 -0
- package/dist/internal/builtin-tools/index.d.ts.map +1 -0
- package/dist/internal/builtin-tools/index.js +27 -0
- package/dist/internal/builtin-tools/reminders.d.ts +9 -0
- package/dist/internal/builtin-tools/reminders.d.ts.map +1 -0
- package/dist/internal/builtin-tools/reminders.js +138 -0
- package/dist/internal/cli-ax.d.ts +27 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +90 -12
- package/dist/internal/cli-mcp.d.ts +8 -0
- package/dist/internal/cli-mcp.d.ts.map +1 -1
- package/dist/internal/cli-mcp.js +98 -0
- package/dist/internal/cli-slack.d.ts +29 -1
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +799 -13
- package/dist/internal/cursor-event-relay.d.ts +30 -0
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +37 -0
- package/dist/internal/deploy-client.d.ts +7 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +9 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +70 -9
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +2 -0
- package/dist/internal/eval-judge-model.d.ts +59 -0
- package/dist/internal/eval-judge-model.d.ts.map +1 -0
- package/dist/internal/eval-judge-model.js +131 -0
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +12 -20
- package/dist/internal/eval-runner.d.ts +31 -25
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +329 -126
- package/dist/internal/evals-client.d.ts.map +1 -1
- package/dist/internal/evals-client.js +4 -1
- package/dist/internal/local-env.d.ts +8 -0
- package/dist/internal/local-env.d.ts.map +1 -0
- package/dist/internal/local-env.js +47 -0
- package/dist/internal/playground/static.d.ts +3 -0
- package/dist/internal/playground/static.d.ts.map +1 -1
- package/dist/internal/playground/static.js +3 -0
- package/dist/internal/reminder-runner.d.ts +7 -0
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +21 -0
- package/dist/internal/run-client.d.ts +15 -0
- package/dist/internal/run-client.d.ts.map +1 -1
- package/dist/internal/run-client.js +3 -2
- package/dist/internal/sdk-runner.d.ts +51 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +176 -36
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +11 -1
- package/dist/internal/session-engine.d.ts +18 -5
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +81 -19
- package/dist/internal/slack-provision-client.d.ts +133 -0
- package/dist/internal/slack-provision-client.d.ts.map +1 -0
- package/dist/internal/slack-provision-client.js +286 -0
- package/dist/playground/assets/cursor-icons-16-f_W_ogc-.woff2 +0 -0
- package/dist/playground/assets/index-DRjS5klQ.js +85 -0
- package/dist/playground/assets/index-TCDs-UhZ.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +98 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +26 -0
- package/docs/example-agents/security-reviewer.md +1 -1
- package/docs/guides/mcp-oauth.md +10 -5
- package/docs/guides/slack.md +3 -2
- package/docs/reference/connections.md +28 -18
- package/package.json +20 -2
- package/skills/create-agent/SKILL.md +15 -7
- package/skills/debug/SKILL.md +3 -1
- package/skills/mcp-auth/SKILL.md +1 -1
- package/skills/setup-slack/SKILL.md +177 -11
- package/src/bin/agent-serve.ts +63 -2
- package/src/channels/slack/api.ts +16 -3
- package/src/channels/slack/auth.ts +16 -1
- package/src/channels/slack/blocks.ts +139 -0
- package/src/channels/slack/cursor-account.ts +1 -0
- package/src/channels/slack/defaults.ts +32 -4
- package/src/channels/slack/dispatch.ts +6 -19
- package/src/channels/slack/inbound.ts +6 -1
- package/src/channels/slack/index.ts +2 -0
- package/src/channels/slack/init.ts +3 -0
- package/src/channels/slack/interactive.ts +184 -19
- package/src/channels/slack/manifest.ts +37 -6
- package/src/channels/slack/message-body.ts +90 -0
- package/src/channels/slack/setup.ts +16 -2
- package/src/channels/slack/slack-channel.ts +37 -6
- package/src/channels/slack/thread-context.ts +1 -69
- package/src/channels/slack/types.ts +105 -1
- package/src/connections.ts +4 -1
- package/src/evals/assertions.ts +704 -0
- package/src/evals/expect.ts +272 -0
- package/src/evals/judge.ts +346 -0
- package/src/evals/loaders.ts +113 -0
- package/src/evals/matchers.ts +246 -0
- package/src/evals/reporters.ts +274 -0
- package/src/evals/results.ts +132 -0
- package/src/evals/run-facts.ts +274 -0
- package/src/evals.ts +205 -42
- package/src/index.ts +2 -0
- package/src/internal/builtin-tools/index.ts +40 -0
- package/src/internal/builtin-tools/reminders.ts +158 -0
- package/src/internal/cli-ax.ts +134 -15
- package/src/internal/cli-mcp.ts +118 -5
- package/src/internal/cli-slack.ts +1017 -15
- package/src/internal/cursor-event-relay.ts +67 -0
- package/src/internal/deploy-client.ts +10 -0
- package/src/internal/discovery.ts +83 -5
- package/src/internal/distribution.ts +2 -0
- package/src/internal/eval-judge-model.ts +148 -0
- package/src/internal/eval-run-store.ts +10 -14
- package/src/internal/eval-runner.ts +460 -214
- package/src/internal/evals-client.ts +5 -0
- package/src/internal/local-env.ts +53 -0
- package/src/internal/playground/static.ts +3 -0
- package/src/internal/reminder-runner.ts +30 -0
- package/src/internal/run-client.ts +17 -1
- package/src/internal/sdk-runner.ts +234 -35
- package/src/internal/server.ts +12 -0
- package/src/internal/session-engine.ts +103 -21
- package/src/internal/slack-provision-client.ts +442 -0
- package/src/types.ts +125 -4
- package/dist/channels/slack/post-update-delivery.d.ts +0 -85
- package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DZtu0ZIc.js +0 -1
- package/dist/internal/json-dir-store.d.ts +0 -32
- package/dist/internal/json-dir-store.d.ts.map +0 -1
- package/dist/internal/persistence-coordinator.d.ts +0 -127
- package/dist/internal/persistence-coordinator.d.ts.map +0 -1
- package/dist/persistence.d.ts +0 -184
- package/dist/persistence.d.ts.map +0 -1
- package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
- package/dist/playground/assets/index-Bwl4Onx4.js +0 -85
- package/dist/playground/assets/index-ByV4nfch.css +0 -1
- /package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.lean.js → example-agents_security-reviewer.md.Dhj_m7_B.lean.js} +0 -0
- /package/dist/docs/assets/{guides_slack.md.FWale6Ip.lean.js → guides_slack.md.D_xFKlnF.lean.js} +0 -0
|
@@ -488,6 +488,11 @@ export function snapshotToEvalRunResults(
|
|
|
488
488
|
path: c.fileId,
|
|
489
489
|
...(c.description === undefined ? {} : { description: c.description }),
|
|
490
490
|
ok: c.ok === true,
|
|
491
|
+
// Older serve processes send no verdict; infer it from `ok` so a remote
|
|
492
|
+
// batch still prints and reports consistently.
|
|
493
|
+
verdict: c.verdict ?? (c.ok === true ? "passed" : "failed"),
|
|
494
|
+
...(c.skipReason === undefined ? {} : { skipReason: c.skipReason }),
|
|
495
|
+
...(c.metadata === undefined ? {} : { metadata: c.metadata }),
|
|
491
496
|
assertions: c.assertions ?? [],
|
|
492
497
|
...(c.sessionId === undefined ? {} : { sessionId: c.sessionId }),
|
|
493
498
|
inputs: c.inputs ?? [],
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads an agent directory's `.env.local` (and each immediate child's,
|
|
3
|
+
* for multi-agent hosts) into process.env. Real env always wins —
|
|
4
|
+
* `process.loadEnvFile` never overrides. Values a previous call loaded are
|
|
5
|
+
* refreshed, so a reload picks up edits made since.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
// Not real env, so a reload has to clear them first: loadEnvFile skips
|
|
12
|
+
// anything already in process.env, which would pin the first-boot values.
|
|
13
|
+
let fileLoadedKeys = new Set<string>();
|
|
14
|
+
|
|
15
|
+
export function loadAgentLocalEnv(dir: string): string[] {
|
|
16
|
+
for (const key of fileLoadedKeys) {
|
|
17
|
+
delete process.env[key];
|
|
18
|
+
}
|
|
19
|
+
const realKeys = new Set(Object.keys(process.env));
|
|
20
|
+
const loaded: string[] = [];
|
|
21
|
+
const tryLoad = (path: string): void => {
|
|
22
|
+
if (!existsSync(path)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
process.loadEnvFile(path);
|
|
26
|
+
loaded.push(path);
|
|
27
|
+
};
|
|
28
|
+
const trackLoadedKeys = (): void => {
|
|
29
|
+
fileLoadedKeys = new Set(
|
|
30
|
+
Object.keys(process.env).filter((key) => !realKeys.has(key))
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
tryLoad(join(dir, ".env.local"));
|
|
35
|
+
|
|
36
|
+
let children: string[];
|
|
37
|
+
try {
|
|
38
|
+
children = readdirSync(dir, { withFileTypes: true })
|
|
39
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
|
|
40
|
+
.map((entry) => entry.name);
|
|
41
|
+
} catch {
|
|
42
|
+
trackLoadedKeys();
|
|
43
|
+
return loaded;
|
|
44
|
+
}
|
|
45
|
+
for (const child of children) {
|
|
46
|
+
if (child === "node_modules") {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
tryLoad(join(dir, child, ".env.local"));
|
|
50
|
+
}
|
|
51
|
+
trackLoadedKeys();
|
|
52
|
+
return loaded;
|
|
53
|
+
}
|
|
@@ -52,6 +52,9 @@ export function playgroundBundleExists(): boolean {
|
|
|
52
52
|
* The SPA `index.html` with a `<base href>` injected so its relative asset
|
|
53
53
|
* URLs resolve under the mount point (`/playground/` or `/<slug>/playground/`).
|
|
54
54
|
* Falls back to a helpful placeholder page when the bundle is missing.
|
|
55
|
+
* The hosted alias performs the same injection for its central copy
|
|
56
|
+
* (`backend/server/src/agentServe/aliasPlaygroundShell.ts`) — keep them
|
|
57
|
+
* behaviorally aligned.
|
|
55
58
|
*/
|
|
56
59
|
export function renderPlaygroundIndexHtml(baseHref: string): string {
|
|
57
60
|
if (!playgroundBundleExists()) {
|
|
@@ -336,6 +336,36 @@ export class ReminderRunner {
|
|
|
336
336
|
return n;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Re-key armed reminders when a channel rotates a session's continuation
|
|
341
|
+
* token (the built-in HTTP channel rotates after every follow-up). Without
|
|
342
|
+
* this, a reminder armed before the rotation misses its session at fire
|
|
343
|
+
* time and disarms as `session_gone`.
|
|
344
|
+
*/
|
|
345
|
+
async rekeyContinuation(
|
|
346
|
+
channelId: string,
|
|
347
|
+
oldToken: string,
|
|
348
|
+
newToken: string
|
|
349
|
+
): Promise<number> {
|
|
350
|
+
const armed = await this.store.listArmed();
|
|
351
|
+
let n = 0;
|
|
352
|
+
for (const record of armed) {
|
|
353
|
+
if (
|
|
354
|
+
record.channelId !== channelId ||
|
|
355
|
+
record.continuationToken !== oldToken
|
|
356
|
+
) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
await this.persist({
|
|
360
|
+
...record,
|
|
361
|
+
continuationToken: newToken,
|
|
362
|
+
updatedAt: new Date().toISOString(),
|
|
363
|
+
});
|
|
364
|
+
n += 1;
|
|
365
|
+
}
|
|
366
|
+
return n;
|
|
367
|
+
}
|
|
368
|
+
|
|
339
369
|
/** Fire one reminder immediately (dev / hillclimb). */
|
|
340
370
|
async dispatch(reminderId: string): Promise<{
|
|
341
371
|
reminderId: string;
|
|
@@ -46,6 +46,11 @@ export interface RunSessionResult {
|
|
|
46
46
|
events: SessionEvent[];
|
|
47
47
|
trajectory: Trajectory;
|
|
48
48
|
ok: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Index to pass as `startIndex` on the next call for this session. Undefined
|
|
51
|
+
* when the turn streamed nothing, in which case reuse the previous value.
|
|
52
|
+
*/
|
|
53
|
+
nextEventIndex?: number;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
const TERMINAL_TYPES = new Set([
|
|
@@ -63,6 +68,16 @@ export async function runSession(
|
|
|
63
68
|
messages: string[];
|
|
64
69
|
sessionId?: string;
|
|
65
70
|
continuationToken?: string;
|
|
71
|
+
/**
|
|
72
|
+
* First event index to read when resuming an existing session.
|
|
73
|
+
*
|
|
74
|
+
* The stream replays from `startIndex` and this client stops at the first
|
|
75
|
+
* terminal event, so a caller that sends several turns across separate
|
|
76
|
+
* `runSession` calls **must** pass the index after its last consumed event
|
|
77
|
+
* (see {@link RunSessionResult.nextEventIndex}). Omitting it replays the
|
|
78
|
+
* earlier turn and returns those stale events instead of the new turn's.
|
|
79
|
+
*/
|
|
80
|
+
startIndex?: number;
|
|
66
81
|
/** Applied only when creating a new session (first message). */
|
|
67
82
|
workspaceDir?: string;
|
|
68
83
|
/** Applied only when creating a new session (first message). */
|
|
@@ -78,7 +93,7 @@ export async function runSession(
|
|
|
78
93
|
let sessionId = options.sessionId;
|
|
79
94
|
let continuationToken = options.continuationToken;
|
|
80
95
|
const allEvents: SessionEvent[] = [];
|
|
81
|
-
let nextEventIndex = 0;
|
|
96
|
+
let nextEventIndex = options.startIndex ?? 0;
|
|
82
97
|
|
|
83
98
|
const auth: RequestAuth = {
|
|
84
99
|
bearerToken: options.bearerToken,
|
|
@@ -126,6 +141,7 @@ export async function runSession(
|
|
|
126
141
|
events: allEvents,
|
|
127
142
|
trajectory,
|
|
128
143
|
ok: trajectory.ok,
|
|
144
|
+
nextEventIndex: allEvents.length === 0 ? undefined : nextEventIndex,
|
|
129
145
|
};
|
|
130
146
|
}
|
|
131
147
|
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type AgentDefinition,
|
|
13
13
|
type AgentOptions,
|
|
14
14
|
type CloudAgentOptions,
|
|
15
|
+
createAgentPlatform,
|
|
15
16
|
JsonlLocalAgentStore,
|
|
16
17
|
type LocalAgentStore,
|
|
17
18
|
type ModelSelection,
|
|
@@ -30,9 +31,48 @@ import {
|
|
|
30
31
|
/** Internal seam between the session engine and the Cursor SDK harness. */
|
|
31
32
|
export interface AgentRunner {
|
|
32
33
|
runTurn(request: RunnerTurnRequest): Promise<TurnOutcome>;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a local workspace before any session needs it, so the first turn
|
|
36
|
+
* is not the one that pays for it. Best effort, hence the reported outcome:
|
|
37
|
+
* a runner that cannot warm must say so rather than let a caller log
|
|
38
|
+
* success over a workspace that was never touched.
|
|
39
|
+
*/
|
|
40
|
+
prewarm?(request: RunnerPrewarmRequest): Promise<PrewarmOutcome>;
|
|
33
41
|
dispose?(): Promise<void>;
|
|
34
42
|
}
|
|
35
43
|
|
|
44
|
+
/**
|
|
45
|
+
* The parts of a turn that decide which local executor it runs on. Must stay
|
|
46
|
+
* a subset of {@link RunnerTurnRequest} — anything that disagrees warms an
|
|
47
|
+
* executor no turn will ask for.
|
|
48
|
+
*/
|
|
49
|
+
export type RunnerPrewarmRequest = Pick<
|
|
50
|
+
RunnerTurnRequest,
|
|
51
|
+
| "workspaceDir"
|
|
52
|
+
| "runnerStateDir"
|
|
53
|
+
| "sandbox"
|
|
54
|
+
| "mcpServers"
|
|
55
|
+
| "subagents"
|
|
56
|
+
| "model"
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
/** Whether a workspace was actually resolved, and why not when it wasn't. */
|
|
60
|
+
export type PrewarmOutcome =
|
|
61
|
+
| { readonly warmed: true }
|
|
62
|
+
| { readonly warmed: false; readonly reason: string };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* `CursorAgentPlatform.prewarmLocalWorkspace` as of `@cursor/sdk` 1.0.26.
|
|
66
|
+
* Declared structurally so this keeps building — and reports that it skipped
|
|
67
|
+
* warming — against the pinned older SDK. Delete once the pin passes that
|
|
68
|
+
* version.
|
|
69
|
+
*/
|
|
70
|
+
type MaybePrewarmingPlatform = {
|
|
71
|
+
prewarmLocalWorkspace?: (
|
|
72
|
+
options: AgentOptions
|
|
73
|
+
) => Promise<() => Promise<void>>;
|
|
74
|
+
};
|
|
75
|
+
|
|
36
76
|
export interface CursorSdkRunnerOptions {
|
|
37
77
|
/** Cursor API key. Defaults to the `CURSOR_API_KEY` environment variable. */
|
|
38
78
|
apiKey?: string;
|
|
@@ -46,15 +86,90 @@ export interface CursorSdkRunnerOptions {
|
|
|
46
86
|
logger?: (line: string) => void;
|
|
47
87
|
}
|
|
48
88
|
|
|
89
|
+
/**
|
|
90
|
+
* How long one SDK agent handle (and the Connect transport inside it) may be
|
|
91
|
+
* reused before the runner retires it.
|
|
92
|
+
*
|
|
93
|
+
* The transport exchanges the account API key for a short-lived access token
|
|
94
|
+
* (`SessionType.API_KEY_TOKEN`, 60 minutes) once per handle, and has no
|
|
95
|
+
* reliable refresh: an Unauthenticated rejection on the agent's streaming RPC
|
|
96
|
+
* surfaces while the stream is being read — outside the transport's
|
|
97
|
+
* request-time 401 retry — so an expired token gets replayed on every
|
|
98
|
+
* subsequent turn until the handle is rebuilt. Long-lived sessions (watch
|
|
99
|
+
* wakes, crons) hit this at the ~60 minute mark and then fail forever.
|
|
100
|
+
* Retiring the handle comfortably before the token can expire keeps them
|
|
101
|
+
* healthy: `Agent.resume` rehydrates the same conversation from the runner
|
|
102
|
+
* store on a fresh transport, so recycling costs only transport warmth.
|
|
103
|
+
*/
|
|
104
|
+
const MAX_AGENT_HANDLE_AGE_MS = 45 * 60 * 1000;
|
|
105
|
+
|
|
106
|
+
interface CachedAgentHandle {
|
|
107
|
+
agent: Promise<SDKAgent>;
|
|
108
|
+
openedAtMs: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The backend's `NotLoggedIn` copy ("Authentication error" title, "try
|
|
113
|
+
* logging out and back in." detail) as it surfaces through SDK RunResults.
|
|
114
|
+
* Deliberately narrow: this gates a one-shot in-place retry, and matching
|
|
115
|
+
* broader error text would retry turns that a fresh transport cannot fix.
|
|
116
|
+
*/
|
|
117
|
+
export function isAuthErrorMessage(message: string | undefined): boolean {
|
|
118
|
+
if (message === undefined) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return /authentication error|try logging out and back in/i.test(message);
|
|
122
|
+
}
|
|
123
|
+
|
|
49
124
|
export class CursorSdkRunner implements AgentRunner {
|
|
50
|
-
private readonly agents = new Map<string,
|
|
125
|
+
private readonly agents = new Map<string, CachedAgentHandle>();
|
|
51
126
|
private readonly stores = new Map<string, Promise<LocalAgentStore>>();
|
|
127
|
+
private readonly prewarmReleases: Array<() => Promise<void>> = [];
|
|
52
128
|
|
|
53
129
|
constructor(private readonly options: CursorSdkRunnerOptions = {}) {}
|
|
54
130
|
|
|
55
131
|
async runTurn(request: RunnerTurnRequest): Promise<TurnOutcome> {
|
|
132
|
+
let sawUpdate = false;
|
|
133
|
+
const tracked: RunnerTurnRequest = {
|
|
134
|
+
...request,
|
|
135
|
+
onUpdate: (update) => {
|
|
136
|
+
sawUpdate = true;
|
|
137
|
+
return request.onUpdate(update);
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
const outcome = await this.runTurnAttempt(tracked);
|
|
141
|
+
if (
|
|
142
|
+
outcome.status !== "error" ||
|
|
143
|
+
!isAuthErrorMessage(outcome.errorMessage) ||
|
|
144
|
+
sawUpdate ||
|
|
145
|
+
request.isFirstTurn ||
|
|
146
|
+
request.signal?.aborted === true
|
|
147
|
+
) {
|
|
148
|
+
return outcome;
|
|
149
|
+
}
|
|
150
|
+
// The turn died on an authentication failure before producing any output
|
|
151
|
+
// — the signature of a handle whose exchanged access token expired (the
|
|
152
|
+
// failed attempt already evicted it). Retrying once on a fresh handle is
|
|
153
|
+
// safe here because nothing streamed and no tool ran. First turns are
|
|
154
|
+
// excluded: their token was exchanged moments ago, so the credential
|
|
155
|
+
// itself is the problem and a retry cannot succeed.
|
|
156
|
+
this.log(
|
|
157
|
+
`[agentkit] session ${request.sessionId}: retrying the turn on a fresh SDK agent handle after an authentication failure`
|
|
158
|
+
);
|
|
56
159
|
try {
|
|
57
|
-
|
|
160
|
+
return await this.runTurnAttempt(tracked);
|
|
161
|
+
} catch {
|
|
162
|
+
// The retry was best effort; report the original failure.
|
|
163
|
+
return outcome;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async runTurnAttempt(
|
|
168
|
+
request: RunnerTurnRequest
|
|
169
|
+
): Promise<TurnOutcome> {
|
|
170
|
+
const agentPromise = this.getAgent(request);
|
|
171
|
+
try {
|
|
172
|
+
const agent = await agentPromise;
|
|
58
173
|
const run = await agent.send(request.prompt, {
|
|
59
174
|
onDelta: async ({ update }) => {
|
|
60
175
|
try {
|
|
@@ -100,20 +215,56 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
100
215
|
}
|
|
101
216
|
try {
|
|
102
217
|
const result = await run.wait();
|
|
103
|
-
|
|
218
|
+
const outcome = toTurnOutcome(result);
|
|
219
|
+
if (outcome.status === "error") {
|
|
220
|
+
// An error result usually means the handle's transport is poisoned
|
|
221
|
+
// — most often a cached access token that aged past its 60-minute
|
|
222
|
+
// lifetime and gets replayed on every request (stream failures
|
|
223
|
+
// bypass the transport's 401 retry). Retire the handle so the next
|
|
224
|
+
// attempt resumes the conversation from the store on a fresh
|
|
225
|
+
// transport instead of failing every turn until a restart.
|
|
226
|
+
this.evictAgent(request.sessionId, agentPromise);
|
|
227
|
+
}
|
|
228
|
+
return outcome;
|
|
104
229
|
} finally {
|
|
105
230
|
signal?.removeEventListener("abort", onAbort);
|
|
106
231
|
}
|
|
107
232
|
} catch (error) {
|
|
108
233
|
// Drop the cached agent so a dead Connect/HTTP2 session cannot poison
|
|
109
234
|
// the next turn (terminal/shell overflow often kills the transport).
|
|
110
|
-
this.
|
|
235
|
+
this.evictAgent(request.sessionId, agentPromise);
|
|
111
236
|
throw error;
|
|
112
237
|
}
|
|
113
238
|
}
|
|
114
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Build the local executor for a workspace before any session asks for it.
|
|
242
|
+
* The warmed executor is reference counted, so the lease is held until
|
|
243
|
+
* {@link dispose}; dropping it early would tear the workspace back down.
|
|
244
|
+
*/
|
|
245
|
+
async prewarm(request: RunnerPrewarmRequest): Promise<PrewarmOutcome> {
|
|
246
|
+
const platform = (await createAgentPlatform()) as MaybePrewarmingPlatform;
|
|
247
|
+
if (typeof platform.prewarmLocalWorkspace !== "function") {
|
|
248
|
+
// Older @cursor/sdk. Turns still work; they just pay on the first one.
|
|
249
|
+
return {
|
|
250
|
+
warmed: false,
|
|
251
|
+
reason:
|
|
252
|
+
"the installed @cursor/sdk has no prewarmLocalWorkspace; upgrade it to warm the workspace before the first turn",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const release = await platform.prewarmLocalWorkspace(
|
|
256
|
+
await this.buildLocalOptions(request)
|
|
257
|
+
);
|
|
258
|
+
this.prewarmReleases.push(release);
|
|
259
|
+
return { warmed: true };
|
|
260
|
+
}
|
|
261
|
+
|
|
115
262
|
async dispose(): Promise<void> {
|
|
116
|
-
const
|
|
263
|
+
const releases = this.prewarmReleases.splice(0);
|
|
264
|
+
await Promise.allSettled(releases.map((release) => release()));
|
|
265
|
+
const agents = await Promise.allSettled(
|
|
266
|
+
[...this.agents.values()].map((handle) => handle.agent)
|
|
267
|
+
);
|
|
117
268
|
this.agents.clear();
|
|
118
269
|
for (const settled of agents) {
|
|
119
270
|
if (settled.status === "fulfilled") {
|
|
@@ -123,58 +274,104 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
123
274
|
}
|
|
124
275
|
|
|
125
276
|
private getAgent(request: RunnerTurnRequest): Promise<SDKAgent> {
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
this
|
|
277
|
+
const cached = this.agents.get(request.sessionId);
|
|
278
|
+
if (cached !== undefined) {
|
|
279
|
+
const ageMs = Date.now() - cached.openedAtMs;
|
|
280
|
+
if (ageMs < MAX_AGENT_HANDLE_AGE_MS) {
|
|
281
|
+
return cached.agent;
|
|
282
|
+
}
|
|
283
|
+
// Past this age the handle's exchanged access token could expire
|
|
284
|
+
// mid-turn. Retire it and reopen below on a fresh transport.
|
|
285
|
+
this.log(
|
|
286
|
+
`[agentkit] session ${request.sessionId}: recycling the SDK agent handle after ${Math.round(ageMs / 60_000)} min so the turn runs on a fresh access token`
|
|
287
|
+
);
|
|
288
|
+
this.evictAgent(request.sessionId, cached.agent);
|
|
133
289
|
}
|
|
290
|
+
const agentPromise = this.openAgent(request).catch((error) => {
|
|
291
|
+
// Nothing opened, so there is no handle to close — just free the slot.
|
|
292
|
+
this.agents.delete(request.sessionId);
|
|
293
|
+
throw error;
|
|
294
|
+
});
|
|
295
|
+
this.agents.set(request.sessionId, {
|
|
296
|
+
agent: agentPromise,
|
|
297
|
+
openedAtMs: Date.now(),
|
|
298
|
+
});
|
|
134
299
|
return agentPromise;
|
|
135
300
|
}
|
|
136
301
|
|
|
137
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Drop a cached handle and close it best-effort to release its transport
|
|
304
|
+
* and executor lease. Identity-checked so a turn that lost a race with a
|
|
305
|
+
* newer open cannot evict the replacement handle.
|
|
306
|
+
*/
|
|
307
|
+
private evictAgent(sessionId: string, agent: Promise<SDKAgent>): void {
|
|
308
|
+
const cached = this.agents.get(sessionId);
|
|
309
|
+
if (cached === undefined || cached.agent !== agent) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
this.agents.delete(sessionId);
|
|
313
|
+
void cached.agent.then((sdkAgent) => sdkAgent.close()).catch(() => {});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private log(line: string): void {
|
|
317
|
+
(this.options.logger ?? (() => {}))(line);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private requireApiKey(): string {
|
|
138
321
|
const apiKey = this.options.apiKey ?? process.env.CURSOR_API_KEY;
|
|
139
322
|
if (apiKey === undefined || apiKey.trim() === "") {
|
|
140
323
|
throw new Error(
|
|
141
324
|
"The Cursor SDK runner requires a Cursor credential: run `agent-serve login`, set CURSOR_API_KEY, or pass serve({ apiKey })."
|
|
142
325
|
);
|
|
143
326
|
}
|
|
144
|
-
|
|
327
|
+
return apiKey;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private buildSharedOptions(
|
|
331
|
+
request: RunnerPrewarmRequest
|
|
332
|
+
): Pick<AgentOptions, "model" | "apiKey" | "mcpServers" | "agents"> {
|
|
145
333
|
const subagents = toAgentDefinitions(request.subagents);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
> = {
|
|
150
|
-
model: toModelSelection(model),
|
|
151
|
-
apiKey,
|
|
334
|
+
return {
|
|
335
|
+
model: toModelSelection(request.model ?? DEFAULT_MODEL),
|
|
336
|
+
apiKey: this.requireApiKey(),
|
|
152
337
|
...(Object.keys(request.mcpServers).length > 0
|
|
153
338
|
? { mcpServers: request.mcpServers as AgentOptions["mcpServers"] }
|
|
154
339
|
: {}),
|
|
155
340
|
...(subagents === undefined ? {} : { agents: subagents }),
|
|
156
341
|
};
|
|
157
|
-
|
|
158
|
-
if (request.runtime === "cloud") {
|
|
159
|
-
return this.openCloudAgent(request, shared);
|
|
160
|
-
}
|
|
161
|
-
return this.openLocalAgent(request, shared);
|
|
162
342
|
}
|
|
163
343
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
344
|
+
/**
|
|
345
|
+
* The options that decide which local executor a turn runs on. Prewarming
|
|
346
|
+
* goes through here too, so a warmed workspace is the one turns land on.
|
|
347
|
+
*/
|
|
348
|
+
private async buildLocalOptions(
|
|
349
|
+
request: RunnerPrewarmRequest
|
|
350
|
+
): Promise<AgentOptions> {
|
|
168
351
|
const store =
|
|
169
352
|
this.options.store ?? (await this.getStore(request.runnerStateDir));
|
|
170
|
-
|
|
171
|
-
...
|
|
353
|
+
return {
|
|
354
|
+
...this.buildSharedOptions(request),
|
|
172
355
|
local: {
|
|
173
356
|
cwd: request.workspaceDir,
|
|
174
357
|
settingSources: ["project"],
|
|
175
358
|
store,
|
|
359
|
+
...(request.sandbox === true
|
|
360
|
+
? { sandboxOptions: { enabled: true } }
|
|
361
|
+
: {}),
|
|
176
362
|
},
|
|
177
363
|
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private async openAgent(request: RunnerTurnRequest): Promise<SDKAgent> {
|
|
367
|
+
if (request.runtime === "cloud") {
|
|
368
|
+
return this.openCloudAgent(request, this.buildSharedOptions(request));
|
|
369
|
+
}
|
|
370
|
+
return this.openLocalAgent(request);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private async openLocalAgent(request: RunnerTurnRequest): Promise<SDKAgent> {
|
|
374
|
+
const options = await this.buildLocalOptions(request);
|
|
178
375
|
|
|
179
376
|
if (request.isFirstTurn || request.sdkAgentId === undefined) {
|
|
180
377
|
return Agent.create({
|
|
@@ -195,7 +392,7 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
195
392
|
// conversation under the same id beats failing this thread's turns
|
|
196
393
|
// forever ("Agent … not found" on every follow-up). The channel's
|
|
197
394
|
// warm-turn prompt context re-hydrates the thread history.
|
|
198
|
-
|
|
395
|
+
this.log(
|
|
199
396
|
`[agentkit] no stored conversation for ${agentId} on this host; starting a fresh one`
|
|
200
397
|
);
|
|
201
398
|
return Agent.create({ ...options, agentId });
|
|
@@ -341,11 +538,13 @@ function toTurnOutcome(result: RunResult): TurnOutcome {
|
|
|
341
538
|
return {
|
|
342
539
|
status: result.status,
|
|
343
540
|
...(result.result === undefined ? {} : { result: result.result }),
|
|
344
|
-
// The published RunResult carries no structured error details; failures
|
|
345
|
-
// thrown by send()/wait() surface through the engine's catch instead.
|
|
346
541
|
...(result.status === "error"
|
|
347
|
-
? { errorMessage:
|
|
542
|
+
? { errorMessage: runErrorMessage(result) }
|
|
348
543
|
: {}),
|
|
349
544
|
...(usage === undefined ? {} : { usage }),
|
|
350
545
|
};
|
|
351
546
|
}
|
|
547
|
+
|
|
548
|
+
export function runErrorMessage(result: Pick<RunResult, "error">): string {
|
|
549
|
+
return result.error?.message?.trim() || "The Cursor agent run failed";
|
|
550
|
+
}
|
package/src/internal/server.ts
CHANGED
|
@@ -385,6 +385,12 @@ export async function startServer(
|
|
|
385
385
|
// channel or route can address them (continuation tokens must resolve).
|
|
386
386
|
await engine.restoreStoredSessions();
|
|
387
387
|
|
|
388
|
+
// Resolve a shared harness workspace in the background: on a large repo
|
|
389
|
+
// that takes seconds, and a deployment coming online should absorb it
|
|
390
|
+
// rather than whoever sends the first message. No-op unless the agent
|
|
391
|
+
// pins `local.workspaceDir`.
|
|
392
|
+
void engine.prewarmSharedWorkspace();
|
|
393
|
+
|
|
388
394
|
if (!githubAuthWarmed) {
|
|
389
395
|
// Soft ambient GitHub auth check before channel onStart (which may call
|
|
390
396
|
// host.github). Missing auth is a warning only — serve still starts.
|
|
@@ -415,6 +421,8 @@ export async function startServer(
|
|
|
415
421
|
cancel: (id, reason) => reminderRunner.cancel(id, reason),
|
|
416
422
|
cancelForContinuation: (token, reason) =>
|
|
417
423
|
reminderRunner.cancelForContinuation(token, reason),
|
|
424
|
+
rekeyContinuation: (channelId, oldToken, newToken) =>
|
|
425
|
+
reminderRunner.rekeyContinuation(channelId, oldToken, newToken),
|
|
418
426
|
dispatch: (id) => reminderRunner.dispatch(id),
|
|
419
427
|
});
|
|
420
428
|
engine.setEvals({
|
|
@@ -911,6 +919,10 @@ function mountAgentRoutes(
|
|
|
911
919
|
name: project.name,
|
|
912
920
|
dev,
|
|
913
921
|
basePath: base,
|
|
922
|
+
// Framework version, for SPA/engine skew observability now that the
|
|
923
|
+
// hosted playground shell is served centrally (backend deploys)
|
|
924
|
+
// while engines stay on their install-time version.
|
|
925
|
+
version: packageVersion(),
|
|
914
926
|
...(multi ? { indexPath: "/" } : {}),
|
|
915
927
|
}),
|
|
916
928
|
});
|