@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,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-neutral PR URL parse. GitHub.com plus the Origin page shapes
|
|
3
|
+
* humans paste: cursor.com/codebase, review.cursor.com/pr, origin.cursor.com.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type PrProvider = "github" | "origin";
|
|
7
|
+
|
|
8
|
+
export type ParsedPrUrl = {
|
|
9
|
+
provider: PrProvider;
|
|
10
|
+
owner: string;
|
|
11
|
+
repo: string;
|
|
12
|
+
number: number;
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const ORIGIN_PAGE = /^origin(-[a-z0-9]+)?\.cursor\.com$/i;
|
|
17
|
+
|
|
18
|
+
function positiveInt(raw: string | undefined): number | undefined {
|
|
19
|
+
if (raw === undefined) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const n = Number(raw);
|
|
23
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return n;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parsed(
|
|
30
|
+
provider: PrProvider,
|
|
31
|
+
owner: string | undefined,
|
|
32
|
+
repo: string | undefined,
|
|
33
|
+
number: number | undefined
|
|
34
|
+
): ParsedPrUrl | undefined {
|
|
35
|
+
if (owner === undefined || repo === undefined || number === undefined) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const url =
|
|
39
|
+
provider === "origin"
|
|
40
|
+
? `https://cursor.com/codebase/${owner}/${repo}/pull/${number}`
|
|
41
|
+
: `https://github.com/${owner}/${repo}/pull/${number}`;
|
|
42
|
+
return { provider, owner, repo, number, url };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Strip Slack mrkdwn wrappers and trailing punctuation before `URL`. */
|
|
46
|
+
function normalizeRaw(raw: string): string {
|
|
47
|
+
return raw
|
|
48
|
+
.trim()
|
|
49
|
+
.replace(/^[<[]+/, "")
|
|
50
|
+
.replace(/[>\].,;]+$/, "");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parsePrUrl(raw: string): ParsedPrUrl | undefined {
|
|
54
|
+
let url: URL;
|
|
55
|
+
try {
|
|
56
|
+
url = new URL(normalizeRaw(raw));
|
|
57
|
+
} catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
62
|
+
const host = url.hostname.toLowerCase();
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
(host === "github.com" || host === "www.github.com") &&
|
|
66
|
+
parts.length >= 4 &&
|
|
67
|
+
parts[2] === "pull"
|
|
68
|
+
) {
|
|
69
|
+
return parsed("github", parts[0], parts[1], positiveInt(parts[3]));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (ORIGIN_PAGE.test(host) && parts.length >= 4 && parts[2] === "github") {
|
|
73
|
+
return parsed("origin", parts[0], parts[1], positiveInt(parts[3]));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
(host === "cursor.com" || host === "www.cursor.com") &&
|
|
78
|
+
parts[0] === "codebase" &&
|
|
79
|
+
parts.length >= 5 &&
|
|
80
|
+
parts[3] === "pull"
|
|
81
|
+
) {
|
|
82
|
+
return parsed("origin", parts[1], parts[2], positiveInt(parts[4]));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (host === "review.cursor.com" && parts[0] === "pr") {
|
|
86
|
+
if (parts.length >= 5 && parts[3] === "github") {
|
|
87
|
+
return parsed("origin", parts[1], parts[2], positiveInt(parts[4]));
|
|
88
|
+
}
|
|
89
|
+
if (parts.length >= 4) {
|
|
90
|
+
return parsed("origin", parts[1], parts[2], positiveInt(parts[3]));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
createAgentPlatform,
|
|
17
17
|
type LocalAgentStore,
|
|
18
18
|
type ModelSelection,
|
|
19
|
+
type Run,
|
|
19
20
|
type RunResult,
|
|
20
21
|
type SDKAgent,
|
|
21
22
|
} from "@cursor/sdk";
|
|
@@ -29,11 +30,20 @@ import {
|
|
|
29
30
|
type TurnUsage,
|
|
30
31
|
} from "../types.js";
|
|
31
32
|
import { mapSdkTokenUsage, usageTokenSum } from "./cloud-turn-cost.js";
|
|
33
|
+
import type { ConversationMirror } from "./conversation-mirror.js";
|
|
32
34
|
import { resolveApiKeySync } from "./cursor/credentials.js";
|
|
35
|
+
import { describeError } from "./describe-error.js";
|
|
36
|
+
|
|
37
|
+
/** Mirrors @cursor/sdk's SteerAckOutcome; the published dist may predate it. */
|
|
38
|
+
export type SteerAckOutcome = "complete_delivered" | "revert_to_followup";
|
|
33
39
|
|
|
34
40
|
/** Internal seam between the session engine and the Cursor SDK harness. */
|
|
35
41
|
export interface AgentRunner {
|
|
36
42
|
runTurn(request: RunnerTurnRequest): Promise<TurnOutcome>;
|
|
43
|
+
steer?(request: {
|
|
44
|
+
sessionId: string;
|
|
45
|
+
text: string;
|
|
46
|
+
}): Promise<SteerAckOutcome>;
|
|
37
47
|
/**
|
|
38
48
|
* Resolve a local workspace before any session needs it, so the first turn
|
|
39
49
|
* is not the one that pays for it. Best effort, hence the reported outcome:
|
|
@@ -117,6 +127,13 @@ export interface CursorSdkRunnerOptions {
|
|
|
117
127
|
* `LocalAgentStore` for heavy multi-session concurrency.
|
|
118
128
|
*/
|
|
119
129
|
store?: LocalAgentStore;
|
|
130
|
+
/**
|
|
131
|
+
* Durable mirror for local conversations (v2 hosted engines). Hydrated
|
|
132
|
+
* before `Agent.resume` so a follow-up landing on a fresh pod finds the
|
|
133
|
+
* conversation, and persisted — awaited — after each local turn settles,
|
|
134
|
+
* because a hosted pod can be parked right after the execution reply.
|
|
135
|
+
*/
|
|
136
|
+
conversationMirror?: ConversationMirror;
|
|
120
137
|
/** Diagnostics for degraded paths (resume-miss fallback). */
|
|
121
138
|
logger?: (line: string) => void;
|
|
122
139
|
/**
|
|
@@ -185,6 +202,7 @@ function retryReason(
|
|
|
185
202
|
|
|
186
203
|
export class CursorSdkRunner implements AgentRunner {
|
|
187
204
|
private readonly agents = new Map<string, CachedAgentHandle>();
|
|
205
|
+
private readonly activeRuns = new Map<string, Run>();
|
|
188
206
|
private readonly stores = new Map<string, Promise<LocalAgentStore>>();
|
|
189
207
|
private readonly prewarmReleases: Array<() => Promise<void>> = [];
|
|
190
208
|
|
|
@@ -314,6 +332,7 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
314
332
|
) {
|
|
315
333
|
await request.onRequestId(run.requestId);
|
|
316
334
|
}
|
|
335
|
+
this.activeRuns.set(request.sessionId, run);
|
|
317
336
|
try {
|
|
318
337
|
const result = await run.wait();
|
|
319
338
|
let outcome = toTurnOutcome(result);
|
|
@@ -338,7 +357,25 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
338
357
|
}
|
|
339
358
|
return outcome;
|
|
340
359
|
} finally {
|
|
360
|
+
if (this.activeRuns.get(request.sessionId) === run) {
|
|
361
|
+
this.activeRuns.delete(request.sessionId);
|
|
362
|
+
}
|
|
341
363
|
signal?.removeEventListener("abort", onAbort);
|
|
364
|
+
// Mirror the conversation before replying: on hosted v2 the pod can
|
|
365
|
+
// be parked as soon as the execution reply lands, so a
|
|
366
|
+
// fire-and-forget write would race VM teardown. In the `finally` so
|
|
367
|
+
// a `wait()` that THROWS (transport death mid-turn, an abort with
|
|
368
|
+
// no follow-up) still persists the checkpoints the turn already
|
|
369
|
+
// wrote — the store is local SQLite, safe to read on the throw
|
|
370
|
+
// path, and persistConversation swallows its own errors. Guarded
|
|
371
|
+
// here (not just inside the helper) so unmirrored turns keep their
|
|
372
|
+
// exact post-wait sequencing.
|
|
373
|
+
if (
|
|
374
|
+
request.runtime === "local" &&
|
|
375
|
+
this.options.conversationMirror !== undefined
|
|
376
|
+
) {
|
|
377
|
+
await this.persistConversation(request, agent.agentId);
|
|
378
|
+
}
|
|
342
379
|
}
|
|
343
380
|
} catch (error) {
|
|
344
381
|
// Drop the cached agent so a dead Connect/HTTP2 session cannot poison
|
|
@@ -348,6 +385,28 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
348
385
|
}
|
|
349
386
|
}
|
|
350
387
|
|
|
388
|
+
async steer(request: {
|
|
389
|
+
sessionId: string;
|
|
390
|
+
text: string;
|
|
391
|
+
}): Promise<SteerAckOutcome> {
|
|
392
|
+
const run = this.activeRuns.get(request.sessionId) as
|
|
393
|
+
| (Run & {
|
|
394
|
+
steer?: (text: string) => Promise<SteerAckOutcome>;
|
|
395
|
+
})
|
|
396
|
+
| undefined;
|
|
397
|
+
if (run?.steer === undefined) {
|
|
398
|
+
return "revert_to_followup";
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
return await run.steer(request.text);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
this.log(
|
|
404
|
+
`[agent-sdk] session ${request.sessionId}: steer failed: ${describeError(error)}`
|
|
405
|
+
);
|
|
406
|
+
return "revert_to_followup";
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
351
410
|
/**
|
|
352
411
|
* Build the local executor for a workspace before any session asks for it.
|
|
353
412
|
* The warmed executor is reference counted, so the lease is held until
|
|
@@ -469,6 +528,9 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
469
528
|
private async buildLocalOptions(
|
|
470
529
|
request: RunnerPrewarmRequest
|
|
471
530
|
): Promise<AgentOptions> {
|
|
531
|
+
// Not resolveStore: an injected store is used synchronously (no
|
|
532
|
+
// microtask, no thenable assimilation), exactly as before the mirror
|
|
533
|
+
// existed.
|
|
472
534
|
const store =
|
|
473
535
|
this.options.store ?? (await this.getStore(request.runnerStateDir));
|
|
474
536
|
return {
|
|
@@ -507,6 +569,15 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
507
569
|
}
|
|
508
570
|
|
|
509
571
|
const agentId = request.sdkAgentId ?? request.sessionId;
|
|
572
|
+
const mirror = this.options.conversationMirror;
|
|
573
|
+
if (mirror !== undefined) {
|
|
574
|
+
// Host replacement: restore the conversation from the durable mirror
|
|
575
|
+
// before resuming, so the thread continues instead of starting fresh.
|
|
576
|
+
// A sink failure propagates and fails the turn (retryable) — reading
|
|
577
|
+
// an outage as "nothing mirrored" would silently fork the thread.
|
|
578
|
+
const store = await this.resolveStore(request);
|
|
579
|
+
await mirror.hydrate(store, agentId);
|
|
580
|
+
}
|
|
510
581
|
try {
|
|
511
582
|
return await Agent.resume(agentId, options);
|
|
512
583
|
} catch (error) {
|
|
@@ -570,6 +641,38 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
570
641
|
return Agent.create(options);
|
|
571
642
|
}
|
|
572
643
|
|
|
644
|
+
private resolveStore(
|
|
645
|
+
request: Pick<RunnerPrewarmRequest, "runnerStateDir">
|
|
646
|
+
): Promise<LocalAgentStore> {
|
|
647
|
+
return this.options.store !== undefined
|
|
648
|
+
? Promise.resolve(this.options.store)
|
|
649
|
+
: this.getStore(request.runnerStateDir);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Mirror the settled turn's conversation. Persist failures are logged and
|
|
654
|
+
* dropped — a durable-mirror outage must not fail a turn that already ran.
|
|
655
|
+
*/
|
|
656
|
+
private async persistConversation(
|
|
657
|
+
request: RunnerTurnRequest,
|
|
658
|
+
sdkAgentId: string
|
|
659
|
+
): Promise<void> {
|
|
660
|
+
const mirror = this.options.conversationMirror;
|
|
661
|
+
if (mirror === undefined) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
const store = await this.resolveStore(request);
|
|
666
|
+
await mirror.persist(store, sdkAgentId);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
this.log(
|
|
669
|
+
`[agent-sdk] conversation mirror: persist for ${sdkAgentId} failed: ${
|
|
670
|
+
error instanceof Error ? error.message : String(error)
|
|
671
|
+
}`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
573
676
|
private getStore(stateDir: string): Promise<LocalAgentStore> {
|
|
574
677
|
let storePromise = this.stores.get(stateDir);
|
|
575
678
|
if (storePromise === undefined) {
|
package/src/internal/server.ts
CHANGED
|
@@ -51,6 +51,7 @@ import type {
|
|
|
51
51
|
ServeOptions,
|
|
52
52
|
} from "../types.js";
|
|
53
53
|
import { V2EngineError } from "./continuation-identity.js";
|
|
54
|
+
import { selectConversationMirror } from "./conversation-mirror.js";
|
|
54
55
|
import {
|
|
55
56
|
type CursorAccountConnectionRef,
|
|
56
57
|
CursorAccountMcpBridge,
|
|
@@ -516,12 +517,21 @@ export async function startServer(
|
|
|
516
517
|
"[agent-sdk] warning: --allow-anonymous admits every HTTP caller as the same principal; use only on a trusted network (e.g. Tailscale) and prefer --bearer-token for shared hosts"
|
|
517
518
|
);
|
|
518
519
|
}
|
|
520
|
+
// v2 hosted engines mirror the SDK conversation store to the deployment's
|
|
521
|
+
// shared file storage so a fresh pod resumes threads instead of forking
|
|
522
|
+
// them. Hosted serve is single-agent; a multi-mount serve (dev) has no
|
|
523
|
+
// single agent name to mint keys under and does not mirror.
|
|
524
|
+
const conversationMirror =
|
|
525
|
+
mounts.length === 1 && mounts[0] !== undefined
|
|
526
|
+
? selectConversationMirror({ project: mounts[0].project, logger })
|
|
527
|
+
: undefined;
|
|
519
528
|
const sdkRunner =
|
|
520
529
|
options.runner ??
|
|
521
530
|
new CursorSdkRunner({
|
|
522
531
|
...(options.apiKey === undefined || options.apiKey.trim() === ""
|
|
523
532
|
? {}
|
|
524
533
|
: { apiKey: options.apiKey }),
|
|
534
|
+
...(conversationMirror === undefined ? {} : { conversationMirror }),
|
|
525
535
|
logger,
|
|
526
536
|
});
|
|
527
537
|
// Grok Bot turns run on Cursor's hosted Grok Bot (Sand) harness through
|
|
@@ -1276,6 +1286,8 @@ function mountAgentRoutes(
|
|
|
1276
1286
|
channelId,
|
|
1277
1287
|
hostedSessionId,
|
|
1278
1288
|
}),
|
|
1289
|
+
drainSessionDurability: (sessionId) =>
|
|
1290
|
+
engine.drainSessionDurability(sessionId),
|
|
1279
1291
|
});
|
|
1280
1292
|
}
|
|
1281
1293
|
// Playground lists/opens Slack (and other channel) sessions across
|
|
@@ -113,6 +113,10 @@ import {
|
|
|
113
113
|
createHostSlackClient,
|
|
114
114
|
type HostPlatforms,
|
|
115
115
|
} from "./host-platforms.js";
|
|
116
|
+
import {
|
|
117
|
+
HostedAdmissionLocalSendError,
|
|
118
|
+
isDuringHostedAdmission,
|
|
119
|
+
} from "./hosted-admission-context.js";
|
|
116
120
|
import {
|
|
117
121
|
formatContinuationToken,
|
|
118
122
|
newContinuationKey,
|
|
@@ -132,10 +136,11 @@ import {
|
|
|
132
136
|
SCHEDULE_CHANNEL_PREFIX,
|
|
133
137
|
} from "./prompt-context.js";
|
|
134
138
|
import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections.js";
|
|
135
|
-
import type { AgentRunner } from "./sdk-runner.js";
|
|
139
|
+
import type { AgentRunner, SteerAckOutcome } from "./sdk-runner.js";
|
|
136
140
|
import { sessionExternalLinks } from "./session-links.js";
|
|
137
141
|
import { SessionStore } from "./session-store.js";
|
|
138
142
|
import { syncSkillsToStore } from "./skills-sync.js";
|
|
143
|
+
import { steeringEnabled } from "./steering.js";
|
|
139
144
|
import { StorageCoordinator } from "./storage-coordinator.js";
|
|
140
145
|
import type { FrameworkSessionStore } from "./storage-roles.js";
|
|
141
146
|
import { decideToolCall, resolveEffect } from "./tool-policy.js";
|
|
@@ -465,6 +470,7 @@ export class SessionEngine {
|
|
|
465
470
|
// for why the choice does not belong to the agent author.
|
|
466
471
|
const frameworkStorage = selectFrameworkStorageDefinition({
|
|
467
472
|
authored: options.project.storage,
|
|
473
|
+
architecture: options.project.agent.architecture,
|
|
468
474
|
});
|
|
469
475
|
this.storage =
|
|
470
476
|
frameworkStorage === undefined
|
|
@@ -693,6 +699,9 @@ export class SessionEngine {
|
|
|
693
699
|
) {
|
|
694
700
|
return;
|
|
695
701
|
}
|
|
702
|
+
if (steeringEnabled()) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
696
705
|
try {
|
|
697
706
|
await materializeSlackNudgeCarrier({
|
|
698
707
|
harnessCwd,
|
|
@@ -811,6 +820,13 @@ export class SessionEngine {
|
|
|
811
820
|
message: string,
|
|
812
821
|
options: EngineSendOptions = {}
|
|
813
822
|
): Promise<ChannelSession> {
|
|
823
|
+
if (isDuringHostedAdmission()) {
|
|
824
|
+
// Only sends that bypassed the admission capture stub (typically a
|
|
825
|
+
// startup-captured `onStart` send) reach the engine inside the
|
|
826
|
+
// admission window; running one would leak an invisible local session
|
|
827
|
+
// on a sessionless pod.
|
|
828
|
+
throw new HostedAdmissionLocalSendError();
|
|
829
|
+
}
|
|
814
830
|
const identity =
|
|
815
831
|
channelId === HTTP_CHANNEL_ID
|
|
816
832
|
? httpContinuationIdentity(this.project.httpChannel)
|
|
@@ -1347,18 +1363,52 @@ export class SessionEngine {
|
|
|
1347
1363
|
});
|
|
1348
1364
|
}
|
|
1349
1365
|
|
|
1366
|
+
private async steerIntoRunningTurn(
|
|
1367
|
+
sessionId: string,
|
|
1368
|
+
message: string
|
|
1369
|
+
): Promise<SteerAckOutcome> {
|
|
1370
|
+
if (this.runner.steer === undefined) {
|
|
1371
|
+
return "revert_to_followup";
|
|
1372
|
+
}
|
|
1373
|
+
try {
|
|
1374
|
+
return await this.runner.steer({ sessionId, text: message });
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
this.logger(
|
|
1377
|
+
`[session] steer failed for ${sessionId}: ${describeError(error)}`
|
|
1378
|
+
);
|
|
1379
|
+
return "revert_to_followup";
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1350
1383
|
private async admitTurn(
|
|
1351
1384
|
sessionId: string,
|
|
1352
1385
|
message: string,
|
|
1353
1386
|
options: AdmitTurnOptions = {}
|
|
1354
1387
|
): Promise<boolean> {
|
|
1355
1388
|
const admission = options.admission ?? "preempt";
|
|
1389
|
+
if (
|
|
1390
|
+
admission === "coalesce" &&
|
|
1391
|
+
steeringEnabled() &&
|
|
1392
|
+
this.isTurnRunning(sessionId)
|
|
1393
|
+
) {
|
|
1394
|
+
const toolCtx = this.activeToolContexts.get(sessionId);
|
|
1395
|
+
const steerOutcome = await this.steerIntoRunningTurn(sessionId, message);
|
|
1396
|
+
if (steerOutcome === "complete_delivered") {
|
|
1397
|
+
this.logger(`[session] steered into running turn ${sessionId}`);
|
|
1398
|
+
if (toolCtx !== undefined) {
|
|
1399
|
+
toolCtx.emit({ type: "message.received", data: { text: message } });
|
|
1400
|
+
toolCtx.emit({ type: "nudge.delivered", data: {} });
|
|
1401
|
+
}
|
|
1402
|
+
return true;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
// Re-checked after the steer: a turn that ended during it falls through
|
|
1406
|
+
// and this message starts the next turn itself. Enqueueing here instead
|
|
1407
|
+
// would orphan it — the completion drain already ran.
|
|
1356
1408
|
if (admission === "coalesce" && this.isTurnRunning(sessionId)) {
|
|
1357
1409
|
this.enqueueCoalesced(sessionId, {
|
|
1358
1410
|
message,
|
|
1359
|
-
|
|
1360
|
-
? {}
|
|
1361
|
-
: { sourceTs: options.sourceTs }),
|
|
1411
|
+
sourceTs: options.sourceTs,
|
|
1362
1412
|
});
|
|
1363
1413
|
this.logger(`[session] coalesce enqueue ${sessionId}`);
|
|
1364
1414
|
return true;
|
|
@@ -3382,6 +3432,28 @@ export class SessionEngine {
|
|
|
3382
3432
|
// Session handles
|
|
3383
3433
|
// ==========================================================================
|
|
3384
3434
|
|
|
3435
|
+
/**
|
|
3436
|
+
* Wait until everything this session has already emitted is durably
|
|
3437
|
+
* mirrored: its dispatch chain first (the chain's tail runs the turn-end
|
|
3438
|
+
* storage flush, so the record carries the boundary event's handler
|
|
3439
|
+
* mutations), then the storage delivery queue. Hosted v2 awaits this
|
|
3440
|
+
* before the execution reply — the pod can be parked the moment the
|
|
3441
|
+
* reply lands, and an unflushed session record would strand a
|
|
3442
|
+
* conversation whose blobs persisted but whose record (which selects the
|
|
3443
|
+
* conversation to hydrate) never reached the sink. No-op without a
|
|
3444
|
+
* storage mirror.
|
|
3445
|
+
*/
|
|
3446
|
+
async drainSessionDurability(sessionId: string): Promise<void> {
|
|
3447
|
+
if (this.storage === undefined) {
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
const chain = this.dispatchChains.get(sessionId);
|
|
3451
|
+
if (chain !== undefined) {
|
|
3452
|
+
await chain.catch(() => {});
|
|
3453
|
+
}
|
|
3454
|
+
await this.storage.whenIdle();
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3385
3457
|
async waitForCompletion(sessionId: string): Promise<TurnOutcome> {
|
|
3386
3458
|
// Drain admission first so pendingTurns → activeTurns is visible before we
|
|
3387
3459
|
// decide the session is idle (reminders use this for queueBehindTurn).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy-time switch between the two mid-turn follow-up carriers.
|
|
3
|
+
*
|
|
4
|
+
* With ENABLE_STEERING set ("1"/"true"), a Slack follow-up arriving mid-turn
|
|
5
|
+
* is steered into the running turn via Run.steer and the nudge-carrier file
|
|
6
|
+
* store is left uninstalled. Unset (the default), the pre-steering behavior
|
|
7
|
+
* is fully preserved: the carrier is materialized and follow-ups ride the
|
|
8
|
+
* nudge store to the running turn's next tool boundary.
|
|
9
|
+
*
|
|
10
|
+
* Read per call rather than cached so tests can flip it, and because this is
|
|
11
|
+
* a rollout switch: once steering has proven out in production the flag and
|
|
12
|
+
* the carrier both go away.
|
|
13
|
+
*/
|
|
14
|
+
export function steeringEnabled(): boolean {
|
|
15
|
+
const value = process.env.ENABLE_STEERING?.trim().toLowerCase();
|
|
16
|
+
return value === "1" || value === "true";
|
|
17
|
+
}
|
|
@@ -76,6 +76,14 @@ export interface CursorHostedStorageOptions {
|
|
|
76
76
|
baseUrl?: string;
|
|
77
77
|
/** Bearer credential (default: `CURSOR_API_KEY`). */
|
|
78
78
|
apiKey?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Lazy credential, re-resolved on every call and preferred over
|
|
81
|
+
* {@link apiKey}: hosted freeze snapshots listen unsigned and bind the
|
|
82
|
+
* key file after serve start, so a value captured at construct time
|
|
83
|
+
* would be permanently absent. Used by the platform-selected v2
|
|
84
|
+
* defaults; kept separate so `apiKey` stays a plain string for authors.
|
|
85
|
+
*/
|
|
86
|
+
resolveApiKey?: () => string | undefined;
|
|
79
87
|
/** Injected for tests. */
|
|
80
88
|
fetch?: typeof fetch;
|
|
81
89
|
}
|
|
@@ -180,7 +188,8 @@ function toCasResult(response: KvCasResponse): KvCasResult {
|
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
function resolveApiKey(options: CursorHostedStorageOptions): string {
|
|
183
|
-
const
|
|
191
|
+
const configured = options.resolveApiKey?.() ?? options.apiKey;
|
|
192
|
+
const key = (configured ?? process.env.CURSOR_API_KEY)?.trim();
|
|
184
193
|
if (key === undefined || key === "") {
|
|
185
194
|
throw new Error(
|
|
186
195
|
"cursorHostedStorage requires CURSOR_API_KEY (deployment pod credential on Cursor hosting)"
|
package/src/types.ts
CHANGED
|
@@ -1262,7 +1262,8 @@ export type ConnectionConfig = (
|
|
|
1262
1262
|
* Local runtime only, like all server tools: a `runtime: "cloud"` agent
|
|
1263
1263
|
* fails discovery, and hybrid per-send cloud sessions refuse the turn.
|
|
1264
1264
|
* The listing runs once per turn; point this at endpoints that can
|
|
1265
|
-
* answer `tools/list` cheaply.
|
|
1265
|
+
* answer `tools/list` cheaply. Tools whose MCP `annotations.title` is
|
|
1266
|
+
* `do-not-call` are omitted (vendor host-UI / MCP Apps helpers).
|
|
1266
1267
|
*/
|
|
1267
1268
|
advertiseTools?: boolean;
|
|
1268
1269
|
/**
|
|
@@ -58,7 +58,7 @@ policy only) opts paths out entirely.
|
|
|
58
58
|
- One model turn per PR head assesses the diff against the base rubric
|
|
59
59
|
(`agent/lib/owners/prompts/base-review.md`) plus matched rule prompts.
|
|
60
60
|
- The model records a verdict with `record_review`; the host recomputes
|
|
61
|
-
the policy and applies the deterministic decision
|
|
61
|
+
the policy and applies the deterministic decision: approve, or request
|
|
62
62
|
the policy owners (max 2, never the author) and leave one comment it
|
|
63
63
|
keeps updated.
|
|
64
64
|
- The model never writes to GitHub. Broken policy files fail closed with
|
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
import { homedir } from "node:os";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import { defineAgent } from "@cursor/july";
|
|
4
2
|
|
|
5
3
|
export default defineAgent({
|
|
6
|
-
model: {
|
|
7
|
-
id: "grok-4.5",
|
|
8
|
-
params: [
|
|
9
|
-
{ id: "effort", value: "high" },
|
|
10
|
-
{ id: "fast", value: "true" },
|
|
11
|
-
],
|
|
12
|
-
},
|
|
13
4
|
tools: ["read", "grep", "glob", "ls"],
|
|
14
5
|
local: {
|
|
15
6
|
sandbox: true,
|
|
16
|
-
cwd: join(homedir(), ".cache", "agent-serve", "agentic-owners"),
|
|
17
7
|
},
|
|
18
8
|
});
|
|
@@ -22,10 +22,8 @@ const REVIEW_ACTIONS = new Set([
|
|
|
22
22
|
let log: (line: string) => void = () => {};
|
|
23
23
|
|
|
24
24
|
export default githubChannel({
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// GitHub App setup. Empty repos boots without the binding so a fresh
|
|
28
|
-
// scaffold's `dev` / `eval` run; use `agent-sdk github replay` there.
|
|
25
|
+
// Empty `repos` skips the Cursor GitHub binding so `dev` / `eval` boot
|
|
26
|
+
// on a fresh scaffold.
|
|
29
27
|
cursorAccount: config.repos.length > 0 ? { repos: config.repos } : undefined,
|
|
30
28
|
// The model never writes to GitHub; record_review actuates host-side.
|
|
31
29
|
deliverReplies: false,
|
|
@@ -33,9 +31,7 @@ export default githubChannel({
|
|
|
33
31
|
log = (line) => args.logger(`[agentic-owners] ${line}`);
|
|
34
32
|
},
|
|
35
33
|
onPullRequest: async (ctx, pr) => {
|
|
36
|
-
// A base retarget re-resolves
|
|
37
|
-
// and the governing policy files both changed, so the old verdict — and
|
|
38
|
-
// any approval it produced — no longer covers this PR. The fresh
|
|
34
|
+
// A base retarget re-resolves policy against the new base. The fresh
|
|
39
35
|
// actuation dismisses a leftover approve when the new decision holds.
|
|
40
36
|
const baseRetargeted =
|
|
41
37
|
pr.action === "edited" && pr.baseRefChangedFrom !== undefined;
|
|
@@ -54,9 +50,8 @@ export default githubChannel({
|
|
|
54
50
|
try {
|
|
55
51
|
const state = await loadReviewState(forge, log);
|
|
56
52
|
if (!state.enrolled) {
|
|
57
|
-
// Enrollment can end while an agent approval is live
|
|
58
|
-
//
|
|
59
|
-
// toward branch protection under a policy that never granted it.
|
|
53
|
+
// Enrollment can end while an agent approval is live; dismiss it
|
|
54
|
+
// instead of leaving it counting toward branch protection.
|
|
60
55
|
return {
|
|
61
56
|
task: () =>
|
|
62
57
|
dismissStaleApprovals(
|
|
@@ -69,14 +64,10 @@ export default githubChannel({
|
|
|
69
64
|
auth: defaultGitHubAuth(ctx),
|
|
70
65
|
message: renderReviewWake(state),
|
|
71
66
|
title: `Review ${ctx.repository.fullName}#${pr.number}`,
|
|
72
|
-
// Freeze the assessed head host-side: record_review reads this file
|
|
73
|
-
// instead of trusting a model-supplied SHA, so a mid-turn push (or a
|
|
74
|
-
// prompt-injected SHA) can never line up with the live head.
|
|
75
67
|
workspaceFiles: { "pr/head.sha": state.pr.headSha },
|
|
76
68
|
};
|
|
77
69
|
} catch (error) {
|
|
78
70
|
if (error instanceof AgenticOwnersConfigValidationError) {
|
|
79
|
-
// Fail closed but visibly: comment the issues, review nothing.
|
|
80
71
|
return { task: () => reportPolicyError(forge, error) };
|
|
81
72
|
}
|
|
82
73
|
throw error;
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The one file to edit: which GitHub repositories the agent reviews.
|
|
3
|
-
*
|
|
4
|
-
* Setting it streams PR events and a repo-scoped GitHub credential through
|
|
5
|
-
* your signed-in Cursor account (`agent-sdk login`) — no webhook or GitHub
|
|
6
|
-
* App setup. While empty, the agent still runs locally against replayed
|
|
7
|
-
* PRs (`agent-sdk github replay <pr-url>`).
|
|
8
|
-
*/
|
|
9
1
|
const config = {
|
|
10
2
|
repos: [] as string[], // e.g. ["acme/api"]
|
|
11
3
|
};
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
// Policy
|
|
2
|
-
//
|
|
3
|
-
// **base ref**, so a PR cannot exempt itself by editing its own governance
|
|
4
|
-
// files. Mirrors the Agentic Owners pipeline's `resolvePolicyForPr`, minus
|
|
5
|
-
// the linguist-generated partition and content caching.
|
|
1
|
+
// Policy files and rule prompts are read from the PR's base ref so a PR
|
|
2
|
+
// cannot exempt itself by editing its own governance files.
|
|
6
3
|
|
|
7
4
|
import {
|
|
8
5
|
decideApproval,
|
|
@@ -297,12 +294,6 @@ export async function loadReviewState(
|
|
|
297
294
|
};
|
|
298
295
|
}
|
|
299
296
|
|
|
300
|
-
/**
|
|
301
|
-
* The model turn's wake message: composed rubric, PR context, and the
|
|
302
|
-
* host-fetched diff. Fenced so author-controlled patch bytes cannot escape
|
|
303
|
-
* into the trusted markdown (the fence outruns any backtick run inside the
|
|
304
|
-
* patch).
|
|
305
|
-
*/
|
|
306
297
|
/**
|
|
307
298
|
* Filenames are author-controlled and render outside the diff fence: strip
|
|
308
299
|
* control characters and backticks so a hostile path cannot fabricate
|
|
@@ -648,9 +639,6 @@ export async function actuateReview(input: {
|
|
|
648
639
|
}).filter(
|
|
649
640
|
(handle) => normalizeHandle(handle) !== normalizeHandle(pr.authorLogin ?? "")
|
|
650
641
|
);
|
|
651
|
-
// The full decision — policy verdict plus the governance / evidence
|
|
652
|
-
// fail-closes — is computed in one place; from here on this function
|
|
653
|
-
// only writes.
|
|
654
642
|
const decision = decideApproval({
|
|
655
643
|
matchedRules: state.matchedRules,
|
|
656
644
|
riskLevel: verdict.riskLevel,
|
|
@@ -742,7 +730,6 @@ export async function actuateReview(input: {
|
|
|
742
730
|
const alreadyRequested = new Set(
|
|
743
731
|
pr.requestedReviewers.map(normalizeHandle)
|
|
744
732
|
);
|
|
745
|
-
// Adds fill only the remaining slots under the max-of-two rule.
|
|
746
733
|
const newReviewers = decision.requestReviewers
|
|
747
734
|
.filter((handle) => !alreadyRequested.has(normalizeHandle(handle)))
|
|
748
735
|
.slice(
|
|
@@ -41,10 +41,8 @@ export default defineTool({
|
|
|
41
41
|
"not a GitHub pull-request session — reviews start from the github channel (agent-sdk github replay <pr-url> to test locally)",
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// mid-turn push can never line up with the live head: the actuation
|
|
47
|
-
// compares wake-observed against live and skips on any mismatch.
|
|
44
|
+
// Wake froze the assessed head into the session workspace. A model-supplied
|
|
45
|
+
// SHA is never trusted.
|
|
48
46
|
let assessedHeadSha: string;
|
|
49
47
|
try {
|
|
50
48
|
assessedHeadSha = (
|