@cursor/july 0.1.94 → 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/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/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/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/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/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/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.CFDEas4I.js → app.jXleCmOc.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DFoGuETX.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.B1IIYpYS.js → VPLocalSearchBox.8LhyglIT.js} +1 -1
- package/dist/docs/assets/chunks/{theme.Ct4NSiLm.js → theme.9ged-pr2.js} +2 -2
- package/dist/docs/assets/index.md.CI93Ltlb.js +5 -0
- package/dist/docs/assets/{index.md.DRakGHFe.lean.js → index.md.CI93Ltlb.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.Cw6_ICYG.js → reference_cli.md.CCnG-nJx.js} +4 -2
- package/dist/docs/assets/{reference_cli.md.Cw6_ICYG.lean.js → reference_cli.md.CCnG-nJx.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_code-wiki.md.CDYZIBFN.js +2 -0
- package/dist/docs/assets/templates_code-wiki.md.CDYZIBFN.lean.js +1 -0
- 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 +5 -1
- package/dist/docs/llms-full.txt +189 -26
- package/dist/docs/llms.txt +2 -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 +8 -6
- package/dist/docs/reference/cli.md +4 -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/code-wiki.html +28 -0
- package/dist/docs/templates/code-wiki.md +91 -0
- 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 +26 -0
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +32 -4
- package/dist/files.d.ts +2 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +5 -0
- 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 +142 -7
- package/dist/internal/conversation-mirror.d.ts +85 -0
- package/dist/internal/conversation-mirror.d.ts.map +1 -0
- package/dist/internal/conversation-mirror.js +312 -0
- 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/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-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 +38 -16
- package/dist/internal/framework-storage-selection.d.ts.map +1 -1
- package/dist/internal/framework-storage-selection.js +78 -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 +11 -2
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +79 -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/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/review-comments.d.ts.map +1 -1
- package/dist/internal/review-comments.js +10 -98
- 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/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 +25 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +70 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +11 -2
- 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 +71 -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-B3JCyigB.js → index-Cx9CclK_.js} +47 -47
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +8 -0
- 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 +5 -1
- package/docs/reference/cli.md +4 -1
- package/docs/templates/agentic-owners.md +1 -1
- package/docs/templates/code-wiki.md +96 -0
- 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 +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/github/github-channel.ts +23 -8
- package/src/channels/github/types.ts +19 -2
- 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/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/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/pr-ref.ts +56 -25
- package/src/channels/slack/slack-channel.ts +2 -0
- package/src/files-backends/cursor-hosted.ts +41 -2
- package/src/files.ts +11 -0
- package/src/internal/advertise-tools.ts +18 -2
- package/src/internal/bounded.ts +28 -0
- package/src/internal/cli-deploy.ts +183 -7
- package/src/internal/conversation-mirror.ts +406 -0
- 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/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-file-storage.ts +222 -0
- package/src/internal/framework-storage-selection.ts +101 -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 +105 -40
- package/src/internal/hosted-managed-automation.ts +72 -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/review-comments.ts +10 -113
- package/src/internal/runtime-dispatch-runner.ts +10 -0
- package/src/internal/scm/pr-url.ts +95 -0
- package/src/internal/sdk-runner.ts +103 -0
- package/src/internal/server.ts +12 -0
- package/src/internal/session-engine.ts +76 -4
- package/src/internal/steering.ts +17 -0
- package/src/storage-backends/cursor-hosted.ts +10 -1
- package/src/types.ts +2 -1
- 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/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/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/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-D9N7-q97.css +0 -1
- package/src/internal/deployment-scm-event-stream.ts +0 -593
- 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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ChannelSession } from "../types.js";
|
|
2
|
+
|
|
3
|
+
const deferredSessions = new WeakSet<ChannelSession>();
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Authored code touched a member of the deferred handle that needs the live
|
|
7
|
+
* session. This is deterministic (the session never exists during admission),
|
|
8
|
+
* so the admission route surfaces it as a non-retryable 422 rather than a
|
|
9
|
+
* retryable failure.
|
|
10
|
+
*/
|
|
11
|
+
export class DeferredChannelSessionError extends Error {
|
|
12
|
+
constructor(member: string) {
|
|
13
|
+
super(
|
|
14
|
+
`ChannelSession.${member} is not available on a send() captured ` +
|
|
15
|
+
"during hosted admission: the control plane allocates the session " +
|
|
16
|
+
"(and runs the turn) only after this delivery is acknowledged, so " +
|
|
17
|
+
"the handle cannot observe it yet. Move work that needs the live " +
|
|
18
|
+
"session into the session turn instead."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function unavailable(member: string): never {
|
|
24
|
+
throw new DeferredChannelSessionError(member);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Inert `ChannelSession` returned by the hosted-admission `send` stub: every
|
|
29
|
+
* member fails loudly via {@link DeferredChannelSessionError}. Caveat: new
|
|
30
|
+
* OPTIONAL members added to `ChannelSession` slip through this factory
|
|
31
|
+
* silently (reads return `undefined`) until a throwing getter is added here.
|
|
32
|
+
*/
|
|
33
|
+
export function createDeferredChannelSession(): ChannelSession {
|
|
34
|
+
const session: ChannelSession = {
|
|
35
|
+
get id(): string {
|
|
36
|
+
return unavailable("id");
|
|
37
|
+
},
|
|
38
|
+
get continuationToken(): string | null {
|
|
39
|
+
return unavailable("continuationToken");
|
|
40
|
+
},
|
|
41
|
+
get isNew(): boolean {
|
|
42
|
+
return unavailable("isNew");
|
|
43
|
+
},
|
|
44
|
+
get coalesced(): boolean | undefined {
|
|
45
|
+
return unavailable("coalesced");
|
|
46
|
+
},
|
|
47
|
+
get info(): ChannelSession["info"] {
|
|
48
|
+
return unavailable("info");
|
|
49
|
+
},
|
|
50
|
+
getEventStream: async () => unavailable("getEventStream()"),
|
|
51
|
+
waitForCompletion: async () => unavailable("waitForCompletion()"),
|
|
52
|
+
setContinuationToken: async () => unavailable("setContinuationToken()"),
|
|
53
|
+
};
|
|
54
|
+
deferredSessions.add(session);
|
|
55
|
+
return session;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** True when `send()` returned a hosted-admission capture handle. */
|
|
59
|
+
export function isDeferredChannelSession(session: ChannelSession): boolean {
|
|
60
|
+
return deferredSessions.has(session);
|
|
61
|
+
}
|
|
@@ -224,6 +224,16 @@ export interface AgentkitDeployAccepted {
|
|
|
224
224
|
status: string;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
/**
|
|
228
|
+
* One release component of a v2 multi-component deploy: a stable key (the
|
|
229
|
+
* child agent's slug) plus that agent's directory relative to the repo
|
|
230
|
+
* root.
|
|
231
|
+
*/
|
|
232
|
+
export interface AgentkitDeployComponent {
|
|
233
|
+
componentKey: string;
|
|
234
|
+
agentPath?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
227
237
|
/** Customer-visible v2 deploy status (`GET /internal/agentkit/deployments/:slug`). */
|
|
228
238
|
export type AgentkitCustomerStatus =
|
|
229
239
|
| "pending"
|
|
@@ -576,7 +586,9 @@ export class AgentServeDeployClient {
|
|
|
576
586
|
/**
|
|
577
587
|
* Create or redeploy onto the v2 control plane
|
|
578
588
|
* (`POST /internal/agentkit/deployments`). 202 means accepted; poll
|
|
579
|
-
* {@link getAgentkitDeploy} until a terminal status.
|
|
589
|
+
* {@link getAgentkitDeploy} until a terminal status. `components` deploys
|
|
590
|
+
* one application with several release components (one per agent
|
|
591
|
+
* directory); it is mutually exclusive with `agentPath`.
|
|
580
592
|
*/
|
|
581
593
|
async deployAgentkit(args: {
|
|
582
594
|
teamId: number;
|
|
@@ -584,12 +596,22 @@ export class AgentServeDeployClient {
|
|
|
584
596
|
gitRepoUrl: string;
|
|
585
597
|
gitRef?: string;
|
|
586
598
|
agentPath?: string;
|
|
599
|
+
components?: AgentkitDeployComponent[];
|
|
587
600
|
repositories?: string[];
|
|
588
601
|
}): Promise<AgentkitDeployAccepted> {
|
|
589
602
|
const slugError = validateDeploymentSlug(args.slug);
|
|
590
603
|
if (slugError !== undefined) {
|
|
591
604
|
throw new DeployApiError(slugError, 400);
|
|
592
605
|
}
|
|
606
|
+
for (const component of args.components ?? []) {
|
|
607
|
+
const keyError = validateDeploymentSlug(component.componentKey);
|
|
608
|
+
if (keyError !== undefined) {
|
|
609
|
+
throw new DeployApiError(
|
|
610
|
+
`Invalid component key ${JSON.stringify(component.componentKey)}: use lowercase letters, digits, '-' or '_' (max 64 chars)`,
|
|
611
|
+
400
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
593
615
|
const raw = await this.request({
|
|
594
616
|
method: "POST",
|
|
595
617
|
path: "/internal/agentkit/deployments",
|
|
@@ -599,6 +621,7 @@ export class AgentServeDeployClient {
|
|
|
599
621
|
gitRepoUrl: args.gitRepoUrl,
|
|
600
622
|
gitRef: args.gitRef,
|
|
601
623
|
agentPath: args.agentPath,
|
|
624
|
+
components: args.components,
|
|
602
625
|
repositories:
|
|
603
626
|
args.repositories !== undefined && args.repositories.length > 0
|
|
604
627
|
? args.repositories
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-level file storage for v2 hosted engines.
|
|
3
|
+
*
|
|
4
|
+
* Hosted deployments carry two file stores with different audiences:
|
|
5
|
+
*
|
|
6
|
+
* - the **user-level** store (`AGENT_SERVE_STORE_SOURCE_ID`) holds
|
|
7
|
+
* author-facing bytes — `ctx.host.files`, skills, memory, artifacts — and
|
|
8
|
+
* is browsable from the dashboard and mountable on cloud-runtime VMs;
|
|
9
|
+
* - the **framework-level** store (`AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID`)
|
|
10
|
+
* holds engine-owned session state — session records, event transcripts,
|
|
11
|
+
* the continuation index, and the SDK conversation mirror — and is never
|
|
12
|
+
* exposed to authors or models.
|
|
13
|
+
*
|
|
14
|
+
* This module puts framework session state on the framework store. The
|
|
15
|
+
* {@link storageKeys} scheme was designed for exactly this: keys are a
|
|
16
|
+
* stable versioned contract that sinks may route on, so
|
|
17
|
+
* {@link composeFrameworkStorage} splits one {@link StorageConfig} by key
|
|
18
|
+
* kind — `session` / `session-events` / `continuation` go to the framework
|
|
19
|
+
* file store (file-shaped; the store accepts values up to 64 MiB per
|
|
20
|
+
* object without chunking, versus the KV proxy's 1 MiB row cap), while
|
|
21
|
+
* author `kv`, eval runs, A/B samples, and conditional writes stay on the
|
|
22
|
+
* control-plane KV proxy (the dashboard reads them there, and CAS needs a
|
|
23
|
+
* database).
|
|
24
|
+
*
|
|
25
|
+
* v2 only, by construction: the composite is selected in
|
|
26
|
+
* framework-storage-selection.ts, which v1 never reaches with a
|
|
27
|
+
* platform-selected definition.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { FileSink } from "../files.js";
|
|
31
|
+
import type { StorageConfig, StorageEntry } from "../storage.js";
|
|
32
|
+
import { STORAGE_KEY_ROOT } from "../storage-protocol.js";
|
|
33
|
+
import type { JsonValue } from "../types.js";
|
|
34
|
+
import { mapBounded } from "./bounded.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Key kinds (`agentkit/v1/{agent}/{kind}/…`) that live on the framework
|
|
38
|
+
* file store. Everything else stays on the KV sink.
|
|
39
|
+
*/
|
|
40
|
+
const FRAMEWORK_FILE_KINDS: ReadonlySet<string> = new Set([
|
|
41
|
+
"session",
|
|
42
|
+
"session-events",
|
|
43
|
+
"continuation",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether a framework-minted key (or list prefix) belongs on the framework
|
|
48
|
+
* file store. Prefixes used by the coordinator are always at least
|
|
49
|
+
* kind-scoped (`…/{agent}/session/`), so a prefix routes wholly to one side.
|
|
50
|
+
*/
|
|
51
|
+
export function isFrameworkFileKey(key: string): boolean {
|
|
52
|
+
if (!key.startsWith(`${STORAGE_KEY_ROOT}/`)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const rest = key.slice(STORAGE_KEY_ROOT.length + 1);
|
|
56
|
+
const agentEnd = rest.indexOf("/");
|
|
57
|
+
if (agentEnd === -1) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const afterAgent = rest.slice(agentEnd + 1);
|
|
61
|
+
const kindEnd = afterAgent.indexOf("/");
|
|
62
|
+
const kind = kindEnd === -1 ? afterAgent : afterAgent.slice(0, kindEnd);
|
|
63
|
+
return FRAMEWORK_FILE_KINDS.has(kind);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The KV core over a {@link FileSink}: one JSON document per key. */
|
|
67
|
+
export type FileSinkStorageCore = Required<
|
|
68
|
+
Pick<StorageConfig, "put" | "get" | "delete" | "list">
|
|
69
|
+
>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Adapt a {@link FileSink} to the `defineStorage` KV core. Storage keys are
|
|
73
|
+
* framework-minted (URI-encoded, length-bounded segments — see
|
|
74
|
+
* {@link storageKeys}), so they are used as file keys verbatim; values are
|
|
75
|
+
* stored as JSON bytes. Parse failures throw: on the lazy-restore path a
|
|
76
|
+
* corrupt document must fail the follow-up rather than read as a miss, and
|
|
77
|
+
* the startup bulk restore already logs-and-skips a throwing `list`.
|
|
78
|
+
*/
|
|
79
|
+
/** Parallel per-key reads backing `list` (each is presign + object GET). */
|
|
80
|
+
const LIST_READ_CONCURRENCY = 8;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A storage key whose path the file store cannot represent (an empty, `.`,
|
|
84
|
+
* or `..` segment — possible only through caller-controlled segments such
|
|
85
|
+
* as continuation tokens or a caller-supplied session id). Such an object
|
|
86
|
+
* can never exist in the store, so reads are a definitive miss rather than
|
|
87
|
+
* a per-request 400 that the lazy-restore path would propagate forever.
|
|
88
|
+
*/
|
|
89
|
+
function isUnrepresentableFileKey(key: string): boolean {
|
|
90
|
+
return key
|
|
91
|
+
.split("/")
|
|
92
|
+
.some((segment) => segment === "" || segment === "." || segment === "..");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function fileSinkStorageCore(sink: FileSink): FileSinkStorageCore {
|
|
96
|
+
return {
|
|
97
|
+
async put(key, value): Promise<void> {
|
|
98
|
+
if (isUnrepresentableFileKey(key)) {
|
|
99
|
+
// Loud rather than silent: the coordinator logs and drops failed
|
|
100
|
+
// mirror writes (at-most-once), and awaited callers must not
|
|
101
|
+
// believe an impossible write landed.
|
|
102
|
+
throw new Error(
|
|
103
|
+
`framework file storage: key "${key}" has a path segment the store cannot represent`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
await sink.put(key, new TextEncoder().encode(JSON.stringify(value)));
|
|
107
|
+
},
|
|
108
|
+
async get(key): Promise<JsonValue | undefined> {
|
|
109
|
+
if (isUnrepresentableFileKey(key)) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const body = await sink.get(key);
|
|
113
|
+
if (body === undefined) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return decodeDocument(key, body instanceof Uint8Array ? body : body.body);
|
|
117
|
+
},
|
|
118
|
+
async delete(key): Promise<void> {
|
|
119
|
+
if (isUnrepresentableFileKey(key)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
await sink.delete(key);
|
|
123
|
+
},
|
|
124
|
+
async list(prefix): Promise<StorageEntry[]> {
|
|
125
|
+
const entries = await sink.list(prefix);
|
|
126
|
+
// FileSink.list returns metadata only, so each value is its own
|
|
127
|
+
// read; bound the fan-out — a bulk read here sits on restore paths.
|
|
128
|
+
const bodies = await mapBounded(entries, LIST_READ_CONCURRENCY, (entry) =>
|
|
129
|
+
sink.get(entry.key)
|
|
130
|
+
);
|
|
131
|
+
const out: StorageEntry[] = [];
|
|
132
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
133
|
+
const entry = entries[index];
|
|
134
|
+
const body = bodies[index];
|
|
135
|
+
if (entry === undefined || body === undefined) {
|
|
136
|
+
// Deleted between list and read; a miss is not corruption.
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
out.push({
|
|
140
|
+
key: entry.key,
|
|
141
|
+
value: decodeDocument(
|
|
142
|
+
entry.key,
|
|
143
|
+
body instanceof Uint8Array ? body : body.body
|
|
144
|
+
),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function decodeDocument(key: string, bytes: Uint8Array): JsonValue {
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(new TextDecoder().decode(bytes)) as JsonValue;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`framework file storage: stored document at "${key}" is not JSON (${
|
|
158
|
+
error instanceof Error ? error.message : String(error)
|
|
159
|
+
})`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Split one storage core by key kind: framework session state to `files`,
|
|
166
|
+
* everything else to `kv`. Spread over the KV backend when building the
|
|
167
|
+
* full definition so its `evals` / `abs` groups (and the cursor-hosted
|
|
168
|
+
* marker) ride along untouched; wrap the KV `cas` group with
|
|
169
|
+
* {@link frameworkGuardedCas} rather than spreading it as-is.
|
|
170
|
+
*/
|
|
171
|
+
export function composeFrameworkStorage(input: {
|
|
172
|
+
kv: FileSinkStorageCore;
|
|
173
|
+
files: FileSinkStorageCore;
|
|
174
|
+
}): FileSinkStorageCore {
|
|
175
|
+
const route = (key: string): FileSinkStorageCore =>
|
|
176
|
+
isFrameworkFileKey(key) ? input.files : input.kv;
|
|
177
|
+
return {
|
|
178
|
+
put: (key, value, ctx) => route(key).put(key, value, ctx),
|
|
179
|
+
get: (key, ctx) => route(key).get(key, ctx),
|
|
180
|
+
delete: (key, ctx) => route(key).delete(key, ctx),
|
|
181
|
+
list: (prefix, ctx) => route(prefix).list(prefix, ctx),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The KV backend's conditional writes, refusing framework-file keys.
|
|
187
|
+
*
|
|
188
|
+
* CAS runs on the KV proxy (a database row version); the plain core routes
|
|
189
|
+
* framework session state to the file store. A future coordination path
|
|
190
|
+
* that CASes a `session/` or `continuation/` key would otherwise read and
|
|
191
|
+
* write a different store than the plain path — silent split-brain. Fail
|
|
192
|
+
* loudly instead; nothing in the package CASes those kinds today.
|
|
193
|
+
*/
|
|
194
|
+
export function frameworkGuardedCas(
|
|
195
|
+
cas: NonNullable<StorageConfig["cas"]>
|
|
196
|
+
): NonNullable<StorageConfig["cas"]> {
|
|
197
|
+
const refuse = (key: string): void => {
|
|
198
|
+
if (isFrameworkFileKey(key)) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`framework file storage: conditional writes are KV-only, but "${key}" routes to the framework file store`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
getWithVersion: (key, ctx) => {
|
|
206
|
+
refuse(key);
|
|
207
|
+
return cas.getWithVersion(key, ctx);
|
|
208
|
+
},
|
|
209
|
+
putIfAbsent: (key, value, ctx) => {
|
|
210
|
+
refuse(key);
|
|
211
|
+
return cas.putIfAbsent(key, value, ctx);
|
|
212
|
+
},
|
|
213
|
+
putIfVersion: (key, value, expectedVersion, ctx) => {
|
|
214
|
+
refuse(key);
|
|
215
|
+
return cas.putIfVersion(key, value, expectedVersion, ctx);
|
|
216
|
+
},
|
|
217
|
+
listKeys: (prefix, ctx) => {
|
|
218
|
+
refuse(prefix);
|
|
219
|
+
return cas.listKeys(prefix, ctx);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -2,19 +2,37 @@
|
|
|
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. When the deployment carries a
|
|
21
|
+
* **framework-level file store**
|
|
22
|
+
* (`AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID`), session records, event
|
|
23
|
+
* transcripts, and the continuation index live there — file-shaped state
|
|
24
|
+
* on file storage, apart from the author-facing user store — while
|
|
25
|
+
* `host.kv`, eval runs, A/B samples, and conditional writes stay on the
|
|
26
|
+
* control-plane KV proxy (see framework-file-storage.ts). Without one
|
|
27
|
+
* (a deploy predating framework-store provisioning), everything goes to
|
|
28
|
+
* the KV proxy as before. The API key resolves through the full
|
|
29
|
+
* credential chain on every call because hosted freeze snapshots listen
|
|
30
|
+
* unsigned and the binder drops the key file after restore.
|
|
31
|
+
* - **Self-hosted**: no mirror. `--state-root` is the durable copy, which
|
|
32
|
+
* is correct when the operator owns the disk.
|
|
33
|
+
*
|
|
34
|
+
* v1 keeps the authored definition (or nothing) so existing deployments are
|
|
35
|
+
* untouched while they run.
|
|
18
36
|
*
|
|
19
37
|
* Local disk is not part of this decision. `<stateRoot>/sessions/<id>/`
|
|
20
38
|
* already holds `session.json` and `events.ndjson` unconditionally, through
|
|
@@ -22,24 +40,88 @@
|
|
|
22
40
|
* pod's filesystem.
|
|
23
41
|
*/
|
|
24
42
|
|
|
25
|
-
import
|
|
43
|
+
import {
|
|
44
|
+
cursorFrameworkStoreSourceId,
|
|
45
|
+
cursorHostedFiles,
|
|
46
|
+
} from "../files-backends/cursor-hosted.js";
|
|
47
|
+
import { defineStorage, type StorageDefinition } from "../storage.js";
|
|
48
|
+
import { cursorHostedStorage } from "../storage-backends/cursor-hosted.js";
|
|
49
|
+
import type { AgentArchitecture } from "../types.js";
|
|
50
|
+
import { resolveApiKeySync } from "./cursor/credentials.js";
|
|
51
|
+
import { AGENT_SERVE_GENERATION_ENV_VAR } from "./cursor-relay-core.js";
|
|
52
|
+
import {
|
|
53
|
+
composeFrameworkStorage,
|
|
54
|
+
fileSinkStorageCore,
|
|
55
|
+
frameworkGuardedCas,
|
|
56
|
+
} from "./framework-file-storage.js";
|
|
26
57
|
|
|
27
58
|
export interface FrameworkStorageSelectionInput {
|
|
28
|
-
/** `agent/storage.ts`, when the author wrote one. */
|
|
59
|
+
/** `agent/storage.ts`, when the author wrote one (v1 only: discovery rejects it on v2). */
|
|
29
60
|
readonly authored: StorageDefinition | undefined;
|
|
61
|
+
/** {@link AgentConfig.architecture}; omitted means v1. */
|
|
62
|
+
readonly architecture?: AgentArchitecture | undefined;
|
|
63
|
+
/** Environment override for tests; defaults to `process.env`. */
|
|
64
|
+
readonly env?: Record<string, string | undefined>;
|
|
30
65
|
}
|
|
31
66
|
|
|
32
67
|
/**
|
|
33
68
|
* Pick the backend for framework session state.
|
|
34
69
|
*
|
|
35
70
|
* Returns `undefined` when there is no mirror, in which case the pod's local
|
|
36
|
-
* disk is the only copy
|
|
37
|
-
* deployments.
|
|
71
|
+
* disk is the only copy.
|
|
38
72
|
*/
|
|
39
73
|
export function selectFrameworkStorageDefinition(
|
|
40
74
|
input: FrameworkStorageSelectionInput
|
|
41
75
|
): StorageDefinition | undefined {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
76
|
+
if (input.architecture !== "v2") {
|
|
77
|
+
// v1: the author decides, or there is no mirror.
|
|
78
|
+
return input.authored;
|
|
79
|
+
}
|
|
80
|
+
if (input.authored !== undefined) {
|
|
81
|
+
// Discovery rejects agent/storage.ts on v2 before an engine is built;
|
|
82
|
+
// an authored definition reaching this point is a wiring bug. Raise
|
|
83
|
+
// rather than silently discard what the author believes is configured.
|
|
84
|
+
throw new Error(
|
|
85
|
+
'defineStorage (agent/storage.ts) is not supported on architecture "v2": storage is platform-selected. Delete the file.'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const env = input.env ?? process.env;
|
|
89
|
+
if (env[AGENT_SERVE_GENERATION_ENV_VAR] === undefined) {
|
|
90
|
+
// Self-hosted v2: the state root is the durable copy.
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
// Hosted v2 pods wake per delivery: bulk-restoring up to the default
|
|
94
|
+
// 1000 sessions on every boot is waste (and against the file-store
|
|
95
|
+
// backend it is per-key HTTP round trips before the listener even
|
|
96
|
+
// comes up). Follow-ups restore lazily by continuation or session id;
|
|
97
|
+
// GET /v1/sessions lists from the sink either way.
|
|
98
|
+
const policy = { restore: "off" } as const;
|
|
99
|
+
// Full chain (env → key file → stored login): frozen engines listen
|
|
100
|
+
// unsigned and bind CURSOR_API_KEY_FILE at task time, after this
|
|
101
|
+
// definition is constructed.
|
|
102
|
+
const resolveApiKey = (): string | undefined => resolveApiKeySync()?.apiKey;
|
|
103
|
+
const kv = cursorHostedStorage({ resolveApiKey });
|
|
104
|
+
const frameworkStoreId = cursorFrameworkStoreSourceId(
|
|
105
|
+
env as NodeJS.ProcessEnv
|
|
106
|
+
);
|
|
107
|
+
if (frameworkStoreId === undefined) {
|
|
108
|
+
return defineStorage({ name: "cursor-hosted", policy, ...kv });
|
|
109
|
+
}
|
|
110
|
+
// Framework session state on the framework-level file store; author kv,
|
|
111
|
+
// evals, and A/B tables stay on the KV proxy (spread from `kv`, which
|
|
112
|
+
// also carries the cursor-hosted marker for GET /v1/sessions).
|
|
113
|
+
return defineStorage({
|
|
114
|
+
name: "cursor-hosted (framework files)",
|
|
115
|
+
policy,
|
|
116
|
+
...kv,
|
|
117
|
+
...composeFrameworkStorage({
|
|
118
|
+
kv,
|
|
119
|
+
files: fileSinkStorageCore(
|
|
120
|
+
cursorHostedFiles({ storeSourceId: frameworkStoreId, resolveApiKey })
|
|
121
|
+
),
|
|
122
|
+
}),
|
|
123
|
+
// CAS stays on the KV proxy but must refuse framework-file keys, or a
|
|
124
|
+
// future coordination path would split-brain against the plain core.
|
|
125
|
+
cas: frameworkGuardedCas(kv.cas),
|
|
126
|
+
});
|
|
45
127
|
}
|
|
@@ -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
|
}
|