@cursor/july 0.1.84 → 0.1.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels/deployments/deployments-channel.d.ts +2 -2
- package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
- package/dist/channels/deployments/deployments-channel.js +7 -6
- package/dist/channels/deployments/index.d.ts +2 -3
- package/dist/channels/deployments/index.d.ts.map +1 -1
- package/dist/channels/deployments/index.js +2 -3
- package/dist/channels/deployments/types.d.ts +14 -14
- package/dist/channels/deployments/types.d.ts.map +1 -1
- package/dist/channels/github/github-channel.d.ts +4 -0
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +18 -11
- package/dist/channels/origin/origin-channel.d.ts +6 -0
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +24 -11
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +12 -2
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.kqdC1N7p.js → app.DUOPbN18.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.CxCtxfDE.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DXDE2WgG.js → VPLocalSearchBox.CmWbGcGk.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DFYqbyta.js → theme.S57OeOLA.js} +2 -2
- package/dist/docs/assets/{reference_channels.md.G5GysZ7e.js → reference_channels.md.CboFd5IH.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/convert-automation.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +5 -5
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/templates/agentic-owners.html +3 -3
- package/dist/docs/templates/demo.html +3 -3
- package/dist/docs/templates/pr-autofixer.html +3 -3
- package/dist/docs/templates/security-reviewer.html +3 -3
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/internal/continuation-identity.d.ts +10 -0
- package/dist/internal/continuation-identity.d.ts.map +1 -1
- package/dist/internal/continuation-identity.js +10 -0
- package/dist/internal/cursor-deployment-relay.d.ts +4 -4
- package/dist/internal/cursor-deployment-relay.d.ts.map +1 -1
- package/dist/internal/cursor-deployment-relay.js +33 -32
- package/dist/internal/handleAgentServeTrigger.d.ts +5 -0
- package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
- package/dist/internal/handleAgentServeTrigger.js +11 -3
- package/dist/internal/local-control-plane.d.ts +19 -0
- package/dist/internal/local-control-plane.d.ts.map +1 -1
- package/dist/internal/local-control-plane.js +17 -0
- package/dist/internal/local-ingress.d.ts +35 -0
- package/dist/internal/local-ingress.d.ts.map +1 -0
- package/dist/internal/local-ingress.js +44 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +151 -75
- package/dist/playground/assets/{index-Cspgk9jC.js → index-Bqn91tW4.js} +45 -45
- package/dist/playground/assets/index-C0_5hOsf.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +2 -2
- package/docs/reference/channels.md +13 -11
- package/package.json +1 -1
- package/src/channels/deployments/deployments-channel.ts +7 -6
- package/src/channels/deployments/index.ts +2 -3
- package/src/channels/deployments/types.ts +14 -14
- package/src/channels/github/github-channel.ts +18 -21
- package/src/channels/origin/origin-channel.ts +26 -15
- package/src/channels/slack/dispatch.ts +11 -7
- package/src/internal/continuation-identity.ts +10 -0
- package/src/internal/cursor-deployment-relay.ts +38 -36
- package/src/internal/handleAgentServeTrigger.ts +31 -2
- package/src/internal/local-control-plane.ts +33 -0
- package/src/internal/local-ingress.ts +90 -0
- package/src/internal/server.ts +194 -40
- package/src/types.ts +2 -2
- package/dist/docs/assets/chunks/@localSearchIndexroot.BqhvUA76.js +0 -1
- package/dist/playground/assets/index-ChETdG-_.css +0 -1
- /package/dist/docs/assets/{reference_channels.md.G5GysZ7e.lean.js → reference_channels.md.CboFd5IH.lean.js} +0 -0
|
@@ -2,15 +2,37 @@
|
|
|
2
2
|
* In-process control plane started with local `serve`. Owns schedule clocks
|
|
3
3
|
* and reminder documents + clocks, then fires into the kit the same way the
|
|
4
4
|
* Cursor backend does over HTTP when `--no-control-plane` is set.
|
|
5
|
+
*
|
|
6
|
+
* v2 ingest lives here: `routeContinuation` stamps the mailbox (July twin
|
|
7
|
+
* of hosted `routeVerifiedIngressContinuation`), and `attachScmEvents`
|
|
8
|
+
* owns the `/v0/scm-events` long poll. v1 keeps engine-side scm-events.
|
|
9
|
+
* Hosted `--no-control-plane` v2 skips that poll — the hosted CP delivers.
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
import { dirname, join } from "node:path";
|
|
8
13
|
import { type CronExpression, parseCron } from "./cron.js";
|
|
14
|
+
import {
|
|
15
|
+
type LocalIngressContinuation,
|
|
16
|
+
type RouteLocalIngressContinuationInput,
|
|
17
|
+
routeLocalIngressContinuation,
|
|
18
|
+
} from "./local-ingress.js";
|
|
9
19
|
import type { PlatformTimers } from "./platform-timers.js";
|
|
10
20
|
import { MAX_TIMER_MS } from "./reminder-schedule.js";
|
|
11
21
|
import { type ReminderRecord, ReminderStore } from "./reminder-store.js";
|
|
12
22
|
|
|
13
23
|
export interface LocalControlPlane extends PlatformTimers {
|
|
24
|
+
/**
|
|
25
|
+
* Stamp a v2 mailbox from the channel selector + live payload. Same
|
|
26
|
+
* contract as hosted verified ingress. `/v1` is a no-op.
|
|
27
|
+
*/
|
|
28
|
+
routeContinuation(
|
|
29
|
+
input: RouteLocalIngressContinuationInput
|
|
30
|
+
): LocalIngressContinuation;
|
|
31
|
+
/**
|
|
32
|
+
* Take ownership of the v2 `/v0/scm-events` long poll. Starts the relay.
|
|
33
|
+
* v1 scm-events stay on the engine serve path.
|
|
34
|
+
*/
|
|
35
|
+
attachScmEvents(relay: { start(): void; stop(): Promise<void> }): void;
|
|
14
36
|
/** Arm persisted reminder clocks after the kit fire callback is live. */
|
|
15
37
|
startClocks(): void;
|
|
16
38
|
stop(): void;
|
|
@@ -40,6 +62,7 @@ export async function createLocalControlPlane(opts: {
|
|
|
40
62
|
const reminders = new Map<string, ReminderRecord>();
|
|
41
63
|
let scheduleTimer: NodeJS.Timeout | undefined;
|
|
42
64
|
let lastFiredMinute = -1;
|
|
65
|
+
let scmRelay: { start(): void; stop(): Promise<void> } | undefined;
|
|
43
66
|
|
|
44
67
|
if (store !== undefined) {
|
|
45
68
|
for (const record of await store.list()) {
|
|
@@ -138,6 +161,12 @@ export async function createLocalControlPlane(opts: {
|
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
return {
|
|
164
|
+
routeContinuation: routeLocalIngressContinuation,
|
|
165
|
+
attachScmEvents(relay) {
|
|
166
|
+
scmRelay = relay;
|
|
167
|
+
relay.start();
|
|
168
|
+
logger("[agent-sdk] cursor-events: control-plane ingest started");
|
|
169
|
+
},
|
|
141
170
|
async upsertSchedule(input) {
|
|
142
171
|
schedules.set(input.name, parseCron(input.cron));
|
|
143
172
|
armScheduleTicker();
|
|
@@ -183,6 +212,10 @@ export async function createLocalControlPlane(opts: {
|
|
|
183
212
|
for (const id of [...reminderTimers.keys()]) {
|
|
184
213
|
clearReminderTimer(id);
|
|
185
214
|
}
|
|
215
|
+
if (scmRelay !== undefined) {
|
|
216
|
+
void scmRelay.stop();
|
|
217
|
+
scmRelay = undefined;
|
|
218
|
+
}
|
|
186
219
|
},
|
|
187
220
|
};
|
|
188
221
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local control-plane ingest for v2 continuation.
|
|
3
|
+
*
|
|
4
|
+
* Twin of backend `routeVerifiedIngressContinuation`. Hosted Cursor runs
|
|
5
|
+
* that copy after the webhook is verified. Self-hosted / `july serve`
|
|
6
|
+
* runs this copy on the in-process local CP before the channel adapter
|
|
7
|
+
* `send()`s. Channel adapters do not extract.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ChannelHandlerArgs } from "../types.js";
|
|
11
|
+
import {
|
|
12
|
+
bindMailboxContinuationSend,
|
|
13
|
+
type ContinuationIdentity,
|
|
14
|
+
continuationSource,
|
|
15
|
+
type MailboxContinuation,
|
|
16
|
+
normalizeIdentity,
|
|
17
|
+
stampMailboxContinuation,
|
|
18
|
+
} from "./continuation-identity.js";
|
|
19
|
+
|
|
20
|
+
export type LocalIngressContinuation = {
|
|
21
|
+
explicitContinuationKey?: string;
|
|
22
|
+
mailboxContinuation?: MailboxContinuation;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type RouteLocalIngressContinuation = (
|
|
26
|
+
input: RouteLocalIngressContinuationInput
|
|
27
|
+
) => LocalIngressContinuation;
|
|
28
|
+
|
|
29
|
+
export type RouteLocalIngressContinuationInput = {
|
|
30
|
+
identity?: ContinuationIdentity;
|
|
31
|
+
kind?: string;
|
|
32
|
+
eventType: string;
|
|
33
|
+
payload: unknown;
|
|
34
|
+
deterministicContinuationKey?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Selector + live payload → mailbox stamp. `/v1` and omitted identity
|
|
39
|
+
* keep the caller-supplied deterministic key and do not stamp a mailbox.
|
|
40
|
+
*/
|
|
41
|
+
export function routeLocalIngressContinuation(
|
|
42
|
+
input: RouteLocalIngressContinuationInput
|
|
43
|
+
): LocalIngressContinuation {
|
|
44
|
+
if (input.identity === undefined) {
|
|
45
|
+
return {
|
|
46
|
+
...(input.deterministicContinuationKey === undefined
|
|
47
|
+
? {}
|
|
48
|
+
: { explicitContinuationKey: input.deterministicContinuationKey }),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const identity = normalizeIdentity(input.identity, input.kind);
|
|
52
|
+
if (continuationSource(identity, input.kind) !== "control-plane") {
|
|
53
|
+
return {
|
|
54
|
+
...(input.deterministicContinuationKey === undefined
|
|
55
|
+
? {}
|
|
56
|
+
: { explicitContinuationKey: input.deterministicContinuationKey }),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const mailboxContinuation = stampMailboxContinuation(
|
|
60
|
+
identity,
|
|
61
|
+
input.eventType,
|
|
62
|
+
input.payload,
|
|
63
|
+
input.kind
|
|
64
|
+
);
|
|
65
|
+
if (mailboxContinuation.key.startsWith("cont:")) {
|
|
66
|
+
return {
|
|
67
|
+
explicitContinuationKey: mailboxContinuation.key,
|
|
68
|
+
mailboxContinuation,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return { mailboxContinuation };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Bind a CP mailbox stamp onto `args.send` so the channel adapter only
|
|
76
|
+
* calls `send()` — it does not extract. No-op for `/v1`.
|
|
77
|
+
*/
|
|
78
|
+
export function bindLocalIngressSend(
|
|
79
|
+
args: ChannelHandlerArgs,
|
|
80
|
+
input: RouteLocalIngressContinuationInput & {
|
|
81
|
+
routeContinuation?: RouteLocalIngressContinuation;
|
|
82
|
+
}
|
|
83
|
+
): ChannelHandlerArgs {
|
|
84
|
+
const route = input.routeContinuation ?? routeLocalIngressContinuation;
|
|
85
|
+
const routed = route(input);
|
|
86
|
+
return {
|
|
87
|
+
...args,
|
|
88
|
+
send: bindMailboxContinuationSend(args.send, routed.mailboxContinuation),
|
|
89
|
+
};
|
|
90
|
+
}
|
package/src/internal/server.ts
CHANGED
|
@@ -129,6 +129,7 @@ import {
|
|
|
129
129
|
createLocalControlPlane,
|
|
130
130
|
type LocalControlPlane,
|
|
131
131
|
} from "./local-control-plane.js";
|
|
132
|
+
import type { RouteLocalIngressContinuation } from "./local-ingress.js";
|
|
132
133
|
import { createLogRing, type LogRing } from "./log-ring.js";
|
|
133
134
|
import {
|
|
134
135
|
createAgentMcpEndpoint,
|
|
@@ -216,6 +217,28 @@ interface MountedRuntime {
|
|
|
216
217
|
evalRuns: EvalRunStore;
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
function localControlPlaneRoute(
|
|
221
|
+
runtimes: readonly MountedRuntime[]
|
|
222
|
+
): RouteLocalIngressContinuation | undefined {
|
|
223
|
+
for (const runtime of runtimes) {
|
|
224
|
+
if (runtime.controlPlane !== undefined) {
|
|
225
|
+
return (input) => runtime.controlPlane!.routeContinuation(input);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function firstLocalControlPlane(
|
|
232
|
+
runtimes: readonly MountedRuntime[]
|
|
233
|
+
): LocalControlPlane | undefined {
|
|
234
|
+
return runtimes.find((runtime) => runtime.controlPlane !== undefined)
|
|
235
|
+
?.controlPlane;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function isV2Project(project: { agent: { architecture?: string } }): boolean {
|
|
239
|
+
return project.agent.architecture === "v2";
|
|
240
|
+
}
|
|
241
|
+
|
|
219
242
|
/** Internal-only options for {@link startServer} (not on public `serve`). */
|
|
220
243
|
export interface StartServerInternalOptions {
|
|
221
244
|
/** Test seam: replace the Cursor SDK runner (integration tests). */
|
|
@@ -317,6 +340,20 @@ export async function startServer(
|
|
|
317
340
|
? undefined
|
|
318
341
|
: requireCursorEventsAuth(effectiveCursorEvents, accountKey);
|
|
319
342
|
|
|
343
|
+
// Two architectures + local CP + cursor-events would register two
|
|
344
|
+
// /v0/scm-events consumers (engine offset vs control-plane offset).
|
|
345
|
+
// Fail before engines or relays start. `--no-control-plane` mixed is one
|
|
346
|
+
// July consumer (v1 engine); hosted CP owns v2.
|
|
347
|
+
assertSingleCursorEventsLane({
|
|
348
|
+
noControlPlane,
|
|
349
|
+
config: effectiveCursorEvents,
|
|
350
|
+
auth: cursorEventsAuth,
|
|
351
|
+
v1Mounts: mounts.filter((mount) => !isV2Project(mount.project)),
|
|
352
|
+
v2Mounts: mounts.filter((mount) => isV2Project(mount.project)),
|
|
353
|
+
githubRepos: cursorGithubConfig.cursorEvents?.repos ?? [],
|
|
354
|
+
originRepos: cursorOriginScope?.repos ?? [],
|
|
355
|
+
});
|
|
356
|
+
|
|
320
357
|
let cursorGithubCredentials: CursorGithubCredentialProvider | undefined;
|
|
321
358
|
// Proxy-wired GitHub token resolver for `ctx.host.github` when no startup
|
|
322
359
|
// token mint runs (empty repo scope): host Octokit calls must route
|
|
@@ -755,36 +792,46 @@ export async function startServer(
|
|
|
755
792
|
// job (backend `/github_webhook` already verified); we only admit loopback
|
|
756
793
|
// so this route is not a public unsigned endpoint. Agents decide whether
|
|
757
794
|
// to run via their onPullRequest / onComment / … hooks.
|
|
758
|
-
const
|
|
795
|
+
const collectGitHubTargetsFrom = (selected: readonly MountedRuntime[]) =>
|
|
759
796
|
collectGitHubTriggerTargets({
|
|
760
|
-
mounts:
|
|
797
|
+
mounts: selected.map((runtime) => ({
|
|
761
798
|
slug: runtime.slug,
|
|
762
799
|
channels: runtime.project.channels,
|
|
763
800
|
buildHandlerArgs: (channelId) =>
|
|
764
801
|
buildChannelHandlerArgs({ runtime, channelId }),
|
|
765
802
|
})),
|
|
766
803
|
});
|
|
767
|
-
const
|
|
804
|
+
const collectOriginTargetsFrom = (selected: readonly MountedRuntime[]) =>
|
|
768
805
|
collectOriginTriggerTargets({
|
|
769
|
-
mounts:
|
|
806
|
+
mounts: selected.map((runtime) => ({
|
|
770
807
|
slug: runtime.slug,
|
|
771
808
|
channels: runtime.project.channels,
|
|
772
809
|
buildHandlerArgs: (channelId) =>
|
|
773
810
|
buildChannelHandlerArgs({ runtime, channelId }),
|
|
774
811
|
})),
|
|
775
812
|
});
|
|
813
|
+
const collectGitHubTargets = () => collectGitHubTargetsFrom(runtimes);
|
|
814
|
+
const collectOriginTargets = () => collectOriginTargetsFrom(runtimes);
|
|
815
|
+
const v1Runtimes = runtimes.filter(
|
|
816
|
+
(runtime) => !isV2Project(runtime.project)
|
|
817
|
+
);
|
|
818
|
+
const v2Runtimes = runtimes.filter((runtime) => isV2Project(runtime.project));
|
|
819
|
+
const v1Mounts = mounts.filter((mount) => !isV2Project(mount.project));
|
|
820
|
+
const v2Mounts = mounts.filter((mount) => isV2Project(mount.project));
|
|
776
821
|
router.add({
|
|
777
822
|
method: "POST",
|
|
778
823
|
pattern: "/v1/webhooks/github",
|
|
779
824
|
auth: [localDevStrict()],
|
|
780
825
|
handler: async (request) => {
|
|
781
826
|
const bodyText = await request.text();
|
|
827
|
+
const routeContinuation = localControlPlaneRoute(runtimes);
|
|
782
828
|
const result = await handleAgentServeTrigger({
|
|
783
829
|
type: "github",
|
|
784
830
|
bodyText,
|
|
785
831
|
eventName: request.headers.get("x-github-event"),
|
|
786
832
|
deliveryId: request.headers.get("x-github-delivery") ?? undefined,
|
|
787
833
|
targets: collectGitHubTargets(),
|
|
834
|
+
...(routeContinuation === undefined ? {} : { routeContinuation }),
|
|
788
835
|
});
|
|
789
836
|
const status = result.ok ? 202 : (result.targets[0]?.status ?? 500);
|
|
790
837
|
return jsonResponse(result, { status });
|
|
@@ -820,21 +867,48 @@ export async function startServer(
|
|
|
820
867
|
throw error;
|
|
821
868
|
}
|
|
822
869
|
|
|
823
|
-
// Cursor SCM events
|
|
824
|
-
|
|
870
|
+
// Cursor SCM events: v1 stays on the engine poll. v2 ingest is local-CP
|
|
871
|
+
// only — hosted `--no-control-plane` skips the July poll (hosted CP wakes).
|
|
872
|
+
const routeContinuation = localControlPlaneRoute(runtimes);
|
|
873
|
+
const cursorEventsCommon = {
|
|
825
874
|
config: effectiveCursorEvents,
|
|
826
875
|
githubRepos: cursorGithubConfig.cursorEvents?.repos ?? [],
|
|
827
876
|
originRepos: cursorOriginScope?.repos ?? [],
|
|
828
877
|
stateRoot: options.stateRoot,
|
|
829
|
-
mounts,
|
|
830
878
|
auth: cursorEventsAuth,
|
|
831
879
|
logger,
|
|
832
|
-
collectGitHubTargets,
|
|
833
|
-
collectOriginTargets,
|
|
834
880
|
cursorAccountOnly:
|
|
835
881
|
options.cursorEvents === undefined &&
|
|
836
882
|
(cursorGithubTargets.length > 0 || cursorOriginTargets.length > 0),
|
|
883
|
+
};
|
|
884
|
+
const cursorEventRelay = startCursorEventRelay({
|
|
885
|
+
...cursorEventsCommon,
|
|
886
|
+
mounts: v1Mounts,
|
|
887
|
+
collectGitHubTargets: () => collectGitHubTargetsFrom(v1Runtimes),
|
|
888
|
+
collectOriginTargets: () => collectOriginTargetsFrom(v1Runtimes),
|
|
889
|
+
lane: "engine",
|
|
890
|
+
skippedV2: v2Mounts.length > 0,
|
|
891
|
+
});
|
|
892
|
+
const controlPlaneScmRelay = startCursorEventRelay({
|
|
893
|
+
...cursorEventsCommon,
|
|
894
|
+
mounts: v2Mounts,
|
|
895
|
+
collectGitHubTargets: () => collectGitHubTargetsFrom(v2Runtimes),
|
|
896
|
+
collectOriginTargets: () => collectOriginTargetsFrom(v2Runtimes),
|
|
897
|
+
...(routeContinuation === undefined ? {} : { routeContinuation }),
|
|
898
|
+
lane: "control-plane",
|
|
899
|
+
start: false,
|
|
900
|
+
stateDirName: join("control-plane", "cursor-events"),
|
|
837
901
|
});
|
|
902
|
+
const localCp = firstLocalControlPlane(runtimes);
|
|
903
|
+
if (controlPlaneScmRelay !== undefined) {
|
|
904
|
+
if (localCp !== undefined) {
|
|
905
|
+
localCp.attachScmEvents(controlPlaneScmRelay);
|
|
906
|
+
} else {
|
|
907
|
+
logger(
|
|
908
|
+
"[agent-sdk] cursor-events: skipping v2 scm-events poll (--no-control-plane); hosted CP owns ingest"
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
838
912
|
|
|
839
913
|
// Cursor deployment-events relay: automatic when a mounted agent authored
|
|
840
914
|
// `deploymentsChannel({ ... })`.
|
|
@@ -866,6 +940,7 @@ export async function startServer(
|
|
|
866
940
|
} catch (error) {
|
|
867
941
|
await cursorDeploymentRelay?.stop();
|
|
868
942
|
await cursorEventRelay?.stop();
|
|
943
|
+
await controlPlaneScmRelay?.stop();
|
|
869
944
|
await cursorSlackRelay?.stop();
|
|
870
945
|
cursorGithubCredentials?.stop();
|
|
871
946
|
await shutdownOtelRuntime(otelRuntime);
|
|
@@ -1027,6 +1102,7 @@ export async function startServer(
|
|
|
1027
1102
|
playgroundDev?.stop();
|
|
1028
1103
|
await cursorDeploymentRelay?.stop();
|
|
1029
1104
|
await cursorEventRelay?.stop();
|
|
1105
|
+
await controlPlaneScmRelay?.stop();
|
|
1030
1106
|
await cursorSlackRelay?.stop();
|
|
1031
1107
|
for (const runtime of runtimes) {
|
|
1032
1108
|
runtime.controlPlane?.stop();
|
|
@@ -1878,24 +1954,13 @@ function requireCursorEventsAuth(
|
|
|
1878
1954
|
* channels; Origin is subscribed only when an `originChannel({ cursorAccount })`
|
|
1879
1955
|
* opted in.
|
|
1880
1956
|
*/
|
|
1881
|
-
function
|
|
1882
|
-
|
|
1957
|
+
function collectCursorEventSubscriptions(input: {
|
|
1958
|
+
mounts: readonly AgentMount[];
|
|
1883
1959
|
githubRepos: readonly string[];
|
|
1884
1960
|
originRepos: readonly string[];
|
|
1885
|
-
|
|
1886
|
-
mounts
|
|
1887
|
-
|
|
1888
|
-
logger: (line: string) => void;
|
|
1889
|
-
collectGitHubTargets: () => ReturnType<typeof collectGitHubTriggerTargets>;
|
|
1890
|
-
collectOriginTargets: () => ReturnType<typeof collectOriginTriggerTargets>;
|
|
1891
|
-
cursorAccountOnly: boolean;
|
|
1892
|
-
}): CursorEventRelay | undefined {
|
|
1893
|
-
const { config, stateRoot, mounts, auth, logger } = input;
|
|
1894
|
-
if (config === undefined || auth === undefined) {
|
|
1895
|
-
return undefined;
|
|
1896
|
-
}
|
|
1897
|
-
const githubTargets = findGitHubChannelTargets(mounts);
|
|
1898
|
-
const originTargets = findOriginChannelTargets(mounts);
|
|
1961
|
+
}): CursorScmSubscription[] {
|
|
1962
|
+
const githubTargets = findGitHubChannelTargets(input.mounts);
|
|
1963
|
+
const originTargets = findOriginChannelTargets(input.mounts);
|
|
1899
1964
|
const githubEvents = [
|
|
1900
1965
|
...new Set(githubTargets.flatMap((target) => target.events)),
|
|
1901
1966
|
];
|
|
@@ -1928,8 +1993,88 @@ function startCursorEventRelay(input: {
|
|
|
1928
1993
|
});
|
|
1929
1994
|
}
|
|
1930
1995
|
}
|
|
1996
|
+
return subscriptions;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
/**
|
|
2000
|
+
* Local CP + `--cursor-events` + both architectures would start two
|
|
2001
|
+
* consumers (different offset dirs). Refuse that at serve start.
|
|
2002
|
+
* `--no-control-plane` is one July consumer — hosted CP owns v2.
|
|
2003
|
+
*/
|
|
2004
|
+
function assertSingleCursorEventsLane(input: {
|
|
2005
|
+
noControlPlane: boolean;
|
|
2006
|
+
config: ServeOptions["cursorEvents"];
|
|
2007
|
+
auth: CursorEventsAuth | undefined;
|
|
2008
|
+
v1Mounts: readonly AgentMount[];
|
|
2009
|
+
v2Mounts: readonly AgentMount[];
|
|
2010
|
+
githubRepos: readonly string[];
|
|
2011
|
+
originRepos: readonly string[];
|
|
2012
|
+
}): void {
|
|
2013
|
+
if (
|
|
2014
|
+
input.noControlPlane ||
|
|
2015
|
+
input.config === undefined ||
|
|
2016
|
+
input.auth === undefined
|
|
2017
|
+
) {
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
const shared = {
|
|
2021
|
+
githubRepos: input.githubRepos,
|
|
2022
|
+
originRepos: input.originRepos,
|
|
2023
|
+
};
|
|
2024
|
+
if (
|
|
2025
|
+
collectCursorEventSubscriptions({ mounts: input.v1Mounts, ...shared })
|
|
2026
|
+
.length === 0 ||
|
|
2027
|
+
collectCursorEventSubscriptions({ mounts: input.v2Mounts, ...shared })
|
|
2028
|
+
.length === 0
|
|
2029
|
+
) {
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
throw new Error(
|
|
2033
|
+
"[agent-sdk] cursor-events: refusing to serve v1 and v2 github/origin agents in one process — they would open two /v0/scm-events consumers. Serve one architecture per process."
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
function startCursorEventRelay(input: {
|
|
2038
|
+
config: ServeOptions["cursorEvents"];
|
|
2039
|
+
githubRepos: readonly string[];
|
|
2040
|
+
originRepos: readonly string[];
|
|
2041
|
+
stateRoot: string | undefined;
|
|
2042
|
+
mounts: AgentMount[];
|
|
2043
|
+
auth: CursorEventsAuth | undefined;
|
|
2044
|
+
logger: (line: string) => void;
|
|
2045
|
+
collectGitHubTargets: () => ReturnType<typeof collectGitHubTriggerTargets>;
|
|
2046
|
+
collectOriginTargets: () => ReturnType<typeof collectOriginTriggerTargets>;
|
|
2047
|
+
routeContinuation?: RouteLocalIngressContinuation;
|
|
2048
|
+
cursorAccountOnly: boolean;
|
|
2049
|
+
/** Engine = v1 poll. Control-plane = v2 poll (started by local CP). */
|
|
2050
|
+
lane: "engine" | "control-plane";
|
|
2051
|
+
/** When the engine lane has no v1 targets, mention skipped v2 mounts. */
|
|
2052
|
+
skippedV2?: boolean;
|
|
2053
|
+
/** When false, return an unstarted relay (local CP calls start). */
|
|
2054
|
+
start?: boolean;
|
|
2055
|
+
/** Offset dir under stateRoot. Defaults to `cursor-events`. */
|
|
2056
|
+
stateDirName?: string;
|
|
2057
|
+
}): CursorEventRelay | undefined {
|
|
2058
|
+
const { config, stateRoot, mounts, auth, logger } = input;
|
|
2059
|
+
if (config === undefined || auth === undefined) {
|
|
2060
|
+
return undefined;
|
|
2061
|
+
}
|
|
2062
|
+
const subscriptions = collectCursorEventSubscriptions({
|
|
2063
|
+
mounts,
|
|
2064
|
+
githubRepos: input.githubRepos,
|
|
2065
|
+
originRepos: input.originRepos,
|
|
2066
|
+
});
|
|
1931
2067
|
if (subscriptions.length === 0) {
|
|
1932
|
-
if (
|
|
2068
|
+
if (input.lane === "engine" && input.skippedV2 === true) {
|
|
2069
|
+
logger(
|
|
2070
|
+
"[agent-sdk] cursor-events: no v1 github/origin targets; v2 ingest is control-plane only"
|
|
2071
|
+
);
|
|
2072
|
+
} else if (input.lane === "control-plane") {
|
|
2073
|
+
return undefined;
|
|
2074
|
+
} else if (
|
|
2075
|
+
input.githubRepos.every((repo) => repo.trim() === "") &&
|
|
2076
|
+
input.originRepos.every((repo) => repo.trim() === "")
|
|
2077
|
+
) {
|
|
1933
2078
|
logger(
|
|
1934
2079
|
"[agent-sdk] cursor-events: no repos configured (pass --repo owner/name); relay disabled"
|
|
1935
2080
|
);
|
|
@@ -1947,7 +2092,7 @@ function startCursorEventRelay(input: {
|
|
|
1947
2092
|
baseUrl: auth.baseUrl,
|
|
1948
2093
|
apiKey: auth.apiKey,
|
|
1949
2094
|
subscriptions,
|
|
1950
|
-
stateDir: join(baseStateRoot, "cursor-events"),
|
|
2095
|
+
stateDir: join(baseStateRoot, input.stateDirName ?? "cursor-events"),
|
|
1951
2096
|
logger,
|
|
1952
2097
|
dispatch: async (event) => {
|
|
1953
2098
|
if (event.provider === "origin") {
|
|
@@ -1970,6 +2115,9 @@ function startCursorEventRelay(input: {
|
|
|
1970
2115
|
type: "origin",
|
|
1971
2116
|
bodyText: JSON.stringify(scmMetadataToOriginPayload(event)),
|
|
1972
2117
|
targets,
|
|
2118
|
+
...(input.routeContinuation === undefined
|
|
2119
|
+
? {}
|
|
2120
|
+
: { routeContinuation: input.routeContinuation }),
|
|
1973
2121
|
});
|
|
1974
2122
|
logCursorScmDispatch(logger, event, result);
|
|
1975
2123
|
return;
|
|
@@ -1996,10 +2144,16 @@ function startCursorEventRelay(input: {
|
|
|
1996
2144
|
eventName: event.eventName,
|
|
1997
2145
|
deliveryId: event.deliveryId,
|
|
1998
2146
|
targets,
|
|
2147
|
+
...(input.routeContinuation === undefined
|
|
2148
|
+
? {}
|
|
2149
|
+
: { routeContinuation: input.routeContinuation }),
|
|
1999
2150
|
});
|
|
2000
2151
|
logCursorScmDispatch(logger, event, result);
|
|
2001
2152
|
},
|
|
2002
2153
|
});
|
|
2154
|
+
if (input.start === false) {
|
|
2155
|
+
return relay;
|
|
2156
|
+
}
|
|
2003
2157
|
relay.start();
|
|
2004
2158
|
logger(
|
|
2005
2159
|
`[agent-sdk] cursor-events: relaying ${subscriptions.length} subscription(s) from ${auth.baseUrl}`
|
|
@@ -2291,7 +2445,7 @@ function collectCursorDeploymentTargets(
|
|
|
2291
2445
|
}
|
|
2292
2446
|
const subscriptions = resolveDeploymentSubscriptions(options);
|
|
2293
2447
|
if (subscriptions.length === 0) {
|
|
2294
|
-
// Declared the channel but named no
|
|
2448
|
+
// Declared the channel but named no deploy sources — usually an unset
|
|
2295
2449
|
// env var. Say so rather than starting a relay that receives nothing.
|
|
2296
2450
|
skipped.push(`${runtime.slug || runtime.project.name}/${channel.id}`);
|
|
2297
2451
|
continue;
|
|
@@ -2305,7 +2459,7 @@ function collectCursorDeploymentTargets(
|
|
|
2305
2459
|
/**
|
|
2306
2460
|
* Cursor deployment-events relay: automatic when a mounted agent authored
|
|
2307
2461
|
* `deploymentsChannel({ ... })`. One relay per process reads
|
|
2308
|
-
* `/v0/deployment-events` for the union of every channel's
|
|
2462
|
+
* `/v0/deployment-events` for the union of every channel's deploy sources, and
|
|
2309
2463
|
* each event is routed to the channels that asked for it — the same ownership
|
|
2310
2464
|
* `startCursorSlackRelay` and `startCursorEventRelay` have.
|
|
2311
2465
|
*/
|
|
@@ -2320,7 +2474,7 @@ function startCursorDeploymentRelay(input: {
|
|
|
2320
2474
|
const targets = collectCursorDeploymentTargets(runtimes, skipped);
|
|
2321
2475
|
if (skipped.length > 0) {
|
|
2322
2476
|
logger(
|
|
2323
|
-
`[agent-sdk] deployment-events: no
|
|
2477
|
+
`[agent-sdk] deployment-events: no deploy sources declared on ${skipped.join(", ")}; those channels receive nothing`
|
|
2324
2478
|
);
|
|
2325
2479
|
}
|
|
2326
2480
|
if (targets.length === 0) {
|
|
@@ -2328,19 +2482,19 @@ function startCursorDeploymentRelay(input: {
|
|
|
2328
2482
|
}
|
|
2329
2483
|
if (accountKey === undefined) {
|
|
2330
2484
|
throw new Error(
|
|
2331
|
-
`This project has channels subscribed to deployment events (deploymentsChannel({
|
|
2485
|
+
`This project has channels subscribed to deployment events (deploymentsChannel({ deploySourceUris })) but the host is not signed in to Cursor. ${SIGN_IN_HINT}`
|
|
2332
2486
|
);
|
|
2333
2487
|
}
|
|
2334
|
-
// Union across channels: one registration per
|
|
2335
|
-
// however many channels named it. Per-channel environment and
|
|
2336
|
-
// filtering happens at dispatch.
|
|
2337
|
-
const
|
|
2488
|
+
// Union across channels: one registration per deploy source for the whole
|
|
2489
|
+
// process, however many channels named it. Per-channel environment and
|
|
2490
|
+
// event-type filtering happens at dispatch.
|
|
2491
|
+
const bySource = new Map<string, ResolvedSubscription>();
|
|
2338
2492
|
for (const target of targets) {
|
|
2339
2493
|
for (const subscription of target.subscriptions) {
|
|
2340
|
-
const existing =
|
|
2494
|
+
const existing = bySource.get(subscription.deploySourceUri);
|
|
2341
2495
|
if (existing === undefined) {
|
|
2342
|
-
|
|
2343
|
-
|
|
2496
|
+
bySource.set(subscription.deploySourceUri, {
|
|
2497
|
+
deploySourceUri: subscription.deploySourceUri,
|
|
2344
2498
|
environments: [...subscription.environments],
|
|
2345
2499
|
events: new Set(subscription.events),
|
|
2346
2500
|
});
|
|
@@ -2369,7 +2523,7 @@ function startCursorDeploymentRelay(input: {
|
|
|
2369
2523
|
baseUrl: cursorExternalApiUrl(),
|
|
2370
2524
|
apiKey: accountKey.apiKey,
|
|
2371
2525
|
stateDir: join(baseStateRoot, "deployment-events"),
|
|
2372
|
-
subscriptions: [...
|
|
2526
|
+
subscriptions: [...bySource.values()],
|
|
2373
2527
|
logger,
|
|
2374
2528
|
dispatch: async (event) =>
|
|
2375
2529
|
fanOutDeploymentEvent({
|
|
@@ -2392,7 +2546,7 @@ function startCursorDeploymentRelay(input: {
|
|
|
2392
2546
|
});
|
|
2393
2547
|
relay.start();
|
|
2394
2548
|
logger(
|
|
2395
|
-
`[agent-sdk] deployment-events: relaying ${
|
|
2549
|
+
`[agent-sdk] deployment-events: relaying ${bySource.size} deploy-source subscription(s) to ${targets.length} channel(s)`
|
|
2396
2550
|
);
|
|
2397
2551
|
return relay;
|
|
2398
2552
|
}
|
package/src/types.ts
CHANGED
|
@@ -1689,8 +1689,8 @@ export interface SendMessageOptions {
|
|
|
1689
1689
|
*/
|
|
1690
1690
|
continuationToken?: string;
|
|
1691
1691
|
/**
|
|
1692
|
-
* v2 mailbox stamp from the control plane (
|
|
1693
|
-
*
|
|
1692
|
+
* v2 mailbox stamp from the control plane (hosted verified ingress or
|
|
1693
|
+
* the local CP). Channel adapters do not extract. Ignored on v1.
|
|
1694
1694
|
*/
|
|
1695
1695
|
mailboxContinuation?: MailboxContinuation;
|
|
1696
1696
|
/**
|