@cursor/july 0.1.94 → 0.1.95
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/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +14 -2
- package/dist/channels/github/types.d.ts +18 -3
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +11 -3
- package/dist/channels/origin/origin-webhook.d.ts +11 -1
- package/dist/channels/origin/origin-webhook.d.ts.map +1 -1
- package/dist/channels/origin/origin-webhook.js +23 -3
- package/dist/channels/origin/types.d.ts +8 -0
- package/dist/channels/origin/types.d.ts.map +1 -1
- package/dist/channels/slack/pr-ref.d.ts +7 -1
- package/dist/channels/slack/pr-ref.d.ts.map +1 -1
- package/dist/channels/slack/pr-ref.js +42 -23
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.CFDEas4I.js → app.BBj0klBO.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.oqPawjiD.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.B1IIYpYS.js → VPLocalSearchBox.CUEXpL78.js} +1 -1
- package/dist/docs/assets/chunks/{theme.Ct4NSiLm.js → theme.DabmQnia.js} +2 -2
- package/dist/docs/assets/index.md.BoHaMdeZ.js +5 -0
- package/dist/docs/assets/{index.md.DRakGHFe.lean.js → index.md.BoHaMdeZ.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.Cw6_ICYG.js → reference_cli.md.BsMOxDvh.js} +3 -2
- package/dist/docs/assets/{reference_cli.md.Cw6_ICYG.lean.js → reference_cli.md.BsMOxDvh.lean.js} +1 -1
- package/dist/docs/assets/{reference_skills.md.8son6Hjm.js → reference_skills.md.BFW9retM.js} +3 -3
- package/dist/docs/assets/{templates_agentic-owners.md.DqtPdm6f.js → templates_agentic-owners.md.BZSH4N9z.js} +1 -1
- package/dist/docs/assets/{templates_demo.md.DhFcWN6j.js → templates_demo.md.BeQX9V3H.js} +1 -1
- package/dist/docs/assets/{templates_pr-autofixer.md.DU7dQpor.js → templates_pr-autofixer.md.x5zl6-GT.js} +2 -2
- package/dist/docs/assets/{templates_pr-autofixer.md.DU7dQpor.lean.js → templates_pr-autofixer.md.x5zl6-GT.lean.js} +1 -1
- package/dist/docs/assets/templates_security-help.md.C3Ny_Qr2.js +4 -0
- package/dist/docs/assets/templates_security-help.md.C3Ny_Qr2.lean.js +1 -0
- package/dist/docs/assets/{templates_security-reviewer.md.CTa7u_l1.js → templates_security-reviewer.md.lshxbCLK.js} +2 -2
- package/dist/docs/assets/{templates_security-reviewer.md.CTa7u_l1.lean.js → templates_security-reviewer.md.lshxbCLK.lean.js} +1 -1
- package/dist/docs/assets/{templates_triage.md.CVlpctKS.js → templates_triage.md.Co4UNzkZ.js} +3 -3
- package/dist/docs/assets/{templates_triage.md.CVlpctKS.lean.js → templates_triage.md.Co4UNzkZ.lean.js} +1 -1
- 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 +90 -26
- 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 -1
- 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 +7 -7
- 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 +7 -7
- package/dist/docs/templates/agentic-owners.md +1 -1
- package/dist/docs/templates/demo.html +6 -6
- package/dist/docs/templates/demo.md +3 -2
- package/dist/docs/templates/pr-autofixer.html +6 -6
- package/dist/docs/templates/pr-autofixer.md +7 -13
- package/dist/docs/templates/security-help.html +30 -0
- package/dist/docs/templates/security-help.md +65 -0
- package/dist/docs/templates/security-reviewer.html +6 -6
- package/dist/docs/templates/security-reviewer.md +1 -2
- package/dist/docs/templates/triage.html +7 -7
- package/dist/docs/templates/triage.md +2 -6
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/files-backends/cursor-hosted.d.ts +6 -2
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +2 -2
- package/dist/files.d.ts +2 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +5 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +135 -6
- package/dist/internal/conversation-mirror.d.ts +82 -0
- package/dist/internal/conversation-mirror.d.ts.map +1 -0
- package/dist/internal/conversation-mirror.js +251 -0
- package/dist/internal/deferred-channel-session.d.ts +20 -0
- package/dist/internal/deferred-channel-session.d.ts.map +1 -0
- package/dist/internal/deferred-channel-session.js +62 -0
- package/dist/internal/deploy-client.d.ts +13 -1
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +11 -1
- package/dist/internal/deploy-manifest.d.ts.map +1 -1
- package/dist/internal/deploy-manifest.js +11 -5
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +22 -2
- package/dist/internal/framework-storage-selection.d.ts +32 -16
- package/dist/internal/framework-storage-selection.d.ts.map +1 -1
- package/dist/internal/framework-storage-selection.js +51 -17
- package/dist/internal/hosted-admission-context.d.ts +20 -0
- package/dist/internal/hosted-admission-context.d.ts.map +1 -0
- package/dist/internal/hosted-admission-context.js +31 -0
- package/dist/internal/hosted-delivery-protocol.d.ts +5 -0
- package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
- package/dist/internal/hosted-delivery-protocol.js +33 -1
- package/dist/internal/hosted-delivery.d.ts +4 -2
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +75 -35
- package/dist/internal/hosted-managed-automation.d.ts +30 -0
- package/dist/internal/hosted-managed-automation.d.ts.map +1 -0
- package/dist/internal/hosted-managed-automation.js +58 -0
- package/dist/internal/review-comments.d.ts.map +1 -1
- package/dist/internal/review-comments.js +10 -98
- package/dist/internal/scm/pr-url.d.ts +14 -0
- package/dist/internal/scm/pr-url.d.ts.map +1 -0
- package/dist/internal/scm/pr-url.js +65 -0
- package/dist/internal/sdk-runner.d.ts +14 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +41 -2
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +10 -2
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +9 -0
- package/dist/playground/assets/index-CF7hUDnQ.css +1 -0
- package/dist/playground/assets/{index-B3JCyigB.js → index-CZA1uEWA.js} +48 -48
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +7 -2
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/storage-backends/cursor-hosted.js +2 -2
- package/docs/README.md +3 -1
- package/docs/reference/cli.md +3 -1
- package/docs/templates/agentic-owners.md +1 -1
- package/docs/templates/demo.md +3 -2
- package/docs/templates/pr-autofixer.md +7 -13
- package/docs/templates/security-help.md +70 -0
- package/docs/templates/security-reviewer.md +1 -2
- package/docs/templates/triage.md +2 -6
- package/package.json +1 -1
- package/src/channels/github/github-channel.ts +23 -8
- package/src/channels/github/types.ts +19 -2
- package/src/channels/origin/origin-channel.ts +13 -1
- package/src/channels/origin/origin-webhook.ts +27 -3
- package/src/channels/origin/types.ts +8 -0
- package/src/channels/slack/pr-ref.ts +56 -25
- package/src/files-backends/cursor-hosted.ts +9 -3
- package/src/files.ts +11 -0
- package/src/internal/cli-deploy.ts +171 -7
- package/src/internal/conversation-mirror.ts +330 -0
- package/src/internal/deferred-channel-session.ts +61 -0
- package/src/internal/deploy-client.ts +24 -1
- package/src/internal/deploy-manifest.ts +10 -5
- package/src/internal/discovery.ts +25 -2
- package/src/internal/framework-storage-selection.ts +61 -19
- package/src/internal/hosted-admission-context.ts +37 -0
- package/src/internal/hosted-delivery-protocol.ts +44 -1
- package/src/internal/hosted-delivery.ts +95 -40
- package/src/internal/hosted-managed-automation.ts +72 -0
- package/src/internal/review-comments.ts +10 -113
- package/src/internal/scm/pr-url.ts +95 -0
- package/src/internal/sdk-runner.ts +57 -2
- package/src/internal/server.ts +10 -0
- package/src/internal/session-engine.ts +12 -0
- package/src/storage-backends/cursor-hosted.ts +10 -3
- package/templates/agentic-owners/README.md +1 -1
- package/templates/agentic-owners/agent/agent.ts +0 -10
- package/templates/agentic-owners/agent/channels/github.ts +5 -14
- package/templates/agentic-owners/agent/lib/config.ts +0 -8
- package/templates/agentic-owners/agent/lib/review.ts +2 -15
- package/templates/agentic-owners/agent/tools/record_review.ts +2 -4
- package/templates/demo/agent/agent.ts +0 -10
- package/templates/pr-autofixer/README.md +0 -2
- package/templates/pr-autofixer/agent/agent.ts +0 -11
- package/templates/pr-autofixer/agent/channels/slack.ts +0 -7
- package/templates/pr-autofixer/agent/lib/pr-state.ts +5 -17
- package/templates/pr-autofixer/agent/lib/repos.ts +0 -1
- package/templates/security-help/README.md +2 -2
- package/templates/security-help/agent/agent.ts +1 -2
- package/templates/security-help/agent/channels/slack.ts +0 -3
- package/templates/security-help/agent/instructions.md +9 -10
- package/templates/security-help/agent/skills/access-request.md +1 -1
- package/templates/security-help/agent/skills/faq.md +31 -0
- package/templates/security-help/agent/skills/security-playbooks.md +1 -1
- package/templates/security-help/package.json +1 -2
- package/templates/security-reviewer/agent/agent.ts +0 -10
- package/templates/triage/agent/agent.ts +0 -10
- package/templates/triage/agent/channels/queue.ts +1 -1
- package/templates/triage/agent/channels/webhook.ts +1 -3
- package/dist/docs/assets/chunks/@localSearchIndexroot.DU3U2Ij2.js +0 -1
- package/dist/docs/assets/index.md.DRakGHFe.js +0 -5
- package/dist/playground/assets/index-D9N7-q97.css +0 -1
- package/templates/security-help/agent/knowledge/faq/approvals.md +0 -5
- package/templates/security-help/agent/knowledge/faq/channels.md +0 -6
- package/templates/security-help/agent/knowledge/faq/phishing.md +0 -10
- package/templates/security-help/agent/skills/security-first-pass.md +0 -15
- /package/dist/docs/assets/{reference_skills.md.8son6Hjm.lean.js → reference_skills.md.BFW9retM.lean.js} +0 -0
- /package/dist/docs/assets/{templates_agentic-owners.md.DqtPdm6f.lean.js → templates_agentic-owners.md.BZSH4N9z.lean.js} +0 -0
- /package/dist/docs/assets/{templates_demo.md.DhFcWN6j.lean.js → templates_demo.md.BeQX9V3H.lean.js} +0 -0
|
@@ -168,12 +168,17 @@ export function buildAgentDeployManifest(
|
|
|
168
168
|
declarations: {
|
|
169
169
|
egressDomains: [...(project.agent.hosting?.egressDomains ?? [])],
|
|
170
170
|
publicRoutePaths,
|
|
171
|
+
// v2 ignores authored storage; deploy manifests describe
|
|
172
|
+
// Cursor-hosted deployments, where framework-storage-selection.ts
|
|
173
|
+
// injects the Cursor-hosted mirror.
|
|
171
174
|
frameworkStorage:
|
|
172
|
-
project.
|
|
173
|
-
? "
|
|
174
|
-
:
|
|
175
|
-
? "
|
|
176
|
-
:
|
|
175
|
+
project.agent.architecture === "v2"
|
|
176
|
+
? "cursor-hosted"
|
|
177
|
+
: project.storage === undefined
|
|
178
|
+
? "none"
|
|
179
|
+
: isCursorHostedStorage(project.storage)
|
|
180
|
+
? "cursor-hosted"
|
|
181
|
+
: "other",
|
|
177
182
|
...(project.agent.architecture === undefined
|
|
178
183
|
? {}
|
|
179
184
|
: { architecture: project.agent.architecture }),
|
|
@@ -307,7 +307,7 @@ export async function loadAgentProject(
|
|
|
307
307
|
const hooks = await loadHooks(ctx, join(agentDir, "hooks"));
|
|
308
308
|
const abs = await loadABs(ctx, agentDir);
|
|
309
309
|
const abConfig = await loadABConfig(ctx, agentDir);
|
|
310
|
-
const storage = await loadStorage(ctx, agentDir);
|
|
310
|
+
const storage = await loadStorage(ctx, agentDir, agent.architecture);
|
|
311
311
|
const artifacts = await loadArtifacts(ctx, agentDir);
|
|
312
312
|
const otel = await loadOtel(ctx, agentDir);
|
|
313
313
|
await appendArtifactTool(ctx, agentDir, agent, artifacts);
|
|
@@ -2151,12 +2151,35 @@ async function loadABConfig(
|
|
|
2151
2151
|
|
|
2152
2152
|
async function loadStorage(
|
|
2153
2153
|
ctx: DiscoveryContext,
|
|
2154
|
-
agentDir: string
|
|
2154
|
+
agentDir: string,
|
|
2155
|
+
architecture: AgentArchitecture | undefined
|
|
2155
2156
|
): Promise<StorageDefinition | undefined> {
|
|
2156
2157
|
const path = await findModuleFile(agentDir, "storage");
|
|
2157
2158
|
if (path === undefined) {
|
|
2158
2159
|
return undefined;
|
|
2159
2160
|
}
|
|
2161
|
+
if (architecture === "v2") {
|
|
2162
|
+
// Storage is a hosting-mode decision on v2, made by
|
|
2163
|
+
// framework-storage-selection.ts: the state root when self-hosted,
|
|
2164
|
+
// Cursor-managed storage on Cursor hosting. Fail early (error
|
|
2165
|
+
// diagnostics refuse serve and deploy) rather than silently running
|
|
2166
|
+
// with storage the author believes they configured. Not imported.
|
|
2167
|
+
ctx.diagnostics.push({
|
|
2168
|
+
severity: "error",
|
|
2169
|
+
path: displayPath(ctx, path),
|
|
2170
|
+
message:
|
|
2171
|
+
'agent/storage.ts is not supported on architecture "v2": the platform selects storage (the local state root when self-hosted; Cursor-managed storage on Cursor hosting). Delete the file.',
|
|
2172
|
+
});
|
|
2173
|
+
return undefined;
|
|
2174
|
+
}
|
|
2175
|
+
// v1 keeps loading the authored sink, with notice: defineStorage is going
|
|
2176
|
+
// away with the v1 control plane.
|
|
2177
|
+
ctx.diagnostics.push({
|
|
2178
|
+
severity: "warning",
|
|
2179
|
+
path: displayPath(ctx, path),
|
|
2180
|
+
message:
|
|
2181
|
+
'agent/storage.ts (defineStorage) is deprecated and going away: architecture "v2" selects storage automatically (the local state root when self-hosted; Cursor-managed storage on Cursor hosting). Still honored on v1.',
|
|
2182
|
+
});
|
|
2160
2183
|
const definition = await importDefault(ctx, path, "storage");
|
|
2161
2184
|
return definition as StorageDefinition | undefined;
|
|
2162
2185
|
}
|
|
@@ -2,19 +2,31 @@
|
|
|
2
2
|
* Which backend framework session state is mirrored to.
|
|
3
3
|
*
|
|
4
4
|
* Framework state — session records, event chunks, the continuation index —
|
|
5
|
-
* is how a session outlives the process that served it.
|
|
6
|
-
* exists only when the agent author wrote `agent/storage.ts`,
|
|
7
|
-
* platform durability an agent-level decision: of the
|
|
8
|
-
* some choose `cursorHostedStorage()` and some choose
|
|
9
|
-
* disk, lost on pod replacement), and one selects
|
|
10
|
-
* reading the deployment-id environment variable
|
|
5
|
+
* is how a session outlives the process that served it. On architecture v1
|
|
6
|
+
* that mirror exists only when the agent author wrote `agent/storage.ts`,
|
|
7
|
+
* which makes platform durability an agent-level decision: of the
|
|
8
|
+
* first-party agents, some choose `cursorHostedStorage()` and some choose
|
|
9
|
+
* `fileKv()` (pod-local disk, lost on pod replacement), and one selects
|
|
10
|
+
* between them at runtime by reading the deployment-id environment variable
|
|
11
|
+
* itself.
|
|
11
12
|
*
|
|
12
|
-
* That selection is a hosting-mode question, not an authoring question
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* That selection is a hosting-mode question, not an authoring question.
|
|
14
|
+
* Architecture v2 moves it here: `agent/storage.ts` is rejected at
|
|
15
|
+
* discovery (error diagnostic, so serve and deploy fail early), and the
|
|
16
|
+
* platform picks the default —
|
|
17
|
+
*
|
|
18
|
+
* - **Cursor-managed hosting** (the deployment-generation environment
|
|
19
|
+
* variable is set, same signal that registers `GET /v1/manifest`):
|
|
20
|
+
* the Cursor-hosted backend, so session records, event chunks, `host.kv`,
|
|
21
|
+
* eval runs, and A/B samples land in the control plane and survive pod
|
|
22
|
+
* replacement. The API key resolves through the full credential chain on
|
|
23
|
+
* every call because hosted freeze snapshots listen unsigned and the
|
|
24
|
+
* binder drops the key file after restore.
|
|
25
|
+
* - **Self-hosted**: no mirror. `--state-root` is the durable copy, which
|
|
26
|
+
* is correct when the operator owns the disk.
|
|
27
|
+
*
|
|
28
|
+
* v1 keeps the authored definition (or nothing) so existing deployments are
|
|
29
|
+
* untouched while they run.
|
|
18
30
|
*
|
|
19
31
|
* Local disk is not part of this decision. `<stateRoot>/sessions/<id>/`
|
|
20
32
|
* already holds `session.json` and `events.ndjson` unconditionally, through
|
|
@@ -22,24 +34,54 @@
|
|
|
22
34
|
* pod's filesystem.
|
|
23
35
|
*/
|
|
24
36
|
|
|
25
|
-
import type
|
|
37
|
+
import { defineStorage, type StorageDefinition } from "../storage.js";
|
|
38
|
+
import { cursorHostedStorage } from "../storage-backends/cursor-hosted.js";
|
|
39
|
+
import type { AgentArchitecture } from "../types.js";
|
|
40
|
+
import { resolveApiKeySync } from "./cursor/credentials.js";
|
|
41
|
+
import { AGENT_SERVE_GENERATION_ENV_VAR } from "./cursor-relay-core.js";
|
|
26
42
|
|
|
27
43
|
export interface FrameworkStorageSelectionInput {
|
|
28
|
-
/** `agent/storage.ts`, when the author wrote one. */
|
|
44
|
+
/** `agent/storage.ts`, when the author wrote one (v1 only: discovery rejects it on v2). */
|
|
29
45
|
readonly authored: StorageDefinition | undefined;
|
|
46
|
+
/** {@link AgentConfig.architecture}; omitted means v1. */
|
|
47
|
+
readonly architecture?: AgentArchitecture | undefined;
|
|
48
|
+
/** Environment override for tests; defaults to `process.env`. */
|
|
49
|
+
readonly env?: Record<string, string | undefined>;
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
/**
|
|
33
53
|
* Pick the backend for framework session state.
|
|
34
54
|
*
|
|
35
55
|
* Returns `undefined` when there is no mirror, in which case the pod's local
|
|
36
|
-
* disk is the only copy
|
|
37
|
-
* deployments.
|
|
56
|
+
* disk is the only copy.
|
|
38
57
|
*/
|
|
39
58
|
export function selectFrameworkStorageDefinition(
|
|
40
59
|
input: FrameworkStorageSelectionInput
|
|
41
60
|
): StorageDefinition | undefined {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
if (input.architecture !== "v2") {
|
|
62
|
+
// v1: the author decides, or there is no mirror.
|
|
63
|
+
return input.authored;
|
|
64
|
+
}
|
|
65
|
+
if (input.authored !== undefined) {
|
|
66
|
+
// Discovery rejects agent/storage.ts on v2 before an engine is built;
|
|
67
|
+
// an authored definition reaching this point is a wiring bug. Raise
|
|
68
|
+
// rather than silently discard what the author believes is configured.
|
|
69
|
+
throw new Error(
|
|
70
|
+
'defineStorage (agent/storage.ts) is not supported on architecture "v2": storage is platform-selected. Delete the file.'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const env = input.env ?? process.env;
|
|
74
|
+
if (env[AGENT_SERVE_GENERATION_ENV_VAR] === undefined) {
|
|
75
|
+
// Self-hosted v2: the state root is the durable copy.
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return defineStorage({
|
|
79
|
+
name: "cursor-hosted",
|
|
80
|
+
...cursorHostedStorage({
|
|
81
|
+
// Full chain (env → key file → stored login): frozen engines listen
|
|
82
|
+
// unsigned and bind CURSOR_API_KEY_FILE at task time, after this
|
|
83
|
+
// definition is constructed.
|
|
84
|
+
apiKey: () => resolveApiKeySync()?.apiKey,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
45
87
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A `send()` captured outside the delivery (typically at channel `onStart`,
|
|
5
|
+
* which binds directly to the engine) reached the engine during a hosted
|
|
6
|
+
* admission window. Running it would start a local July session on a
|
|
7
|
+
* sessionless pod — invisible to the control plane, destroyed with the pod —
|
|
8
|
+
* so it fails loudly instead. This is a deterministic authored bug and is
|
|
9
|
+
* surfaced as a non-retryable 422 by the admission route.
|
|
10
|
+
*/
|
|
11
|
+
export class HostedAdmissionLocalSendError extends Error {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(
|
|
14
|
+
"send() reached the local engine during hosted admission. A send " +
|
|
15
|
+
"captured at channel startup (onStart) bypasses hosted capture and " +
|
|
16
|
+
"would run an invisible local session on a sessionless pod. Use the " +
|
|
17
|
+
"per-delivery send passed to the task callback " +
|
|
18
|
+
"(task: (args) => args.send(…)) instead."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const admissionWindow = new AsyncLocalStorage<true>();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Run `work` inside the hosted admission window. The context follows the
|
|
27
|
+
* async graph, including `waitUntil` task bodies scheduled during dispatch,
|
|
28
|
+
* so the engine can refuse local sends that bypass the capture stub.
|
|
29
|
+
*/
|
|
30
|
+
export function runDuringHostedAdmission<T>(work: () => T): T {
|
|
31
|
+
return admissionWindow.run(true, work);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** True while executing inside a hosted admission window. */
|
|
35
|
+
export function isDuringHostedAdmission(): boolean {
|
|
36
|
+
return admissionWindow.getStore() === true;
|
|
37
|
+
}
|
|
@@ -3,6 +3,11 @@ export const HOSTED_DELIVERY_HEADER = "x-agent-sdk-hosted-delivery";
|
|
|
3
3
|
export const HOSTED_ADMISSION_PATH = "/v1/internal/hosted-admission";
|
|
4
4
|
export const HOSTED_EXECUTION_PATH = "/v1/internal/hosted-execution";
|
|
5
5
|
|
|
6
|
+
export type HostedAdmissionManagedAutomation = {
|
|
7
|
+
automationUuid: string;
|
|
8
|
+
mcpServerNames: string[];
|
|
9
|
+
};
|
|
10
|
+
|
|
6
11
|
export type HostedAdmission = {
|
|
7
12
|
v: 2;
|
|
8
13
|
channelId: string;
|
|
@@ -12,6 +17,7 @@ export type HostedAdmission = {
|
|
|
12
17
|
sessionMode: "event" | "continuation";
|
|
13
18
|
mailboxContinuationKey: string;
|
|
14
19
|
payload: unknown;
|
|
20
|
+
managedAutomation?: HostedAdmissionManagedAutomation;
|
|
15
21
|
};
|
|
16
22
|
|
|
17
23
|
export type HostedAdmissionOutcome =
|
|
@@ -39,13 +45,49 @@ const DELIVERY_KEYS = new Set([
|
|
|
39
45
|
"sessionMode",
|
|
40
46
|
"mailboxContinuationKey",
|
|
41
47
|
"payload",
|
|
48
|
+
"managedAutomation",
|
|
42
49
|
]);
|
|
50
|
+
const MANAGED_AUTOMATION_KEYS = new Set(["automationUuid", "mcpServerNames"]);
|
|
51
|
+
/** More names than any real automation configures is a malformed envelope. */
|
|
52
|
+
const MANAGED_AUTOMATION_MAX_SERVERS = 64;
|
|
43
53
|
const EXECUTION_KEYS = new Set(["v", "admissionToken", "sessionPublicId"]);
|
|
44
54
|
|
|
45
55
|
function boundedString(value: unknown, max: number): value is string {
|
|
46
56
|
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
47
57
|
}
|
|
48
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Parse the optional managed-automation slice. Absent → valid (undefined);
|
|
61
|
+
* present-but-malformed → invalid (the whole admission fails closed, the
|
|
62
|
+
* strict posture the rest of the envelope already takes).
|
|
63
|
+
*/
|
|
64
|
+
function parseManagedAutomation(
|
|
65
|
+
value: unknown
|
|
66
|
+
): { managedAutomation?: HostedAdmissionManagedAutomation } | undefined {
|
|
67
|
+
if (value === undefined) {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const body = value as Record<string, unknown>;
|
|
74
|
+
if (
|
|
75
|
+
Object.keys(body).some((key) => !MANAGED_AUTOMATION_KEYS.has(key)) ||
|
|
76
|
+
!boundedString(body.automationUuid, 128) ||
|
|
77
|
+
!Array.isArray(body.mcpServerNames) ||
|
|
78
|
+
body.mcpServerNames.length > MANAGED_AUTOMATION_MAX_SERVERS ||
|
|
79
|
+
!body.mcpServerNames.every((name) => boundedString(name, 256))
|
|
80
|
+
) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
managedAutomation: {
|
|
85
|
+
automationUuid: body.automationUuid,
|
|
86
|
+
mcpServerNames: body.mcpServerNames,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
49
91
|
export function parseHostedAdmission(
|
|
50
92
|
value: unknown
|
|
51
93
|
): HostedAdmission | undefined {
|
|
@@ -62,7 +104,8 @@ export function parseHostedAdmission(
|
|
|
62
104
|
!boundedString(body.eventType, 128) ||
|
|
63
105
|
!boundedString(body.deliveryId, 256) ||
|
|
64
106
|
(body.sessionMode !== "event" && body.sessionMode !== "continuation") ||
|
|
65
|
-
!boundedString(body.mailboxContinuationKey, 512)
|
|
107
|
+
!boundedString(body.mailboxContinuationKey, 512) ||
|
|
108
|
+
parseManagedAutomation(body.managedAutomation) === undefined
|
|
66
109
|
) {
|
|
67
110
|
return undefined;
|
|
68
111
|
}
|
|
@@ -10,6 +10,14 @@ import type {
|
|
|
10
10
|
ChannelHandlerArgs,
|
|
11
11
|
SendMessageOptions,
|
|
12
12
|
} from "../types.js";
|
|
13
|
+
import {
|
|
14
|
+
createDeferredChannelSession,
|
|
15
|
+
DeferredChannelSessionError,
|
|
16
|
+
} from "./deferred-channel-session.js";
|
|
17
|
+
import {
|
|
18
|
+
HostedAdmissionLocalSendError,
|
|
19
|
+
runDuringHostedAdmission,
|
|
20
|
+
} from "./hosted-admission-context.js";
|
|
13
21
|
import {
|
|
14
22
|
HOSTED_ADMISSION_PATH,
|
|
15
23
|
HOSTED_DELIVERY_HEADER,
|
|
@@ -19,6 +27,7 @@ import {
|
|
|
19
27
|
parseHostedAdmission,
|
|
20
28
|
parseHostedExecution,
|
|
21
29
|
} from "./hosted-delivery-protocol.js";
|
|
30
|
+
import { recordHostedManagedAutomation } from "./hosted-managed-automation.js";
|
|
22
31
|
import { errorResponse, readJsonObject } from "./http-channel.js";
|
|
23
32
|
import type { Router } from "./router.js";
|
|
24
33
|
|
|
@@ -45,19 +54,66 @@ type ExecutionReply = {
|
|
|
45
54
|
body: Record<string, unknown>;
|
|
46
55
|
};
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
/**
|
|
58
|
+
* A second `send()` in one delivery is a deterministic authored bug — the
|
|
59
|
+
* wire protocol carries exactly one pending send per delivery — so it must
|
|
60
|
+
* fail the delivery exactly once. It surfaces as a 422 because the control
|
|
61
|
+
* plane retries 5xx admissions (fresh pod, side effects re-run, identical
|
|
62
|
+
* failure, up to the Temporal attempt cap); a non-5xx/408/429 status makes
|
|
63
|
+
* it reject the delivery non-retryably instead.
|
|
64
|
+
*/
|
|
65
|
+
class HostedSendLimitExceeded extends Error {
|
|
66
|
+
constructor() {
|
|
67
|
+
super(
|
|
68
|
+
"The hosted delivery protocol carries exactly one pending send per " +
|
|
69
|
+
"delivery, and this delivery already captured one. Combine both " +
|
|
70
|
+
"messages into a single send(), or split the work across deliveries."
|
|
71
|
+
);
|
|
54
72
|
}
|
|
55
73
|
}
|
|
56
74
|
|
|
75
|
+
// Authored/task-derived text stays bounded on this internal response path
|
|
76
|
+
// (and is never persisted to Temporal history by the control plane).
|
|
77
|
+
const MAX_ERROR_MESSAGE_LENGTH = 1024;
|
|
78
|
+
|
|
79
|
+
function boundedMessage(text: string): string {
|
|
80
|
+
return text.length > MAX_ERROR_MESSAGE_LENGTH
|
|
81
|
+
? `${text.slice(0, MAX_ERROR_MESSAGE_LENGTH)}…`
|
|
82
|
+
: text;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Classify deterministic authored protocol violations as non-retryable 422
|
|
87
|
+
* admissions. Anything else returns `undefined` so callers fall through to
|
|
88
|
+
* the existing (retryable or rethrow) handling.
|
|
89
|
+
*/
|
|
90
|
+
function nonRetryableRejection(error: unknown): AdmissionDispatch | undefined {
|
|
91
|
+
const code =
|
|
92
|
+
error instanceof HostedSendLimitExceeded
|
|
93
|
+
? "hosted_send_limit_exceeded"
|
|
94
|
+
: error instanceof DeferredChannelSessionError
|
|
95
|
+
? "hosted_deferred_session_unusable"
|
|
96
|
+
: error instanceof HostedAdmissionLocalSendError
|
|
97
|
+
? "hosted_local_send_rejected"
|
|
98
|
+
: undefined;
|
|
99
|
+
if (code === undefined) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
response: errorResponse(
|
|
104
|
+
422,
|
|
105
|
+
code,
|
|
106
|
+
boundedMessage((error as Error).message)
|
|
107
|
+
),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
57
111
|
/**
|
|
58
112
|
* Run authored admission without creating a framework session. V1 GitHub
|
|
59
|
-
* `task` callbacks execute as sessionless work; `send` is captured
|
|
60
|
-
*
|
|
113
|
+
* `task` callbacks execute as sessionless work; the first `send` is captured
|
|
114
|
+
* and executed only after the control plane allocates `ses_…`. Capturing
|
|
115
|
+
* does not terminate the caller — `send` returns an inert deferred handle so
|
|
116
|
+
* a task body runs to completion before the admission response is computed.
|
|
61
117
|
*/
|
|
62
118
|
export async function dispatchHostedAdmission(
|
|
63
119
|
input: DispatchInput
|
|
@@ -71,24 +127,28 @@ export async function dispatchHostedAdmission(
|
|
|
71
127
|
),
|
|
72
128
|
};
|
|
73
129
|
}
|
|
130
|
+
// The admission window marker follows the async graph into task bodies so
|
|
131
|
+
// the engine can refuse local sends that would bypass the capture stub.
|
|
132
|
+
return runDuringHostedAdmission(() => runAdmission(input, github));
|
|
133
|
+
}
|
|
74
134
|
|
|
135
|
+
async function runAdmission(
|
|
136
|
+
input: DispatchInput,
|
|
137
|
+
github: NonNullable<ReturnType<typeof getGitHubChannelOptions>>
|
|
138
|
+
): Promise<AdmissionDispatch> {
|
|
75
139
|
const background: Promise<unknown>[] = [];
|
|
76
|
-
let
|
|
140
|
+
let captured: { message: string; options: SendMessageOptions } | undefined;
|
|
77
141
|
const args = withPropagatingGitHubBackgroundErrors({
|
|
78
142
|
...input.args,
|
|
79
143
|
send: async (message, options = {}) => {
|
|
80
|
-
|
|
144
|
+
if (captured !== undefined) {
|
|
145
|
+
throw new HostedSendLimitExceeded();
|
|
146
|
+
}
|
|
147
|
+
captured = { message, options };
|
|
148
|
+
return createDeferredChannelSession();
|
|
81
149
|
},
|
|
82
150
|
waitUntil: (promise) => {
|
|
83
|
-
background.push(
|
|
84
|
-
promise.catch((error: unknown) => {
|
|
85
|
-
if (error instanceof HostedSendAdmitted) {
|
|
86
|
-
deferredSend = error;
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
throw error;
|
|
90
|
-
})
|
|
91
|
-
);
|
|
151
|
+
background.push(promise);
|
|
92
152
|
},
|
|
93
153
|
});
|
|
94
154
|
let dispatched: Response;
|
|
@@ -101,20 +161,9 @@ export async function dispatchHostedAdmission(
|
|
|
101
161
|
args,
|
|
102
162
|
});
|
|
103
163
|
} catch (error) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
ok: true,
|
|
108
|
-
v: HOSTED_DELIVERY_PROTOCOL_VERSION,
|
|
109
|
-
outcome: "send",
|
|
110
|
-
}),
|
|
111
|
-
pendingSend: {
|
|
112
|
-
channelId: input.delivery.channelId,
|
|
113
|
-
message: error.prompt,
|
|
114
|
-
options: error.options,
|
|
115
|
-
mailboxContinuationKey: input.delivery.mailboxContinuationKey,
|
|
116
|
-
},
|
|
117
|
-
};
|
|
164
|
+
const rejected = nonRetryableRejection(error);
|
|
165
|
+
if (rejected !== undefined) {
|
|
166
|
+
return rejected;
|
|
118
167
|
}
|
|
119
168
|
throw error;
|
|
120
169
|
}
|
|
@@ -129,15 +178,20 @@ export async function dispatchHostedAdmission(
|
|
|
129
178
|
|
|
130
179
|
try {
|
|
131
180
|
await Promise.all(background);
|
|
132
|
-
} catch {
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const rejected = nonRetryableRejection(error);
|
|
183
|
+
if (rejected !== undefined) {
|
|
184
|
+
return rejected;
|
|
185
|
+
}
|
|
133
186
|
return {
|
|
134
|
-
response:
|
|
135
|
-
|
|
136
|
-
|
|
187
|
+
response: errorResponse(
|
|
188
|
+
503,
|
|
189
|
+
"background_dispatch_failed",
|
|
190
|
+
boundedMessage(error instanceof Error ? error.message : String(error))
|
|
137
191
|
),
|
|
138
192
|
};
|
|
139
193
|
}
|
|
140
|
-
if (
|
|
194
|
+
if (captured !== undefined) {
|
|
141
195
|
return {
|
|
142
196
|
response: Response.json({
|
|
143
197
|
ok: true,
|
|
@@ -146,8 +200,8 @@ export async function dispatchHostedAdmission(
|
|
|
146
200
|
}),
|
|
147
201
|
pendingSend: {
|
|
148
202
|
channelId: input.delivery.channelId,
|
|
149
|
-
message:
|
|
150
|
-
options:
|
|
203
|
+
message: captured.message,
|
|
204
|
+
options: captured.options,
|
|
151
205
|
mailboxContinuationKey: input.delivery.mailboxContinuationKey,
|
|
152
206
|
},
|
|
153
207
|
};
|
|
@@ -241,6 +295,7 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
241
295
|
)
|
|
242
296
|
);
|
|
243
297
|
}
|
|
298
|
+
recordHostedManagedAutomation(delivery);
|
|
244
299
|
const channel = input.channels.find(
|
|
245
300
|
(candidate) => candidate.id === delivery.channelId
|
|
246
301
|
);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed-automation envelope slices by provider delivery id (plan 0002
|
|
3
|
+
* G2), recorded at hosted admission so channel code running the delivery's
|
|
4
|
+
* task (or session turn) can attach the customer's MCP servers through the
|
|
5
|
+
* control-plane relay. In-memory and TTL-bounded like the hosted pending-
|
|
6
|
+
* send table: the envelope slice carries names only (never credentials)
|
|
7
|
+
* and evaporates with the process.
|
|
8
|
+
*
|
|
9
|
+
* A leaf module (protocol type import only) so product channels can read
|
|
10
|
+
* it without pulling the hosted-delivery route — and its session-engine
|
|
11
|
+
* dependency graph — into their bundles.
|
|
12
|
+
*
|
|
13
|
+
* INTERNAL: first-party multi-tenant deployment plumbing. Deliberately
|
|
14
|
+
* excluded from the published `@cursor/july` npm package (see the `files`
|
|
15
|
+
* whitelist); consumed by internal deployments via relative source imports.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { HostedAdmissionManagedAutomation } from "./hosted-delivery-protocol.js";
|
|
19
|
+
|
|
20
|
+
const managedAutomationByDelivery = new Map<
|
|
21
|
+
string,
|
|
22
|
+
{ managedAutomation: HostedAdmissionManagedAutomation; expiresAt: number }
|
|
23
|
+
>();
|
|
24
|
+
/** Long enough to outlive a slow review run kicked off by the delivery. */
|
|
25
|
+
const MANAGED_AUTOMATION_TTL_MS = 60 * 60_000;
|
|
26
|
+
const MANAGED_AUTOMATION_MAX_ENTRIES = 10_000;
|
|
27
|
+
|
|
28
|
+
export function recordHostedManagedAutomation(delivery: {
|
|
29
|
+
deliveryId: string;
|
|
30
|
+
managedAutomation?: HostedAdmissionManagedAutomation;
|
|
31
|
+
}): void {
|
|
32
|
+
if (delivery.managedAutomation === undefined) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
for (const [key, entry] of managedAutomationByDelivery) {
|
|
37
|
+
if (entry.expiresAt <= now) {
|
|
38
|
+
managedAutomationByDelivery.delete(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (managedAutomationByDelivery.size >= MANAGED_AUTOMATION_MAX_ENTRIES) {
|
|
42
|
+
managedAutomationByDelivery.clear();
|
|
43
|
+
}
|
|
44
|
+
managedAutomationByDelivery.set(delivery.deliveryId, {
|
|
45
|
+
managedAutomation: delivery.managedAutomation,
|
|
46
|
+
expiresAt: now + MANAGED_AUTOMATION_TTL_MS,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The managed automation the control plane attributed to a hosted delivery,
|
|
52
|
+
* when the envelope carried one. Undefined for non-managed deliveries,
|
|
53
|
+
* expired entries, and non-hosted (webhook/stream) dispatch paths.
|
|
54
|
+
*/
|
|
55
|
+
export function getHostedManagedAutomation(
|
|
56
|
+
deliveryId: string
|
|
57
|
+
): HostedAdmissionManagedAutomation | undefined {
|
|
58
|
+
const entry = managedAutomationByDelivery.get(deliveryId);
|
|
59
|
+
if (entry === undefined) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
if (entry.expiresAt <= Date.now()) {
|
|
63
|
+
managedAutomationByDelivery.delete(deliveryId);
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return entry.managedAutomation;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Drop recorded managed-automation slices (tests only). */
|
|
70
|
+
export function resetHostedManagedAutomationForTest(): void {
|
|
71
|
+
managedAutomationByDelivery.clear();
|
|
72
|
+
}
|