@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
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
/>
|
|
9
9
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
10
10
|
<title>agent-serve playground</title>
|
|
11
|
-
<script type="module" crossorigin src="./assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
11
|
+
<script type="module" crossorigin src="./assets/index-Bqn91tW4.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="./assets/index-C0_5hOsf.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
package/dist/types.d.ts
CHANGED
|
@@ -1463,8 +1463,8 @@ export interface SendMessageOptions {
|
|
|
1463
1463
|
*/
|
|
1464
1464
|
continuationToken?: string;
|
|
1465
1465
|
/**
|
|
1466
|
-
* v2 mailbox stamp from the control plane (
|
|
1467
|
-
*
|
|
1466
|
+
* v2 mailbox stamp from the control plane (hosted verified ingress or
|
|
1467
|
+
* the local CP). Channel adapters do not extract. Ignored on v1.
|
|
1468
1468
|
*/
|
|
1469
1469
|
mailboxContinuation?: MailboxContinuation;
|
|
1470
1470
|
/**
|
|
@@ -218,19 +218,21 @@ converge a merge-box check and sticky PR comment from default stream
|
|
|
218
218
|
events. Guide: [GitHub](../guides/github.md).
|
|
219
219
|
|
|
220
220
|
**Deployments** (`@cursor/july/channels/deployments`): pull transport
|
|
221
|
-
over `/v0/deployment-events`. Subscribe per
|
|
222
|
-
narrow with `environments` / `events`, and handle
|
|
223
|
-
`onEvent`. `
|
|
224
|
-
writer records it; the
|
|
225
|
-
case-insensitive but otherwise
|
|
221
|
+
over `/v0/deployment-events`. Subscribe per deploy source with
|
|
222
|
+
`deploySourceUris`, narrow with `environments` / `events`, and handle
|
|
223
|
+
each event in `onEvent`. `deploySourceUris` must match
|
|
224
|
+
`Deployment.deploy_source_uri` as your deployment writer records it; the
|
|
225
|
+
field has no format, and matching is case-insensitive but otherwise
|
|
226
|
+
literal. Each event carries `deploySourceUri` and `deployVersion`.
|
|
226
227
|
Author `agent/channels/deployments.ts` with `deploymentsChannel()`. The
|
|
227
228
|
serve host discovers every mounted deployments channel, runs one relay
|
|
228
|
-
for the process against the union of their
|
|
229
|
-
event to the channels that asked for it
|
|
230
|
-
|
|
231
|
-
a durable offset, so a restart resumes rather than dropping
|
|
232
|
-
empty `
|
|
233
|
-
env-configured agent stays inert until its
|
|
229
|
+
for the process against the union of their deploy sources, and routes
|
|
230
|
+
each event to the channels that asked for it; the Slack and SCM relays
|
|
231
|
+
use the same ownership. It authenticates with the host credential and
|
|
232
|
+
keeps a durable offset, so a restart resumes rather than dropping
|
|
233
|
+
events. An empty `deploySourceUris` list mounts the channel but starts
|
|
234
|
+
no relay for it, so an env-configured agent stays inert until its
|
|
235
|
+
deploy sources are set.
|
|
234
236
|
|
|
235
237
|
For other platforms like Discord or Teams, use the authored
|
|
236
238
|
`defineChannel` webhook form.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cursor/july",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.86",
|
|
4
4
|
"description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"repository": {
|
|
@@ -14,11 +14,11 @@ const deploymentEventSchema = z.object({
|
|
|
14
14
|
eventType: z.enum(DEPLOYMENT_EVENT_TYPES),
|
|
15
15
|
eventId: z.string().min(1),
|
|
16
16
|
teamId: z.number(),
|
|
17
|
-
|
|
17
|
+
deploySourceUri: z.string().min(1),
|
|
18
18
|
deploymentId: z.string().min(1),
|
|
19
19
|
environment: z.string().min(1),
|
|
20
20
|
service: z.string().min(1).optional(),
|
|
21
|
-
|
|
21
|
+
deployVersion: z.string().min(1),
|
|
22
22
|
status: z.enum(["pending", "in_progress", "success", "failed"]),
|
|
23
23
|
occurredAtMs: z.number(),
|
|
24
24
|
});
|
|
@@ -60,10 +60,10 @@ export async function dispatchDeploymentEvent(input: {
|
|
|
60
60
|
* import { deploymentsChannel } from "@cursor/july/channels/deployments";
|
|
61
61
|
*
|
|
62
62
|
* export default deploymentsChannel({
|
|
63
|
-
*
|
|
63
|
+
* deploySourceUris: ["https://origin.cursor.com/repos/anysphere/everysphere"],
|
|
64
64
|
* environments: ["prod"],
|
|
65
65
|
* onEvent: async (event, { send }) => {
|
|
66
|
-
* await send(`${event.
|
|
66
|
+
* await send(`${event.deploySourceUri}@${event.deployVersion} shipped to ${event.environment}`);
|
|
67
67
|
* },
|
|
68
68
|
* });
|
|
69
69
|
* ```
|
|
@@ -81,8 +81,9 @@ export async function dispatchDeploymentEvent(input: {
|
|
|
81
81
|
export function deploymentsChannel(
|
|
82
82
|
options: DeploymentsChannelOptions
|
|
83
83
|
): ChannelDefinition {
|
|
84
|
-
// Throws here rather than at startup, so a malformed `
|
|
85
|
-
// when the channel module is authored instead of when the host
|
|
84
|
+
// Throws here rather than at startup, so a malformed `deploySourceUris`
|
|
85
|
+
// entry fails when the channel module is authored instead of when the host
|
|
86
|
+
// mounts it.
|
|
86
87
|
resolveDeploymentSubscriptions(options);
|
|
87
88
|
const routePath = options.path ?? "/";
|
|
88
89
|
|
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
* import { deploymentsChannel } from "@cursor/july/channels/deployments";
|
|
6
6
|
*
|
|
7
7
|
* export default deploymentsChannel({
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* onEvent: (event, { send }) => send(`${event.sha} shipped`),
|
|
8
|
+
* deploySourceUris: ["https://origin.cursor.com/repos/anysphere/everysphere"],
|
|
9
|
+
* onEvent: (event, { send }) => send(`${event.deployVersion} shipped`),
|
|
11
10
|
* });
|
|
12
11
|
* ```
|
|
13
12
|
*
|
|
@@ -18,25 +18,25 @@ export interface DeploymentEvent {
|
|
|
18
18
|
eventId: string;
|
|
19
19
|
/** Owning Cursor team. */
|
|
20
20
|
teamId: number;
|
|
21
|
-
/** The Deployment's
|
|
22
|
-
|
|
21
|
+
/** The Deployment's deploy_source_uri string, case-folded. */
|
|
22
|
+
deploySourceUri: string;
|
|
23
23
|
deploymentId: string;
|
|
24
24
|
/** Environment slug, e.g. `prod`. */
|
|
25
25
|
environment: string;
|
|
26
26
|
/** Absent when the deployment records no service. */
|
|
27
27
|
service?: string;
|
|
28
|
-
|
|
28
|
+
deployVersion: string;
|
|
29
29
|
status: DeploymentStatus;
|
|
30
30
|
occurredAtMs: number;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** What a
|
|
33
|
+
/** What a deploy-source subscription asks for. */
|
|
34
34
|
export interface DeploymentSubscription {
|
|
35
35
|
/**
|
|
36
|
-
* The
|
|
36
|
+
* The deploy-source URI as the factory records it on a Deployment. Opaque:
|
|
37
37
|
* whatever your deployment writer puts there, matched case-insensitively.
|
|
38
38
|
*/
|
|
39
|
-
|
|
39
|
+
deploySourceUri: string;
|
|
40
40
|
/** Omit or leave empty for every environment. */
|
|
41
41
|
environments?: readonly string[];
|
|
42
42
|
/** Defaults to `deployment.succeeded`. */
|
|
@@ -45,18 +45,18 @@ export interface DeploymentSubscription {
|
|
|
45
45
|
|
|
46
46
|
export interface DeploymentsChannelOptions {
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
49
|
-
* `Deployment.
|
|
50
|
-
* puts there is the key — and matching is case-insensitive but
|
|
51
|
-
* literal, so a value that does not match subscribes successfully
|
|
52
|
-
* receives nothing. A bare string takes the default environment and
|
|
53
|
-
* filters.
|
|
48
|
+
* Deploy sources to watch, spelled exactly as your deployment writer records
|
|
49
|
+
* `Deployment.deploy_source_uri`. The field has no format — whatever the
|
|
50
|
+
* writer puts there is the key — and matching is case-insensitive but
|
|
51
|
+
* otherwise literal, so a value that does not match subscribes successfully
|
|
52
|
+
* and then receives nothing. A bare string takes the default environment and
|
|
53
|
+
* event filters.
|
|
54
54
|
*
|
|
55
|
-
* There is no team-wide subscription: every
|
|
55
|
+
* There is no team-wide subscription: every deploy source is named. At least
|
|
56
56
|
* one is required — an empty list is how a consumer *deregisters*, so it
|
|
57
57
|
* cannot also mean "everything".
|
|
58
58
|
*/
|
|
59
|
-
|
|
59
|
+
deploySourceUris: readonly (string | DeploymentSubscription)[];
|
|
60
60
|
/**
|
|
61
61
|
* Applied to every subscription that does not set its own. Omit or leave
|
|
62
62
|
* empty for every environment.
|
|
@@ -8,12 +8,11 @@ import {
|
|
|
8
8
|
POST,
|
|
9
9
|
} from "../../channels.js";
|
|
10
10
|
import {
|
|
11
|
-
bindMailboxContinuationSend,
|
|
12
11
|
continuationSource,
|
|
13
12
|
normalizeIdentity,
|
|
14
|
-
stampMailboxContinuation,
|
|
15
13
|
} from "../../internal/continuation-identity.js";
|
|
16
14
|
import { isDevMode } from "../../internal/dev-mode.js";
|
|
15
|
+
import { bindLocalIngressSend } from "../../internal/local-ingress.js";
|
|
17
16
|
import type {
|
|
18
17
|
AgentCloudOptions,
|
|
19
18
|
ArtifactsApi,
|
|
@@ -477,6 +476,10 @@ function verifyWebhookSha256Signature(args: {
|
|
|
477
476
|
* {@link handleAgentServeTrigger} fan-out. Signature verification is the
|
|
478
477
|
* caller's responsibility (HTTP route verifies; internal fan-out skips).
|
|
479
478
|
*/
|
|
479
|
+
/**
|
|
480
|
+
* Run authored GitHub hooks and `send()`. Does not extract a v2 mailbox —
|
|
481
|
+
* the local CP (or hosted verified ingress) stamps before this runs.
|
|
482
|
+
*/
|
|
480
483
|
export async function dispatchGitHubWebhook(input: {
|
|
481
484
|
options: GitHubChannelOptions;
|
|
482
485
|
eventName: string | null;
|
|
@@ -484,23 +487,7 @@ export async function dispatchGitHubWebhook(input: {
|
|
|
484
487
|
deliveryId?: string;
|
|
485
488
|
args: ChannelHandlerArgs;
|
|
486
489
|
}): Promise<Response> {
|
|
487
|
-
|
|
488
|
-
let args = input.args;
|
|
489
|
-
if (
|
|
490
|
-
continuationSource(identity, "github") === "control-plane" &&
|
|
491
|
-
input.eventName !== null
|
|
492
|
-
) {
|
|
493
|
-
const mailbox = stampMailboxContinuation(
|
|
494
|
-
identity,
|
|
495
|
-
input.eventName,
|
|
496
|
-
input.payload
|
|
497
|
-
);
|
|
498
|
-
args = {
|
|
499
|
-
...input.args,
|
|
500
|
-
send: bindMailboxContinuationSend(input.args.send, mailbox),
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
return dispatchGitHubWebhookInner({ ...input, args });
|
|
490
|
+
return dispatchGitHubWebhookInner(input);
|
|
504
491
|
}
|
|
505
492
|
|
|
506
493
|
async function dispatchGitHubWebhookInner(input: {
|
|
@@ -644,12 +631,22 @@ export async function handleGitHubWebhookRequest(input: {
|
|
|
644
631
|
return Response.json({ ok: false, error: "invalid_json" }, { status: 400 });
|
|
645
632
|
}
|
|
646
633
|
|
|
634
|
+
const eventName = input.request.headers.get("x-github-event");
|
|
635
|
+
const args =
|
|
636
|
+
eventName === null
|
|
637
|
+
? input.args
|
|
638
|
+
: bindLocalIngressSend(input.args, {
|
|
639
|
+
identity: input.options.continuation,
|
|
640
|
+
kind: "github",
|
|
641
|
+
eventType: eventName,
|
|
642
|
+
payload,
|
|
643
|
+
});
|
|
647
644
|
return dispatchGitHubWebhook({
|
|
648
645
|
options: input.options,
|
|
649
|
-
eventName
|
|
646
|
+
eventName,
|
|
650
647
|
payload,
|
|
651
648
|
deliveryId: input.request.headers.get("x-github-delivery") ?? undefined,
|
|
652
|
-
args
|
|
649
|
+
args,
|
|
653
650
|
});
|
|
654
651
|
}
|
|
655
652
|
|
|
@@ -7,12 +7,11 @@ import {
|
|
|
7
7
|
POST,
|
|
8
8
|
} from "../../channels.js";
|
|
9
9
|
import {
|
|
10
|
-
bindMailboxContinuationSend,
|
|
11
10
|
continuationSource,
|
|
12
11
|
normalizeIdentity,
|
|
13
|
-
stampMailboxContinuation,
|
|
14
12
|
} from "../../internal/continuation-identity.js";
|
|
15
13
|
import { isDevMode } from "../../internal/dev-mode.js";
|
|
14
|
+
import { bindLocalIngressSend } from "../../internal/local-ingress.js";
|
|
16
15
|
import type {
|
|
17
16
|
AgentCloudOptions,
|
|
18
17
|
AuthContext,
|
|
@@ -364,6 +363,20 @@ export function originContinuationKey(
|
|
|
364
363
|
return `origin-pr:${ctx.repository.fullName}#${ctx.pullRequestNumber}`;
|
|
365
364
|
}
|
|
366
365
|
|
|
366
|
+
/** Payload shape local/hosted CP extract expects (`fullName` + number). */
|
|
367
|
+
export function originIngressExtractPayload(
|
|
368
|
+
envelope: OriginWebhookEnvelope
|
|
369
|
+
): unknown {
|
|
370
|
+
const parsed = parseOriginPullRequestEvent({ envelope });
|
|
371
|
+
if (parsed === null) {
|
|
372
|
+
return envelope.event.payload;
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
pullRequest: { number: parsed.ctx.pullRequestNumber },
|
|
376
|
+
repository: { fullName: parsed.ctx.repository.fullName },
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
367
380
|
export function defaultOriginTitle(
|
|
368
381
|
ctx: Pick<OriginEventContext, "repository" | "pullRequestNumber">
|
|
369
382
|
): string {
|
|
@@ -532,6 +545,10 @@ async function applyDispatch(input: {
|
|
|
532
545
|
* {@link handleAgentServeTrigger} fan-out. Signature verification is the
|
|
533
546
|
* caller's responsibility (HTTP route verifies; internal fan-out skips).
|
|
534
547
|
*/
|
|
548
|
+
/**
|
|
549
|
+
* Run authored Origin hooks and `send()`. Does not extract a v2 mailbox —
|
|
550
|
+
* the local CP (or hosted verified ingress) stamps before this runs.
|
|
551
|
+
*/
|
|
535
552
|
export async function dispatchOriginWebhook(input: {
|
|
536
553
|
options: OriginChannelOptions;
|
|
537
554
|
envelope: OriginWebhookEnvelope;
|
|
@@ -545,18 +562,7 @@ export async function dispatchOriginWebhook(input: {
|
|
|
545
562
|
{ status: 400 }
|
|
546
563
|
);
|
|
547
564
|
}
|
|
548
|
-
const
|
|
549
|
-
let args = input.args;
|
|
550
|
-
if (continuationSource(identity, "origin") === "control-plane") {
|
|
551
|
-
const mailbox = stampMailboxContinuation(identity, eventType, {
|
|
552
|
-
pullRequest: { number: parsed.ctx.pullRequestNumber },
|
|
553
|
-
repository: { fullName: parsed.ctx.repository.fullName },
|
|
554
|
-
});
|
|
555
|
-
args = {
|
|
556
|
-
...input.args,
|
|
557
|
-
send: bindMailboxContinuationSend(input.args.send, mailbox),
|
|
558
|
-
};
|
|
559
|
-
}
|
|
565
|
+
const args = input.args;
|
|
560
566
|
const ctx = enrichEventContext({
|
|
561
567
|
ctx: parsed.ctx,
|
|
562
568
|
args,
|
|
@@ -711,7 +717,12 @@ export async function handleOriginWebhookRequest(input: {
|
|
|
711
717
|
return dispatchOriginWebhook({
|
|
712
718
|
options: input.options,
|
|
713
719
|
envelope,
|
|
714
|
-
args: input.args,
|
|
720
|
+
args: bindLocalIngressSend(input.args, {
|
|
721
|
+
identity: input.options.continuation,
|
|
722
|
+
kind: "origin",
|
|
723
|
+
eventType: envelope.event.type,
|
|
724
|
+
payload: originIngressExtractPayload(envelope),
|
|
725
|
+
}),
|
|
715
726
|
});
|
|
716
727
|
}
|
|
717
728
|
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
continuationSource,
|
|
8
8
|
type MailboxContinuation,
|
|
9
9
|
normalizeIdentity,
|
|
10
|
-
stampMailboxContinuation,
|
|
11
10
|
} from "../../internal/continuation-identity.js";
|
|
11
|
+
import { routeLocalIngressContinuation } from "../../internal/local-ingress.js";
|
|
12
12
|
import type {
|
|
13
13
|
ArtifactsApi,
|
|
14
14
|
HostContext,
|
|
@@ -208,12 +208,16 @@ function slackSessionContinuation(
|
|
|
208
208
|
const key = slackContinuationKey(channelId, threadTs);
|
|
209
209
|
return { probeKey: key, sendPatch: { continuationToken: key } };
|
|
210
210
|
}
|
|
211
|
-
const
|
|
212
|
-
normalizeIdentity(identity, "slack"),
|
|
213
|
-
"
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
);
|
|
211
|
+
const routed = routeLocalIngressContinuation({
|
|
212
|
+
identity: normalizeIdentity(identity, "slack"),
|
|
213
|
+
kind: "slack",
|
|
214
|
+
eventType: "message",
|
|
215
|
+
payload: { channelId, threadTs },
|
|
216
|
+
});
|
|
217
|
+
const mailbox = routed.mailboxContinuation;
|
|
218
|
+
if (mailbox === undefined) {
|
|
219
|
+
throw new Error("v2 slack ingress did not stamp a mailbox");
|
|
220
|
+
}
|
|
217
221
|
return { probeKey: mailbox.key, sendPatch: { mailboxContinuation: mailbox } };
|
|
218
222
|
}
|
|
219
223
|
|
|
@@ -361,6 +361,16 @@ export function extractContinuation(
|
|
|
361
361
|
}
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Extract primitive used by the control plane (hosted
|
|
366
|
+
* `routeVerifiedIngressContinuation` and local
|
|
367
|
+
* `routeLocalIngressContinuation`). Channel adapters must not call this.
|
|
368
|
+
*/
|
|
369
|
+
/**
|
|
370
|
+
* Extract primitive used by the control plane (hosted
|
|
371
|
+
* `routeVerifiedIngressContinuation` and local
|
|
372
|
+
* `routeLocalIngressContinuation`). Channel adapters must not call this.
|
|
373
|
+
*/
|
|
364
374
|
export function stampMailboxContinuation(
|
|
365
375
|
identity: ContinuationIdentity,
|
|
366
376
|
eventName: string,
|
|
@@ -22,13 +22,13 @@ const DEFAULT_EVENTS: readonly DeploymentEventType[] = ["deployment.succeeded"];
|
|
|
22
22
|
export type DeploymentRelayEvent = DeploymentEvent & { offset: string };
|
|
23
23
|
|
|
24
24
|
export interface ResolvedSubscription {
|
|
25
|
-
|
|
25
|
+
deploySourceUri: string;
|
|
26
26
|
environments: string[];
|
|
27
27
|
events: Set<DeploymentEventType>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
interface SubscriptionStatus {
|
|
31
|
-
|
|
31
|
+
deploySourceUri: string;
|
|
32
32
|
isSubscribed: boolean;
|
|
33
33
|
detail?: string;
|
|
34
34
|
}
|
|
@@ -44,22 +44,22 @@ export interface DeploymentEventRelayOptions {
|
|
|
44
44
|
pollWaitMs?: number;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/** Normalize the channel's `
|
|
47
|
+
/** Normalize the channel's `deploySourceUris` option into concrete subscriptions. */
|
|
48
48
|
export function resolveDeploymentSubscriptions(options: {
|
|
49
|
-
|
|
49
|
+
deploySourceUris: readonly (string | DeploymentSubscription)[];
|
|
50
50
|
environments?: readonly string[];
|
|
51
51
|
events?: readonly DeploymentEventType[];
|
|
52
52
|
}): ResolvedSubscription[] {
|
|
53
|
-
const
|
|
54
|
-
for (const entry of options.
|
|
53
|
+
const bySource = new Map<string, ResolvedSubscription>();
|
|
54
|
+
for (const entry of options.deploySourceUris) {
|
|
55
55
|
const subscription: DeploymentSubscription =
|
|
56
|
-
typeof entry === "string" ? {
|
|
57
|
-
// Case-folded only: `Deployment.
|
|
58
|
-
// the server keys subscriptions the same way.
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
56
|
+
typeof entry === "string" ? { deploySourceUri: entry } : entry;
|
|
57
|
+
// Case-folded only: `Deployment.deploy_source_uri` has no documented
|
|
58
|
+
// format, and the server keys subscriptions the same way.
|
|
59
|
+
const deploySourceUri = subscription.deploySourceUri.trim().toLowerCase();
|
|
60
|
+
if (deploySourceUri === "") {
|
|
61
61
|
throw new Error(
|
|
62
|
-
`deploymentsChannel: invalid
|
|
62
|
+
`deploymentsChannel: invalid deploy source "${subscription.deploySourceUri}".`
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
const environments = [
|
|
@@ -74,14 +74,14 @@ export function resolveDeploymentSubscriptions(options: {
|
|
|
74
74
|
);
|
|
75
75
|
if (events.size === 0) {
|
|
76
76
|
throw new Error(
|
|
77
|
-
`deploymentsChannel:
|
|
77
|
+
`deploymentsChannel: deploy source "${deploySourceUri}" subscribes to no events.`
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
|
-
// Later entries for the same
|
|
81
|
-
// silently replacing it.
|
|
82
|
-
const existing =
|
|
80
|
+
// Later entries for the same deploy source widen the earlier one rather
|
|
81
|
+
// than silently replacing it.
|
|
82
|
+
const existing = bySource.get(deploySourceUri);
|
|
83
83
|
if (existing === undefined) {
|
|
84
|
-
|
|
84
|
+
bySource.set(deploySourceUri, { deploySourceUri, environments, events });
|
|
85
85
|
continue;
|
|
86
86
|
}
|
|
87
87
|
for (const event of events) existing.events.add(event);
|
|
@@ -91,9 +91,10 @@ export function resolveDeploymentSubscriptions(options: {
|
|
|
91
91
|
: [...new Set([...existing.environments, ...environments])];
|
|
92
92
|
}
|
|
93
93
|
// Empty is legal here and means "this channel wants nothing", which is how
|
|
94
|
-
// an env-configured channel stays inert without a
|
|
95
|
-
// empty list means deregister, so the host simply never
|
|
96
|
-
|
|
94
|
+
// an env-configured channel stays inert without a deploy-source list. The
|
|
95
|
+
// wire-level empty list means deregister, so the host simply never
|
|
96
|
+
// registers it.
|
|
97
|
+
return [...bySource.values()];
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
/** Whether one confirmed subscription wants this event. */
|
|
@@ -136,7 +137,7 @@ export async function fanOutDeploymentEvent(input: {
|
|
|
136
137
|
const matched = input.targets.filter((target) =>
|
|
137
138
|
target.subscriptions.some(
|
|
138
139
|
(subscription) =>
|
|
139
|
-
subscription.
|
|
140
|
+
subscription.deploySourceUri === event.deploySourceUri &&
|
|
140
141
|
deploymentEventMatches(subscription, event)
|
|
141
142
|
)
|
|
142
143
|
);
|
|
@@ -154,7 +155,7 @@ export async function fanOutDeploymentEvent(input: {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
logger(
|
|
157
|
-
`[agent-sdk] deployment-events: ${event.eventType} ${event.
|
|
158
|
+
`[agent-sdk] deployment-events: ${event.eventType} ${event.deploySourceUri}@${event.deployVersion} (${event.environment}) → ${matched.length - failures.length}/${matched.length} channel(s)`
|
|
158
159
|
);
|
|
159
160
|
if (failures.length > 0 && failures.length === matched.length) {
|
|
160
161
|
throw new Error(
|
|
@@ -166,7 +167,7 @@ export async function fanOutDeploymentEvent(input: {
|
|
|
166
167
|
export class DeploymentEventRelay {
|
|
167
168
|
private readonly core: CursorRelayCore<DeploymentRelayEvent>;
|
|
168
169
|
private subscribed = new Map<string, ResolvedSubscription>();
|
|
169
|
-
private readonly
|
|
170
|
+
private readonly loggedSkippedSources = new Set<string>();
|
|
170
171
|
|
|
171
172
|
constructor(private readonly options: DeploymentEventRelayOptions) {
|
|
172
173
|
const adapter: RelayAdapter<DeploymentRelayEvent> = {
|
|
@@ -176,7 +177,7 @@ export class DeploymentEventRelay {
|
|
|
176
177
|
buildRegistrationBody: (consumerId) => ({
|
|
177
178
|
consumerId,
|
|
178
179
|
subscriptions: options.subscriptions.map((subscription) => ({
|
|
179
|
-
|
|
180
|
+
deploySourceUri: subscription.deploySourceUri,
|
|
180
181
|
environments: subscription.environments,
|
|
181
182
|
events: [...subscription.events],
|
|
182
183
|
})),
|
|
@@ -188,23 +189,24 @@ export class DeploymentEventRelay {
|
|
|
188
189
|
subscriptions: [],
|
|
189
190
|
}),
|
|
190
191
|
applyRegistrationResponse: (json) => {
|
|
191
|
-
const result = json as {
|
|
192
|
+
const result = json as { deploySourceUris?: SubscriptionStatus[] };
|
|
192
193
|
this.subscribed = new Map();
|
|
193
|
-
this.
|
|
194
|
+
this.loggedSkippedSources.clear();
|
|
194
195
|
const lines: string[] = [];
|
|
195
196
|
const confirmed: string[] = [];
|
|
196
|
-
for (const status of result.
|
|
197
|
+
for (const status of result.deploySourceUris ?? []) {
|
|
197
198
|
const wanted = options.subscriptions.find(
|
|
198
|
-
(subscription) =>
|
|
199
|
+
(subscription) =>
|
|
200
|
+
subscription.deploySourceUri === status.deploySourceUri
|
|
199
201
|
);
|
|
200
202
|
if (!status.isSubscribed || wanted === undefined) {
|
|
201
203
|
lines.push(
|
|
202
|
-
`not subscribed to ${status.
|
|
204
|
+
`not subscribed to ${status.deploySourceUri}: ${status.detail ?? "unknown reason"}`
|
|
203
205
|
);
|
|
204
206
|
continue;
|
|
205
207
|
}
|
|
206
|
-
this.subscribed.set(status.
|
|
207
|
-
confirmed.push(status.
|
|
208
|
+
this.subscribed.set(status.deploySourceUri, wanted);
|
|
209
|
+
confirmed.push(status.deploySourceUri);
|
|
208
210
|
}
|
|
209
211
|
if (confirmed.length > 0) {
|
|
210
212
|
const kinds = [
|
|
@@ -247,16 +249,16 @@ export class DeploymentEventRelay {
|
|
|
247
249
|
if (
|
|
248
250
|
envelope === null ||
|
|
249
251
|
typeof envelope !== "object" ||
|
|
250
|
-
typeof envelope.
|
|
252
|
+
typeof envelope.deploySourceUri !== "string"
|
|
251
253
|
) {
|
|
252
254
|
return false;
|
|
253
255
|
}
|
|
254
|
-
const subscription = this.subscribed.get(envelope.
|
|
256
|
+
const subscription = this.subscribed.get(envelope.deploySourceUri);
|
|
255
257
|
if (subscription === undefined) {
|
|
256
|
-
if (!this.
|
|
257
|
-
this.
|
|
258
|
+
if (!this.loggedSkippedSources.has(envelope.deploySourceUri)) {
|
|
259
|
+
this.loggedSkippedSources.add(envelope.deploySourceUri);
|
|
258
260
|
this.options.logger(
|
|
259
|
-
`[agent-sdk] deployment-events: skipping events for ${envelope.
|
|
261
|
+
`[agent-sdk] deployment-events: skipping events for ${envelope.deploySourceUri}: not in this host's subscription (another consumer of this API key likely subscribed it)`
|
|
260
262
|
);
|
|
261
263
|
}
|
|
262
264
|
return false;
|
|
@@ -19,10 +19,15 @@ import type { GitHubChannelOptions } from "../channels/github/types.js";
|
|
|
19
19
|
import {
|
|
20
20
|
dispatchOriginWebhook,
|
|
21
21
|
getOriginChannelOptions,
|
|
22
|
+
originIngressExtractPayload,
|
|
22
23
|
parseOriginWebhookEnvelope,
|
|
23
24
|
} from "../channels/origin/origin-channel.js";
|
|
24
25
|
import type { OriginChannelOptions } from "../channels/origin/types.js";
|
|
25
26
|
import type { ChannelHandlerArgs } from "../types.js";
|
|
27
|
+
import {
|
|
28
|
+
bindLocalIngressSend,
|
|
29
|
+
type RouteLocalIngressContinuation,
|
|
30
|
+
} from "./local-ingress.js";
|
|
26
31
|
|
|
27
32
|
export interface AgentServeGitHubTriggerTarget {
|
|
28
33
|
/** Agent mount slug (empty string in single-agent mode). */
|
|
@@ -40,6 +45,8 @@ export interface AgentServeGitHubTriggerEvent {
|
|
|
40
45
|
eventName: string | null;
|
|
41
46
|
deliveryId?: string;
|
|
42
47
|
targets: AgentServeGitHubTriggerTarget[];
|
|
48
|
+
/** Local CP ingest. Defaults to {@link routeLocalIngressContinuation}. */
|
|
49
|
+
routeContinuation?: RouteLocalIngressContinuation;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
export interface AgentServeOriginTriggerTarget {
|
|
@@ -53,6 +60,8 @@ export interface AgentServeOriginTriggerEvent {
|
|
|
53
60
|
/** Origin envelope JSON (signature already verified by the caller). */
|
|
54
61
|
bodyText: string;
|
|
55
62
|
targets: AgentServeOriginTriggerTarget[];
|
|
63
|
+
/** Local CP ingest. Defaults to {@link routeLocalIngressContinuation}. */
|
|
64
|
+
routeContinuation?: RouteLocalIngressContinuation;
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
export type AgentServeTriggerEvent =
|
|
@@ -177,12 +186,24 @@ async function handleAgentServeGitHubTrigger(
|
|
|
177
186
|
|
|
178
187
|
const targets: AgentServeTriggerTargetResult[] = [];
|
|
179
188
|
for (const target of event.targets) {
|
|
189
|
+
const args =
|
|
190
|
+
event.eventName === null
|
|
191
|
+
? target.args
|
|
192
|
+
: bindLocalIngressSend(target.args, {
|
|
193
|
+
identity: target.options.continuation,
|
|
194
|
+
kind: "github",
|
|
195
|
+
eventType: event.eventName,
|
|
196
|
+
payload,
|
|
197
|
+
...(event.routeContinuation === undefined
|
|
198
|
+
? {}
|
|
199
|
+
: { routeContinuation: event.routeContinuation }),
|
|
200
|
+
});
|
|
180
201
|
const response = await dispatchGitHubWebhook({
|
|
181
202
|
options: target.options,
|
|
182
203
|
eventName: event.eventName,
|
|
183
204
|
payload,
|
|
184
205
|
deliveryId: event.deliveryId,
|
|
185
|
-
args
|
|
206
|
+
args,
|
|
186
207
|
});
|
|
187
208
|
targets.push(await triggerTargetResult(target.slug, response));
|
|
188
209
|
}
|
|
@@ -234,7 +255,15 @@ async function handleAgentServeOriginTrigger(
|
|
|
234
255
|
const response = await dispatchOriginWebhook({
|
|
235
256
|
options: target.options,
|
|
236
257
|
envelope,
|
|
237
|
-
args: target.args,
|
|
258
|
+
args: bindLocalIngressSend(target.args, {
|
|
259
|
+
identity: target.options.continuation,
|
|
260
|
+
kind: "origin",
|
|
261
|
+
eventType: envelope.event.type,
|
|
262
|
+
payload: originIngressExtractPayload(envelope),
|
|
263
|
+
...(event.routeContinuation === undefined
|
|
264
|
+
? {}
|
|
265
|
+
: { routeContinuation: event.routeContinuation }),
|
|
266
|
+
}),
|
|
238
267
|
});
|
|
239
268
|
targets.push(await triggerTargetResult(target.slug, response));
|
|
240
269
|
}
|