@cursor/july 0.1.95 → 0.1.96
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 +3 -4
- package/dist/channels/deployments/types.d.ts +1 -1
- package/dist/channels/deployments/types.d.ts.map +1 -1
- package/dist/channels/deployments/types.js +1 -0
- package/dist/channels/origin/api.d.ts +59 -13
- package/dist/channels/origin/api.d.ts.map +1 -1
- package/dist/channels/origin/api.js +168 -27
- package/dist/channels/origin/binding.d.ts +2 -2
- package/dist/channels/origin/checks.d.ts +6 -6
- package/dist/channels/origin/checks.js +6 -6
- package/dist/channels/origin/cursor-account.d.ts +5 -3
- package/dist/channels/origin/cursor-account.d.ts.map +1 -1
- package/dist/channels/origin/cursor-account.js +5 -3
- package/dist/channels/slack/api.d.ts +9 -0
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +36 -5
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +17 -46
- package/dist/channels/slack/dispatch.d.ts +7 -1
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +47 -23
- package/dist/channels/slack/inbound.d.ts +8 -0
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +32 -0
- package/dist/channels/slack/live-delivery.d.ts +26 -29
- package/dist/channels/slack/live-delivery.d.ts.map +1 -1
- package/dist/channels/slack/live-delivery.js +172 -73
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +5 -1
- package/dist/channels/slack/nudge-carrier.d.ts +2 -0
- package/dist/channels/slack/nudge-carrier.d.ts.map +1 -1
- package/dist/channels/slack/nudge-carrier.js +48 -1
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +2 -2
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.BBj0klBO.js → app.jXleCmOc.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DFoGuETX.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CUEXpL78.js → VPLocalSearchBox.8LhyglIT.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DabmQnia.js → theme.9ged-pr2.js} +2 -2
- package/dist/docs/assets/index.md.CI93Ltlb.js +5 -0
- package/dist/docs/assets/{index.md.BoHaMdeZ.lean.js → index.md.CI93Ltlb.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.BsMOxDvh.js → reference_cli.md.CCnG-nJx.js} +2 -1
- package/dist/docs/assets/templates_code-wiki.md.CDYZIBFN.js +2 -0
- package/dist/docs/assets/templates_code-wiki.md.CDYZIBFN.lean.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +4 -4
- package/dist/docs/evals.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +4 -4
- package/dist/docs/guides/convert-automation.html +4 -4
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +4 -4
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +6 -6
- package/dist/docs/index.md +3 -1
- package/dist/docs/llms-full.txt +102 -3
- package/dist/docs/llms.txt +1 -0
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +7 -6
- package/dist/docs/reference/cli.md +3 -2
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/agentic-owners.html +4 -4
- package/dist/docs/templates/code-wiki.html +28 -0
- package/dist/docs/templates/code-wiki.md +91 -0
- package/dist/docs/templates/demo.html +5 -5
- package/dist/docs/templates/pr-autofixer.html +4 -4
- package/dist/docs/templates/security-help.html +4 -4
- package/dist/docs/templates/security-reviewer.html +5 -5
- package/dist/docs/templates/triage.html +4 -4
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/files-backends/cursor-hosted.d.ts +26 -4
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +32 -4
- package/dist/internal/advertise-tools.d.ts +2 -0
- package/dist/internal/advertise-tools.d.ts.map +1 -1
- package/dist/internal/advertise-tools.js +15 -1
- package/dist/internal/bounded.d.ts +7 -0
- package/dist/internal/bounded.d.ts.map +1 -0
- package/dist/internal/bounded.js +32 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +7 -1
- package/dist/internal/conversation-mirror.d.ts +11 -8
- package/dist/internal/conversation-mirror.d.ts.map +1 -1
- package/dist/internal/conversation-mirror.js +116 -55
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +22 -48
- package/dist/internal/cursor/session-token.d.ts +45 -0
- package/dist/internal/cursor/session-token.d.ts.map +1 -0
- package/dist/internal/cursor/session-token.js +99 -0
- package/dist/internal/cursor-agent-template.d.ts +1 -1
- package/dist/internal/cursor-agent-template.d.ts.map +1 -1
- package/dist/internal/cursor-agent-template.js +1 -0
- package/dist/internal/framework-file-storage.d.ts +61 -0
- package/dist/internal/framework-file-storage.d.ts.map +1 -0
- package/dist/internal/framework-file-storage.js +204 -0
- package/dist/internal/framework-storage-selection.d.ts +11 -5
- package/dist/internal/framework-storage-selection.d.ts.map +1 -1
- package/dist/internal/framework-storage-selection.js +38 -11
- package/dist/internal/hosted-delivery.d.ts +7 -0
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +4 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +2 -0
- package/dist/internal/install-cursor-skills.d.ts.map +1 -1
- package/dist/internal/install-cursor-skills.js +1 -3
- package/dist/internal/managed-delivery.d.ts +80 -0
- package/dist/internal/managed-delivery.d.ts.map +1 -0
- package/dist/internal/managed-delivery.js +86 -0
- package/dist/internal/runtime-dispatch-runner.d.ts +5 -1
- package/dist/internal/runtime-dispatch-runner.d.ts.map +1 -1
- package/dist/internal/runtime-dispatch-runner.js +7 -0
- package/dist/internal/sdk-runner.d.ts +11 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +39 -8
- package/dist/internal/server.js +1 -0
- package/dist/internal/session-engine.d.ts +13 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +62 -3
- package/dist/internal/steering.d.ts +15 -0
- package/dist/internal/steering.d.ts.map +1 -0
- package/dist/internal/steering.js +18 -0
- package/dist/playground/assets/index-B1DDvBeC.css +1 -0
- package/dist/playground/assets/{index-CZA1uEWA.js → index-Cx9CclK_.js} +51 -51
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +8 -5
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/storage-backends/cursor-hosted.js +4 -3
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +3 -1
- package/docs/reference/cli.md +3 -2
- package/docs/templates/code-wiki.md +96 -0
- package/package.json +2 -2
- package/skills/ab/SKILL.md +13 -97
- package/skills/create-agent/SKILL.md +123 -223
- package/skills/debug/SKILL.md +34 -68
- package/skills/evals/SKILL.md +55 -180
- package/skills/framework-map/SKILL.md +83 -145
- package/skills/github/SKILL.md +53 -111
- package/skills/hillclimb/SKILL.md +40 -163
- package/skills/mcp-auth/SKILL.md +48 -107
- package/skills/otel/SKILL.md +22 -68
- package/skills/setup-slack/SKILL.md +77 -208
- package/src/channels/deployments/types.ts +1 -0
- package/src/channels/origin/api.ts +242 -30
- package/src/channels/origin/binding.ts +2 -2
- package/src/channels/origin/checks.ts +6 -6
- package/src/channels/origin/cursor-account.ts +5 -3
- package/src/channels/slack/api.ts +41 -5
- package/src/channels/slack/defaults.ts +15 -49
- package/src/channels/slack/dispatch.ts +56 -22
- package/src/channels/slack/inbound.ts +43 -0
- package/src/channels/slack/live-delivery.ts +235 -114
- package/src/channels/slack/manifest.ts +5 -1
- package/src/channels/slack/nudge-carrier.ts +48 -1
- package/src/channels/slack/slack-channel.ts +2 -0
- package/src/files-backends/cursor-hosted.ts +40 -7
- package/src/internal/advertise-tools.ts +18 -2
- package/src/internal/bounded.ts +28 -0
- package/src/internal/cli-deploy.ts +12 -0
- package/src/internal/conversation-mirror.ts +145 -69
- package/src/internal/cursor/backend-client.ts +34 -58
- package/src/internal/cursor/session-token.ts +115 -0
- package/src/internal/cursor-agent-template.ts +1 -0
- package/src/internal/framework-file-storage.ts +222 -0
- package/src/internal/framework-storage-selection.ts +51 -11
- package/src/internal/hosted-delivery.ts +10 -0
- package/src/internal/init-project.ts +2 -0
- package/src/internal/install-cursor-skills.ts +1 -4
- package/src/internal/managed-delivery.ts +125 -0
- package/src/internal/runtime-dispatch-runner.ts +10 -0
- package/src/internal/sdk-runner.ts +56 -8
- package/src/internal/server.ts +2 -0
- package/src/internal/session-engine.ts +64 -4
- package/src/internal/steering.ts +17 -0
- package/src/storage-backends/cursor-hosted.ts +9 -7
- package/src/types.ts +2 -1
- package/templates/code-wiki/README.md +34 -0
- package/templates/code-wiki/agent/agent.ts +8 -0
- package/templates/code-wiki/agent/channels/github.ts +144 -0
- package/templates/code-wiki/agent/instructions.md +39 -0
- package/templates/code-wiki/agent/lib/repos.ts +5 -0
- package/templates/code-wiki/agent/lib/wiki.ts +22 -0
- package/templates/code-wiki/agent/tools/apply_wiki.ts +219 -0
- package/templates/code-wiki/evals/evals.config.ts +5 -0
- package/templates/code-wiki/evals/wiki.eval.ts +132 -0
- package/templates/code-wiki/init.json +25 -0
- package/templates/code-wiki/package.json +18 -0
- package/templates/code-wiki/tsconfig.json +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.oqPawjiD.js +0 -1
- package/dist/docs/assets/index.md.BoHaMdeZ.js +0 -5
- package/dist/internal/deployment-scm-event-stream.d.ts +0 -116
- package/dist/internal/deployment-scm-event-stream.d.ts.map +0 -1
- package/dist/internal/deployment-scm-event-stream.js +0 -474
- package/dist/playground/assets/index-CF7hUDnQ.css +0 -1
- package/src/internal/deployment-scm-event-stream.ts +0 -593
- /package/dist/docs/assets/{reference_cli.md.BsMOxDvh.lean.js → reference_cli.md.CCnG-nJx.lean.js} +0 -0
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
* the durable Slack nudge store at tool boundaries (MCP / builtin tools).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
9
|
export const SLACK_NUDGE_ROOT_REL = ".agent-serve/slack-nudge-root";
|
|
10
10
|
export const SLACK_NUDGE_CONTINUATION_REL =
|
|
11
11
|
".agent-serve/slack-nudge-continuation";
|
|
12
12
|
export const SLACK_NUDGE_CARRIER_REL = ".agent-serve/slack-nudge-carrier.mjs";
|
|
13
|
+
/** Conversation allowed to claim nudges; subagents share the cwd and would otherwise take the parent's. */
|
|
14
|
+
export const SLACK_NUDGE_OWNER_REL = ".agent-serve/slack-nudge-owner";
|
|
13
15
|
|
|
14
16
|
/** Carrier script body — reads nudge-root + continuation, drains pending. */
|
|
15
17
|
export const SLACK_NUDGE_CARRIER_SCRIPT: string = `#!/usr/bin/env node
|
|
@@ -40,7 +42,46 @@ async function readText(rel) {
|
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
async function readStdin() {
|
|
46
|
+
const chunks = [];
|
|
47
|
+
const read = (async () => {
|
|
48
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
49
|
+
})().catch(() => {});
|
|
50
|
+
await Promise.race([
|
|
51
|
+
read,
|
|
52
|
+
new Promise((resolve) => setTimeout(resolve, 250).unref?.()),
|
|
53
|
+
]);
|
|
54
|
+
process.stdin.destroy();
|
|
55
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** First caller pins ownership; a subagent cannot call a tool before its parent's Task call. */
|
|
59
|
+
async function ownsStore(caller) {
|
|
60
|
+
if (caller === "") return true;
|
|
61
|
+
const ownerPath = join(workspace, ${JSON.stringify(SLACK_NUDGE_OWNER_REL)});
|
|
62
|
+
try {
|
|
63
|
+
// wx is O_CREAT|O_EXCL, so creating the file is the claim: concurrent hooks cannot both win.
|
|
64
|
+
await writeFile(ownerPath, caller + "\\n", { encoding: "utf8", flag: "wx" });
|
|
65
|
+
return true;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error && error.code !== "EEXIST") return false;
|
|
68
|
+
}
|
|
69
|
+
// Claimed already — the only question left is whether we are the claimant.
|
|
70
|
+
const owner = await readText(${JSON.stringify(SLACK_NUDGE_OWNER_REL)});
|
|
71
|
+
return owner === "" || owner === caller;
|
|
72
|
+
}
|
|
73
|
+
|
|
43
74
|
async function main() {
|
|
75
|
+
const raw = await readStdin();
|
|
76
|
+
let caller = "";
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(raw);
|
|
79
|
+
if (parsed && typeof parsed.conversation_id === "string") {
|
|
80
|
+
caller = parsed.conversation_id.trim();
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// Fail open: an unparseable payload must not strand the follow-up.
|
|
84
|
+
}
|
|
44
85
|
const root = await readText(${JSON.stringify(SLACK_NUDGE_ROOT_REL)});
|
|
45
86
|
const continuation = await readText(
|
|
46
87
|
${JSON.stringify(SLACK_NUDGE_CONTINUATION_REL)}
|
|
@@ -49,6 +90,11 @@ async function main() {
|
|
|
49
90
|
process.stdout.write("{}\\n");
|
|
50
91
|
return;
|
|
51
92
|
}
|
|
93
|
+
// Not ours: leave it pending for the parent or the turn-boundary drain.
|
|
94
|
+
if (!(await ownsStore(caller))) {
|
|
95
|
+
process.stdout.write("{}\\n");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
52
98
|
const pendingDir = join(root, safeKey(continuation), "pending");
|
|
53
99
|
const deliveredDir = join(root, safeKey(continuation), "delivered");
|
|
54
100
|
let names = [];
|
|
@@ -157,6 +203,7 @@ export async function materializeSlackNudgeCarrier(args: {
|
|
|
157
203
|
SLACK_NUDGE_CARRIER_SCRIPT,
|
|
158
204
|
"utf8"
|
|
159
205
|
);
|
|
206
|
+
await rm(join(args.harnessCwd, SLACK_NUDGE_OWNER_REL), { force: true });
|
|
160
207
|
|
|
161
208
|
const hooksPath = join(args.harnessCwd, ".cursor", "hooks.json");
|
|
162
209
|
let existing: HooksConfig = { version: 1, hooks: {} };
|
|
@@ -238,6 +238,8 @@ export function slackChannel(
|
|
|
238
238
|
: { interruptContinuation: args.interruptContinuation }),
|
|
239
239
|
...(channelWatch === undefined ? {} : { channelWatch }),
|
|
240
240
|
...(botMentionGate === undefined ? {} : { botMentionGate }),
|
|
241
|
+
suggestedPrompts: options.suggestedPrompts,
|
|
242
|
+
suggestedPromptsTitle: options.suggestedPromptsTitle,
|
|
241
243
|
};
|
|
242
244
|
}
|
|
243
245
|
|
|
@@ -71,6 +71,19 @@ const AGENT_SERVE_FILES_PATH = "/v0/agent-serve-files";
|
|
|
71
71
|
export const AGENT_SERVE_STORE_SOURCE_ID_ENV_VAR =
|
|
72
72
|
"AGENT_SERVE_STORE_SOURCE_ID";
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The deployment's **framework-level** file store, distinct from the
|
|
76
|
+
* user-level store above. The user store carries author-facing bytes
|
|
77
|
+
* (`ctx.host.files`, skills, memory, artifacts) and is browsable and
|
|
78
|
+
* mountable; the framework store carries engine-owned session state —
|
|
79
|
+
* session records, event transcripts, continuation index, and the SDK
|
|
80
|
+
* conversation mirror — and is never exposed to authors or models.
|
|
81
|
+
* Provisioned by the v2 deploy pipeline alongside the user store; v1
|
|
82
|
+
* deployments have only the user store.
|
|
83
|
+
*/
|
|
84
|
+
export const AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID_ENV_VAR =
|
|
85
|
+
"AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID";
|
|
86
|
+
|
|
74
87
|
/** Cap on the per-process etag cache; a miss costs a lookup, not correctness. */
|
|
75
88
|
const MAX_REMEMBERED_ETAGS = 1_000;
|
|
76
89
|
|
|
@@ -101,6 +114,20 @@ export function isCursorHostedFilesAvailable(
|
|
|
101
114
|
return sourceId !== undefined && sourceId !== "";
|
|
102
115
|
}
|
|
103
116
|
|
|
117
|
+
/**
|
|
118
|
+
* The framework-level store's source id, or `undefined` when the deployment
|
|
119
|
+
* has none (v1, self-hosted, or a v2 deploy predating framework-store
|
|
120
|
+
* provisioning). Same detection posture as
|
|
121
|
+
* {@link isCursorHostedFilesAvailable}: keyed on the id alone — a missing
|
|
122
|
+
* credential must fail loudly at use, not silently deselect the store.
|
|
123
|
+
*/
|
|
124
|
+
export function cursorFrameworkStoreSourceId(
|
|
125
|
+
env: NodeJS.ProcessEnv = process.env
|
|
126
|
+
): string | undefined {
|
|
127
|
+
const sourceId = env[AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID_ENV_VAR]?.trim();
|
|
128
|
+
return sourceId === undefined || sourceId === "" ? undefined : sourceId;
|
|
129
|
+
}
|
|
130
|
+
|
|
104
131
|
/**
|
|
105
132
|
* Local `serve`/`run` with a personal `CURSOR_API_KEY` can address
|
|
106
133
|
* `/v0/agent-serve-files/user`. Hosted deployments have a store source id
|
|
@@ -125,12 +152,15 @@ export function isCursorUserStoreFilesAvailable(
|
|
|
125
152
|
export interface CursorHostedFilesOptions {
|
|
126
153
|
/** Control-plane base URL (default: `CURSOR_API_BASE_URL`, else api.cursor.com). */
|
|
127
154
|
baseUrl?: string;
|
|
155
|
+
/** Bearer credential (default: `CURSOR_API_KEY`). */
|
|
156
|
+
apiKey?: string;
|
|
128
157
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* file after serve start.
|
|
158
|
+
* Lazy credential, re-resolved on every call and preferred over
|
|
159
|
+
* {@link apiKey} — hosted freeze snapshots listen unsigned and bind the
|
|
160
|
+
* key file after serve start. Kept separate so `apiKey` stays a plain
|
|
161
|
+
* string.
|
|
132
162
|
*/
|
|
133
|
-
|
|
163
|
+
resolveApiKey?: () => string | undefined;
|
|
134
164
|
/** File store to address (default: `AGENT_SERVE_STORE_SOURCE_ID`). */
|
|
135
165
|
storeSourceId?: string;
|
|
136
166
|
/** Test seam; defaults to global fetch. */
|
|
@@ -253,7 +283,11 @@ export function cursorHostedFiles(
|
|
|
253
283
|
}
|
|
254
284
|
out.push(toEntry(file, file.path));
|
|
255
285
|
}
|
|
256
|
-
|
|
286
|
+
// The FileSink contract promises key order, and transcript-chunk
|
|
287
|
+
// consumers depend on it (chunk keys are zero-padded so key order
|
|
288
|
+
// is append order). The server happens to scan in order today; do
|
|
289
|
+
// not let that be the only thing holding the contract.
|
|
290
|
+
return out.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
257
291
|
}
|
|
258
292
|
// The unscoped listing was truncated (`complete: false`) and has no
|
|
259
293
|
// continuation to follow. `list()` promises the whole scope, so rather
|
|
@@ -626,8 +660,7 @@ function resolveStoreSourceId(options: CursorHostedFilesOptions): string {
|
|
|
626
660
|
}
|
|
627
661
|
|
|
628
662
|
function resolveApiKey(options: CursorHostedFilesOptions): string {
|
|
629
|
-
const configured =
|
|
630
|
-
typeof options.apiKey === "function" ? options.apiKey() : options.apiKey;
|
|
663
|
+
const configured = options.resolveApiKey?.() ?? options.apiKey;
|
|
631
664
|
const key = (configured ?? process.env.CURSOR_API_KEY)?.trim();
|
|
632
665
|
if (key === undefined || key === "") {
|
|
633
666
|
throw new Error(
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
* `defineConnection({ effects })` filters the listing before names are
|
|
28
28
|
* assigned: only tools whose MCP annotations classify as an intended
|
|
29
29
|
* effect are advertised. Unclassified tools are omitted, not guessed.
|
|
30
|
+
* Tools whose `annotations.title` is `do-not-call` are dropped as well
|
|
31
|
+
* (vendor host-UI / MCP Apps helpers that still carry `readOnlyHint`).
|
|
30
32
|
*/
|
|
31
33
|
|
|
32
34
|
import type {
|
|
@@ -134,8 +136,10 @@ export async function resolveAdvertisedTools(args: {
|
|
|
134
136
|
{ cause: error }
|
|
135
137
|
);
|
|
136
138
|
}
|
|
137
|
-
const intended = listed.filter(
|
|
138
|
-
|
|
139
|
+
const intended = listed.filter(
|
|
140
|
+
(info) =>
|
|
141
|
+
!isDoNotCallTool(info.annotations) &&
|
|
142
|
+
matchesIntendedEffects(info.annotations, connection.effects)
|
|
139
143
|
);
|
|
140
144
|
const exposed = exposeListedTools(intended, claimed);
|
|
141
145
|
for (const { info, exposedName } of exposed) {
|
|
@@ -232,6 +236,18 @@ function exposeListedTools(
|
|
|
232
236
|
}));
|
|
233
237
|
}
|
|
234
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Vendors (Datadog MCP Apps) stamp host-UI-only helpers with this title.
|
|
241
|
+
* They still look like reads (`readOnlyHint: true`), so the effects
|
|
242
|
+
* filter would keep them. The model and agsh should not see them; the
|
|
243
|
+
* raw MCP listing stays intact for the host.
|
|
244
|
+
*/
|
|
245
|
+
const DO_NOT_CALL_TITLE = "do-not-call";
|
|
246
|
+
|
|
247
|
+
function isDoNotCallTool(annotations: McpToolAnnotations | undefined): boolean {
|
|
248
|
+
return annotations?.title?.trim() === DO_NOT_CALL_TITLE;
|
|
249
|
+
}
|
|
250
|
+
|
|
235
251
|
function matchesIntendedEffects(
|
|
236
252
|
annotations: McpToolAnnotations | undefined,
|
|
237
253
|
effects: readonly ToolEffect[] | undefined
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded-concurrency map, in input order. Local rather than
|
|
3
|
+
* `@anysphere/utils`: this published package cannot depend on monorepo
|
|
4
|
+
* internals.
|
|
5
|
+
*/
|
|
6
|
+
export async function mapBounded<T, R>(
|
|
7
|
+
items: readonly T[],
|
|
8
|
+
max: number,
|
|
9
|
+
run: (item: T) => Promise<R>
|
|
10
|
+
): Promise<R[]> {
|
|
11
|
+
const out = new Array<R>(items.length);
|
|
12
|
+
let nextIndex = 0;
|
|
13
|
+
const workers = Array.from(
|
|
14
|
+
{ length: Math.min(max, items.length) },
|
|
15
|
+
async () => {
|
|
16
|
+
for (;;) {
|
|
17
|
+
const index = nextIndex;
|
|
18
|
+
nextIndex += 1;
|
|
19
|
+
if (index >= items.length) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
out[index] = await run(items[index] as T);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
await Promise.all(workers);
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
@@ -49,6 +49,8 @@ import {
|
|
|
49
49
|
import { type GitRunner, inferDeployGitSource } from "./deploy-source.js";
|
|
50
50
|
import {
|
|
51
51
|
type ChildAgentDir,
|
|
52
|
+
formatDiagnostics,
|
|
53
|
+
hasErrorDiagnostics,
|
|
52
54
|
isAgentProjectDir,
|
|
53
55
|
listChildAgentDirs,
|
|
54
56
|
loadAgentProject,
|
|
@@ -1135,6 +1137,16 @@ interface DeployManifest {
|
|
|
1135
1137
|
*/
|
|
1136
1138
|
async function readDeployManifest(dir: string): Promise<DeployManifest> {
|
|
1137
1139
|
const project = await loadAgentProject(dir);
|
|
1140
|
+
// Fail here, before anything is dispatched: a project with error
|
|
1141
|
+
// diagnostics (e.g. agent/storage.ts on architecture v2) would only be
|
|
1142
|
+
// refused later by `serve` inside the hosted build, wasting a bake.
|
|
1143
|
+
if (hasErrorDiagnostics(project)) {
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
`Refusing to deploy ${project.name} — it has errors:\n${formatDiagnostics(
|
|
1146
|
+
project.diagnostics.filter((d) => d.severity === "error")
|
|
1147
|
+
)}`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1138
1150
|
return {
|
|
1139
1151
|
hosting: project.agent.hosting,
|
|
1140
1152
|
architecture: project.agent.architecture,
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
* This module mirrors the conversation through the SDK's own store APIs —
|
|
13
13
|
* the agent document (which carries `sdkMetadata.blobEncryptionKey`, without
|
|
14
14
|
* which the checkpoint blobs cannot be decrypted) plus its content-addressed
|
|
15
|
-
* checkpoint blobs — into the deployment's
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* checkpoint blobs — into the deployment's **framework-level** file store
|
|
16
|
+
* (`AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID`), alongside session records and
|
|
17
|
+
* transcripts and apart from the author-facing user store. The store is an
|
|
18
|
+
* Agent Store reached over `/v0/agent-serve-files` presigned URLs — **not
|
|
19
|
+
* mounted** as a filesystem in the engine VM (the FUSE mount at
|
|
20
|
+
* {@link DEPLOYMENT_STORE_MOUNT_PATH} exists only on cloud-runtime VMs) —
|
|
21
|
+
* so the mirror moves bytes through the {@link FileSink} contract instead.
|
|
21
22
|
*
|
|
22
23
|
* Going through the store APIs rather than copying `store.db` files sidesteps
|
|
23
24
|
* WAL consistency and the shared `index.db` (per-session raw copies would
|
|
@@ -38,10 +39,11 @@
|
|
|
38
39
|
import type { LocalAgentDocument, LocalAgentStore } from "@cursor/sdk";
|
|
39
40
|
import { agentStoreKeys, type FileSink } from "../files.js";
|
|
40
41
|
import {
|
|
42
|
+
cursorFrameworkStoreSourceId,
|
|
41
43
|
cursorHostedFiles,
|
|
42
|
-
isCursorHostedFilesAvailable,
|
|
43
44
|
} from "../files-backends/cursor-hosted.js";
|
|
44
45
|
import type { AgentProject } from "../types.js";
|
|
46
|
+
import { mapBounded } from "./bounded.js";
|
|
45
47
|
import { resolveApiKeySync } from "./cursor/credentials.js";
|
|
46
48
|
import { AGENT_SERVE_GENERATION_ENV_VAR } from "./cursor-relay-core.js";
|
|
47
49
|
import { describeError } from "./describe-error.js";
|
|
@@ -91,21 +93,44 @@ export function createConversationMirror(
|
|
|
91
93
|
): ConversationMirror {
|
|
92
94
|
const { sink, agentName } = options;
|
|
93
95
|
const logger = options.logger ?? (() => {});
|
|
94
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* Blob ids known to be in the sink, per SDK agent id (upload dedupe).
|
|
98
|
+
* Bounded oldest-out like the files backend's etag cache: a resident
|
|
99
|
+
* engine touches an unbounded number of conversations over its lifetime,
|
|
100
|
+
* and a miss costs one manifest read, never correctness.
|
|
101
|
+
*/
|
|
95
102
|
const mirrored = new Map<string, Set<string>>();
|
|
103
|
+
const rememberMirrored = (sdkAgentId: string, blobIds: Set<string>): void => {
|
|
104
|
+
if (
|
|
105
|
+
!mirrored.has(sdkAgentId) &&
|
|
106
|
+
mirrored.size >= MAX_TRACKED_CONVERSATIONS
|
|
107
|
+
) {
|
|
108
|
+
const oldest = mirrored.keys().next();
|
|
109
|
+
if (!oldest.done) {
|
|
110
|
+
mirrored.delete(oldest.value);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
mirrored.set(sdkAgentId, blobIds);
|
|
114
|
+
};
|
|
96
115
|
/**
|
|
97
116
|
* Per-conversation serialization so a persist cannot interleave with a
|
|
98
|
-
* hydrate (or another persist) for the same conversation.
|
|
117
|
+
* hydrate (or another persist) for the same conversation. Entries are
|
|
118
|
+
* removed once their chain settles as the tail, so the map tracks only
|
|
119
|
+
* conversations with work in flight.
|
|
99
120
|
*/
|
|
100
121
|
const chains = new Map<string, Promise<unknown>>();
|
|
101
122
|
|
|
102
123
|
function serialize<T>(sdkAgentId: string, run: () => Promise<T>): Promise<T> {
|
|
103
124
|
const previous = chains.get(sdkAgentId) ?? Promise.resolve();
|
|
104
125
|
const next = previous.then(run, run);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
const stored = next
|
|
127
|
+
.catch(() => {})
|
|
128
|
+
.then(() => {
|
|
129
|
+
if (chains.get(sdkAgentId) === stored) {
|
|
130
|
+
chains.delete(sdkAgentId);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
chains.set(sdkAgentId, stored);
|
|
109
134
|
return next;
|
|
110
135
|
}
|
|
111
136
|
|
|
@@ -115,20 +140,22 @@ export function createConversationMirror(
|
|
|
115
140
|
return cached;
|
|
116
141
|
}
|
|
117
142
|
// Seed from the previous manifest so a fresh pod does not re-upload the
|
|
118
|
-
// whole history on its first persist. A miss
|
|
119
|
-
// empty — re-uploading is wasteful, never wrong.
|
|
143
|
+
// whole history on its first persist. A miss, an unusable manifest, or
|
|
144
|
+
// a failed read seeds empty — re-uploading is wasteful, never wrong.
|
|
120
145
|
const seeded = new Set<string>();
|
|
121
146
|
try {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
147
|
+
const read = await readManifest(sink, agentName, sdkAgentId);
|
|
148
|
+
if (read.outcome === "found") {
|
|
149
|
+
for (const blobId of read.manifest.blobIds) {
|
|
150
|
+
seeded.add(blobId);
|
|
151
|
+
}
|
|
125
152
|
}
|
|
126
153
|
} catch (error) {
|
|
127
154
|
logger(
|
|
128
155
|
`[agent-sdk] conversation mirror: could not read the prior manifest for ${sdkAgentId} (${describeError(error)}); re-uploading blobs`
|
|
129
156
|
);
|
|
130
157
|
}
|
|
131
|
-
|
|
158
|
+
rememberMirrored(sdkAgentId, seeded);
|
|
132
159
|
return seeded;
|
|
133
160
|
}
|
|
134
161
|
|
|
@@ -182,57 +209,77 @@ export function createConversationMirror(
|
|
|
182
209
|
if (existing !== null) {
|
|
183
210
|
return false;
|
|
184
211
|
}
|
|
185
|
-
const
|
|
186
|
-
if (
|
|
212
|
+
const read = await readManifest(sink, agentName, sdkAgentId);
|
|
213
|
+
if (read.outcome === "miss") {
|
|
187
214
|
return false;
|
|
188
215
|
}
|
|
216
|
+
if (read.outcome === "unusable") {
|
|
217
|
+
// Definitive, non-transient state (a future manifest version
|
|
218
|
+
// during a rolling redeploy, or corruption): retrying would fail
|
|
219
|
+
// identically forever, so fall through to the caller's
|
|
220
|
+
// fresh-conversation path instead of wedging the session.
|
|
221
|
+
logger(
|
|
222
|
+
`[agent-sdk] conversation mirror: the manifest for ${sdkAgentId} is unusable (${read.reason}); treating the conversation as unrestorable`
|
|
223
|
+
);
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
const manifest = read.manifest;
|
|
189
227
|
const rootBlobId = manifest.agent.latestCheckpoint?.rootBlobId;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
228
|
+
if (
|
|
229
|
+
rootBlobId === undefined ||
|
|
230
|
+
!manifest.blobIds.includes(rootBlobId)
|
|
231
|
+
) {
|
|
232
|
+
logger(
|
|
233
|
+
`[agent-sdk] conversation mirror: the manifest for ${sdkAgentId} does not list its root checkpoint blob; treating the conversation as unrestorable`
|
|
194
234
|
);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
// Bounded-parallel downloads: this sits on the resume critical path
|
|
238
|
+
// (a presign plus an object GET per blob against the hosted sink).
|
|
239
|
+
const bodies = await mapBounded(
|
|
240
|
+
manifest.blobIds,
|
|
241
|
+
HYDRATE_DOWNLOAD_CONCURRENCY,
|
|
242
|
+
async (blobId) => ({
|
|
243
|
+
blobId,
|
|
244
|
+
body: await sink.get(
|
|
245
|
+
agentStoreKeys.conversationBlob(agentName, sdkAgentId, blobId)
|
|
246
|
+
),
|
|
247
|
+
})
|
|
248
|
+
);
|
|
249
|
+
const missing = bodies.filter((entry) => entry.body === undefined);
|
|
250
|
+
if (missing.length > 0) {
|
|
251
|
+
// The manifest lists exactly what a previous persist uploaded, so
|
|
252
|
+
// any definitive absence is store loss — and the checkpoint graph
|
|
253
|
+
// may reach any listed blob, not just the root. Restoring a
|
|
254
|
+
// partial graph would create an agent row whose resume succeeds
|
|
255
|
+
// and whose checkpoint walk then fails unrecoverably.
|
|
256
|
+
logger(
|
|
257
|
+
`[agent-sdk] conversation mirror: ${missing.length} checkpoint blob(s) for ${sdkAgentId} are missing from the sink; treating the conversation as unrestorable`
|
|
258
|
+
);
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
const restored = new Set<string>();
|
|
262
|
+
for (const entry of bodies) {
|
|
263
|
+
const body = entry.body as NonNullable<typeof entry.body>;
|
|
264
|
+
const bytes = body instanceof Uint8Array ? body : body.body;
|
|
214
265
|
if (
|
|
215
|
-
(await store.checkpoints.get({
|
|
216
|
-
|
|
266
|
+
(await store.checkpoints.get({
|
|
267
|
+
agentId: sdkAgentId,
|
|
268
|
+
blobId: entry.blobId,
|
|
269
|
+
})) === null
|
|
217
270
|
) {
|
|
218
271
|
await store.checkpoints.create({
|
|
219
272
|
agentId: sdkAgentId,
|
|
220
|
-
blobId,
|
|
273
|
+
blobId: entry.blobId,
|
|
221
274
|
data: bytes,
|
|
222
275
|
});
|
|
223
276
|
}
|
|
224
|
-
restored.add(blobId);
|
|
225
|
-
}
|
|
226
|
-
if (rootBlobId !== undefined && !restored.has(rootBlobId)) {
|
|
227
|
-
logger(
|
|
228
|
-
`[agent-sdk] conversation mirror: the manifest for ${sdkAgentId} does not list its root checkpoint blob; treating the conversation as unrestorable`
|
|
229
|
-
);
|
|
230
|
-
return false;
|
|
277
|
+
restored.add(entry.blobId);
|
|
231
278
|
}
|
|
232
279
|
await store.agents.create({
|
|
233
280
|
agent: { ...manifest.agent, activeRunId: null },
|
|
234
281
|
});
|
|
235
|
-
|
|
282
|
+
rememberMirrored(sdkAgentId, restored);
|
|
236
283
|
logger(
|
|
237
284
|
`[agent-sdk] conversation mirror: restored the conversation for ${sdkAgentId} (${restored.size} checkpoint blob(s))`
|
|
238
285
|
);
|
|
@@ -242,27 +289,52 @@ export function createConversationMirror(
|
|
|
242
289
|
};
|
|
243
290
|
}
|
|
244
291
|
|
|
292
|
+
/** Oldest-out cap on the per-conversation upload-dedupe cache. */
|
|
293
|
+
const MAX_TRACKED_CONVERSATIONS = 1_000;
|
|
294
|
+
|
|
295
|
+
/** Parallel blob downloads during hydrate (resume critical path). */
|
|
296
|
+
const HYDRATE_DOWNLOAD_CONCURRENCY = 8;
|
|
297
|
+
|
|
298
|
+
type ManifestRead =
|
|
299
|
+
/** No manifest was ever persisted (definitive miss). */
|
|
300
|
+
| { outcome: "miss" }
|
|
301
|
+
/**
|
|
302
|
+
* A manifest exists but cannot be used (future version, corruption,
|
|
303
|
+
* wrong agent). Definitive and non-transient — callers must not retry,
|
|
304
|
+
* and must not treat it as a sink outage.
|
|
305
|
+
*/
|
|
306
|
+
| { outcome: "unusable"; reason: string }
|
|
307
|
+
| { outcome: "found"; manifest: ConversationManifestV1 };
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Read the persisted manifest. Sink transport failures propagate (an
|
|
311
|
+
* outage must fail the follow-up, not fork the thread); everything the
|
|
312
|
+
* bytes themselves can be wrong about is reported as `unusable`.
|
|
313
|
+
*/
|
|
245
314
|
async function readManifest(
|
|
246
315
|
sink: FileSink,
|
|
247
316
|
agentName: string,
|
|
248
317
|
sdkAgentId: string
|
|
249
|
-
): Promise<
|
|
318
|
+
): Promise<ManifestRead> {
|
|
250
319
|
const body = await sink.get(
|
|
251
320
|
agentStoreKeys.conversationManifest(agentName, sdkAgentId)
|
|
252
321
|
);
|
|
253
322
|
if (body === undefined) {
|
|
254
|
-
return
|
|
323
|
+
return { outcome: "miss" };
|
|
255
324
|
}
|
|
256
325
|
const bytes = body instanceof Uint8Array ? body : body.body;
|
|
257
|
-
|
|
326
|
+
let parsed: unknown;
|
|
327
|
+
try {
|
|
328
|
+
parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
329
|
+
} catch {
|
|
330
|
+
return { outcome: "unusable", reason: "not JSON" };
|
|
331
|
+
}
|
|
258
332
|
if (
|
|
259
333
|
parsed === null ||
|
|
260
334
|
typeof parsed !== "object" ||
|
|
261
335
|
(parsed as { v?: unknown }).v !== 1
|
|
262
336
|
) {
|
|
263
|
-
|
|
264
|
-
`conversation mirror: unsupported manifest for ${sdkAgentId}`
|
|
265
|
-
);
|
|
337
|
+
return { outcome: "unusable", reason: "unsupported manifest version" };
|
|
266
338
|
}
|
|
267
339
|
const manifest = parsed as ConversationManifestV1;
|
|
268
340
|
if (
|
|
@@ -271,11 +343,9 @@ async function readManifest(
|
|
|
271
343
|
manifest.agent.agentId !== sdkAgentId ||
|
|
272
344
|
!Array.isArray(manifest.blobIds)
|
|
273
345
|
) {
|
|
274
|
-
|
|
275
|
-
`conversation mirror: malformed manifest for ${sdkAgentId}`
|
|
276
|
-
);
|
|
346
|
+
return { outcome: "unusable", reason: "malformed manifest" };
|
|
277
347
|
}
|
|
278
|
-
return manifest;
|
|
348
|
+
return { outcome: "found", manifest };
|
|
279
349
|
}
|
|
280
350
|
|
|
281
351
|
async function listCheckpointBlobIds(
|
|
@@ -299,8 +369,10 @@ async function listCheckpointBlobIds(
|
|
|
299
369
|
*
|
|
300
370
|
* Selected for architecture-v2 agents on Cursor-managed hosting — the same
|
|
301
371
|
* generation signal as framework-storage-selection.ts, plus a provisioned
|
|
302
|
-
*
|
|
303
|
-
*
|
|
372
|
+
* **framework-level** file store: conversation state is engine-owned, so it
|
|
373
|
+
* lives with session records and transcripts, never on the author-facing
|
|
374
|
+
* user store. Self-hosted v2 keeps everything on the state root (the
|
|
375
|
+
* operator owns the disk), and v1 keeps today's behavior while it runs.
|
|
304
376
|
*/
|
|
305
377
|
export function selectConversationMirror(input: {
|
|
306
378
|
project: AgentProject;
|
|
@@ -311,18 +383,22 @@ export function selectConversationMirror(input: {
|
|
|
311
383
|
return undefined;
|
|
312
384
|
}
|
|
313
385
|
const env = input.env ?? process.env;
|
|
386
|
+
const frameworkStoreId = cursorFrameworkStoreSourceId(
|
|
387
|
+
env as NodeJS.ProcessEnv
|
|
388
|
+
);
|
|
314
389
|
if (
|
|
315
390
|
env[AGENT_SERVE_GENERATION_ENV_VAR] === undefined ||
|
|
316
|
-
|
|
391
|
+
frameworkStoreId === undefined
|
|
317
392
|
) {
|
|
318
393
|
return undefined;
|
|
319
394
|
}
|
|
320
395
|
return createConversationMirror({
|
|
321
396
|
sink: cursorHostedFiles({
|
|
397
|
+
storeSourceId: frameworkStoreId,
|
|
322
398
|
// Full chain (env → key file → stored login): frozen engines listen
|
|
323
399
|
// unsigned and bind CURSOR_API_KEY_FILE at task time, after this sink
|
|
324
400
|
// is constructed.
|
|
325
|
-
|
|
401
|
+
resolveApiKey: () => resolveApiKeySync()?.apiKey,
|
|
326
402
|
}),
|
|
327
403
|
agentName: input.project.name,
|
|
328
404
|
logger: input.logger,
|