@cursor/july 0.1.74 → 0.1.76
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/checks.d.ts +111 -0
- package/dist/channels/checks.d.ts.map +1 -0
- package/dist/channels/checks.js +86 -0
- package/dist/channels/deployments/deployments-channel.d.ts +44 -0
- package/dist/channels/deployments/deployments-channel.d.ts.map +1 -0
- package/dist/channels/deployments/deployments-channel.js +92 -0
- package/dist/channels/deployments/index.d.ts +26 -0
- package/dist/channels/deployments/index.d.ts.map +1 -0
- package/dist/channels/deployments/index.js +25 -0
- package/dist/channels/deployments/types.d.ts +69 -0
- package/dist/channels/deployments/types.d.ts.map +1 -0
- package/dist/channels/deployments/types.js +6 -0
- package/dist/channels/github/checks.d.ts +13 -48
- package/dist/channels/github/checks.d.ts.map +1 -1
- package/dist/channels/github/checks.js +56 -64
- package/dist/channels/origin/api.d.ts +100 -0
- package/dist/channels/origin/api.d.ts.map +1 -0
- package/dist/channels/origin/api.js +225 -0
- package/dist/channels/origin/binding.d.ts +44 -0
- package/dist/channels/origin/binding.d.ts.map +1 -0
- package/dist/channels/origin/binding.js +45 -0
- package/dist/channels/origin/checks.d.ts +107 -0
- package/dist/channels/origin/checks.d.ts.map +1 -0
- package/dist/channels/origin/checks.js +145 -0
- package/dist/channels/origin/index.d.ts +3 -0
- package/dist/channels/origin/index.d.ts.map +1 -1
- package/dist/channels/origin/index.js +3 -0
- package/dist/channels/origin/origin-channel.d.ts +4 -4
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +12 -2
- package/dist/channels/origin/types.d.ts +36 -0
- package/dist/channels/origin/types.d.ts.map +1 -1
- package/dist/channels.d.ts +1 -0
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +1 -0
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.BETd-WqU.js → app.b8bhB2SV.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DGZeXVqn.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.wEaGEmV9.js → VPLocalSearchBox.lLSeIyWw.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DNDicFOP.js → theme.C_owesa-.js} +2 -2
- package/dist/docs/assets/{reference_channels.md.MN8iDy8V.js → reference_channels.md.G5GysZ7e.js} +2 -2
- package/dist/docs/assets/{reference_channels.md.MN8iDy8V.lean.js → reference_channels.md.G5GysZ7e.lean.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/authored-alias-hooks.d.ts +12 -0
- package/dist/internal/authored-alias-hooks.d.ts.map +1 -1
- package/dist/internal/authored-alias-hooks.js +60 -8
- package/dist/internal/authored-loaders.d.ts +5 -3
- package/dist/internal/authored-loaders.d.ts.map +1 -1
- package/dist/internal/authored-loaders.js +13 -4
- package/dist/internal/cursor-deployment-relay.d.ts +77 -0
- package/dist/internal/cursor-deployment-relay.d.ts.map +1 -0
- package/dist/internal/cursor-deployment-relay.js +201 -0
- package/dist/internal/deployment-scm-event-stream.js +4 -4
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/review-comments.d.ts +62 -0
- package/dist/internal/review-comments.d.ts.map +1 -1
- package/dist/internal/review-comments.js +199 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +114 -5
- package/dist/playground/assets/{index-D_8I9TM0.js → index-BWqjJcRo.js} +1 -1
- package/dist/playground/assets/index-Bfwz2yLN.css +1 -0
- package/dist/playground/index.html +2 -2
- package/docs/reference/channels.md +15 -0
- package/package.json +7 -1
- package/src/channels/checks.ts +185 -0
- package/src/channels/deployments/deployments-channel.ts +109 -0
- package/src/channels/deployments/index.ts +26 -0
- package/src/channels/deployments/types.ts +80 -0
- package/src/channels/github/checks.ts +90 -115
- package/src/channels/origin/api.ts +318 -0
- package/src/channels/origin/binding.ts +96 -0
- package/src/channels/origin/checks.ts +250 -0
- package/src/channels/origin/index.ts +3 -0
- package/src/channels/origin/origin-channel.ts +18 -6
- package/src/channels/origin/types.ts +37 -0
- package/src/channels.ts +16 -0
- package/src/internal/authored-alias-hooks.ts +75 -7
- package/src/internal/authored-loaders.ts +15 -4
- package/src/internal/cursor-deployment-relay.ts +274 -0
- package/src/internal/deployment-scm-event-stream.ts +4 -4
- package/src/internal/distribution.ts +1 -0
- package/src/internal/review-comments.ts +286 -0
- package/src/internal/server.ts +149 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.DM5INS71.js +0 -1
- package/dist/playground/assets/index-M3nSMc6_.css +0 -1
|
@@ -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-BWqjJcRo.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="./assets/index-Bfwz2yLN.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
|
@@ -217,6 +217,21 @@ replay and live forwarding. Author `agent/channels/github.ts` with
|
|
|
217
217
|
converge a merge-box check and sticky PR comment from default stream
|
|
218
218
|
events. Guide: [GitHub](../guides/github.md).
|
|
219
219
|
|
|
220
|
+
**Deployments** (`@cursor/july/channels/deployments`): pull transport
|
|
221
|
+
over `/v0/deployment-events`. Subscribe per repository with `repos`,
|
|
222
|
+
narrow with `environments` / `events`, and handle each event in
|
|
223
|
+
`onEvent`. `repos` must match `Deployment.repository` as your deployment
|
|
224
|
+
writer records it; the field has no format, and matching is
|
|
225
|
+
case-insensitive but otherwise literal.
|
|
226
|
+
Author `agent/channels/deployments.ts` with `deploymentsChannel()`. The
|
|
227
|
+
serve host discovers every mounted deployments channel, runs one relay
|
|
228
|
+
for the process against the union of their repositories, and routes each
|
|
229
|
+
event to the channels that asked for it — the same ownership the Slack
|
|
230
|
+
and SCM relays have. It authenticates with the host credential and keeps
|
|
231
|
+
a durable offset, so a restart resumes rather than dropping events. An
|
|
232
|
+
empty `repos` list mounts the channel but starts no relay for it, so an
|
|
233
|
+
env-configured agent stays inert until its repositories are set.
|
|
234
|
+
|
|
220
235
|
For other platforms like Discord or Teams, use the authored
|
|
221
236
|
`defineChannel` webhook form.
|
|
222
237
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cursor/july",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.76",
|
|
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": {
|
|
@@ -77,6 +77,12 @@
|
|
|
77
77
|
"import": "./dist/channels/origin/index.js",
|
|
78
78
|
"default": "./dist/channels/origin/index.js"
|
|
79
79
|
},
|
|
80
|
+
"./channels/deployments": {
|
|
81
|
+
"anysphere-source": "./src/channels/deployments/index.ts",
|
|
82
|
+
"types": "./dist/channels/deployments/index.d.ts",
|
|
83
|
+
"import": "./dist/channels/deployments/index.js",
|
|
84
|
+
"default": "./dist/channels/deployments/index.js"
|
|
85
|
+
},
|
|
80
86
|
"./skills": {
|
|
81
87
|
"anysphere-source": "./src/skills.ts",
|
|
82
88
|
"types": "./dist/skills.d.ts",
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI checks, shared across SCMs.
|
|
3
|
+
*
|
|
4
|
+
* The check lifecycle lives here once: `openCiCheck` opens a run (defaults
|
|
5
|
+
* `in_progress`, stamps `startedAt`), the returned {@link CiCheck} handle
|
|
6
|
+
* patches it (`update`) and completes it (`conclude`, stamps `completedAt`),
|
|
7
|
+
* and `resumeCiCheck` rebuilds a handle after a restart. Each SCM plugs in
|
|
8
|
+
* with one {@link CiCheckWriter} — a function that persists a single desired
|
|
9
|
+
* check state on its API (GitHub Checks API create/patch, Origin's keyed
|
|
10
|
+
* upsert, …) and owns any transport state such as a server-assigned run id.
|
|
11
|
+
*
|
|
12
|
+
* Agent code should usually stay one level higher, on {@link CiChecksClient}:
|
|
13
|
+
* both `ctx.github` and `ctx.origin` satisfy it, so a check reporter written
|
|
14
|
+
* once works on either forge:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* async function reportCheck(checks: CiChecksClient, sha: string) {
|
|
18
|
+
* const check = await checks.createCheck({ name: "My Agent", sha });
|
|
19
|
+
* // …do the work…
|
|
20
|
+
* await check.conclude({
|
|
21
|
+
* conclusion: "success",
|
|
22
|
+
* output: { title: "Done", summary: "All good." },
|
|
23
|
+
* });
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* {@link CiCheckConclusion} is the common subset every SCM accepts; an SCM
|
|
28
|
+
* with extras widens it through the generic parameter (e.g. Origin's
|
|
29
|
+
* `CiCheck<CiCheckConclusion | "stale">`).
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** Output shown on the check page. */
|
|
33
|
+
export interface CiCheckOutput {
|
|
34
|
+
/** Heading on the check page. */
|
|
35
|
+
title: string;
|
|
36
|
+
/** Markdown body (supports links, e.g. `[View session](…)`). */
|
|
37
|
+
summary: string;
|
|
38
|
+
/** Optional detailed markdown body. */
|
|
39
|
+
text?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type CiCheckStatus = "queued" | "in_progress" | "completed";
|
|
43
|
+
|
|
44
|
+
/** Final results accepted by every SCM. */
|
|
45
|
+
export type CiCheckConclusion =
|
|
46
|
+
| "action_required"
|
|
47
|
+
| "cancelled"
|
|
48
|
+
| "failure"
|
|
49
|
+
| "neutral"
|
|
50
|
+
| "skipped"
|
|
51
|
+
| "success"
|
|
52
|
+
| "timed_out";
|
|
53
|
+
|
|
54
|
+
export interface CiCheckUpdateInput<
|
|
55
|
+
TConclusion extends string = CiCheckConclusion,
|
|
56
|
+
> {
|
|
57
|
+
status?: CiCheckStatus;
|
|
58
|
+
conclusion?: TConclusion;
|
|
59
|
+
detailsUrl?: string;
|
|
60
|
+
output?: CiCheckOutput;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CiCheckConcludeInput<
|
|
64
|
+
TConclusion extends string = CiCheckConclusion,
|
|
65
|
+
> {
|
|
66
|
+
/** Final result shown in the merge box. */
|
|
67
|
+
conclusion: TConclusion;
|
|
68
|
+
detailsUrl?: string;
|
|
69
|
+
output?: CiCheckOutput;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** SCM-side reference to a check run, when the SCM reports one. */
|
|
73
|
+
export interface CiCheckRef {
|
|
74
|
+
/** Server-assigned run id (GitHub check-run number, Origin `cr_…`). */
|
|
75
|
+
id?: number | string;
|
|
76
|
+
/** Web URL of the check run page. */
|
|
77
|
+
url?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A created check run, whichever SCM it lives on. */
|
|
81
|
+
export interface CiCheck<TConclusion extends string = CiCheckConclusion> {
|
|
82
|
+
/** Patch status / output / details link while the check is running. */
|
|
83
|
+
update(input: CiCheckUpdateInput<TConclusion>): Promise<void>;
|
|
84
|
+
/** Complete the check with a conclusion (stamps `completedAt`). */
|
|
85
|
+
conclude(input: CiCheckConcludeInput<TConclusion>): Promise<void>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface CiCheckCreateInput {
|
|
89
|
+
/** Check name shown in the PR merge box, e.g. `"Cursor Security Review"`. */
|
|
90
|
+
name: string;
|
|
91
|
+
/** Commit the check attaches to (usually the PR head SHA). */
|
|
92
|
+
sha: string;
|
|
93
|
+
/** Initial status. Defaults to `"in_progress"`. */
|
|
94
|
+
status?: "queued" | "in_progress";
|
|
95
|
+
/** "Details" link on the check, e.g. the agent session run URL. */
|
|
96
|
+
detailsUrl?: string;
|
|
97
|
+
/** Title + markdown summary shown on the check page. */
|
|
98
|
+
output?: CiCheckOutput;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Anything that can open a CI check on its bound repository — `ctx.github`,
|
|
103
|
+
* `ctx.origin`, or the `bindOriginChecks` adapter.
|
|
104
|
+
*/
|
|
105
|
+
export interface CiChecksClient<
|
|
106
|
+
TConclusion extends string = CiCheckConclusion,
|
|
107
|
+
> {
|
|
108
|
+
createCheck(input: CiCheckCreateInput): Promise<CiCheck<TConclusion>>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** One desired check state for a writer to persist. */
|
|
112
|
+
export interface CiCheckWrite<TConclusion extends string = CiCheckConclusion>
|
|
113
|
+
extends CiCheckUpdateInput<TConclusion> {
|
|
114
|
+
startedAt?: string;
|
|
115
|
+
completedAt?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The per-SCM extension point: persist one {@link CiCheckWrite} on the SCM
|
|
120
|
+
* (create-then-patch, keyed upsert, …). Bound to a repo + check identity at
|
|
121
|
+
* construction; owns transport state (e.g. the created run id) across calls.
|
|
122
|
+
* May return a {@link CiCheckRef} to expose on the handle.
|
|
123
|
+
*/
|
|
124
|
+
export type CiCheckWriter<TConclusion extends string = CiCheckConclusion> = (
|
|
125
|
+
write: CiCheckWrite<TConclusion>
|
|
126
|
+
) => Promise<CiCheckRef | undefined>;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Open a check run through `writer` and return a handle for later updates.
|
|
130
|
+
* SCM modules wrap this (`createGitHubCheck`, `createOriginCheck`); channels
|
|
131
|
+
* expose it repo-bound as `ctx.github.createCheck` / `ctx.origin.createCheck`.
|
|
132
|
+
*/
|
|
133
|
+
export async function openCiCheck<
|
|
134
|
+
TConclusion extends string = CiCheckConclusion,
|
|
135
|
+
>(
|
|
136
|
+
writer: CiCheckWriter<TConclusion>,
|
|
137
|
+
input?: Pick<CiCheckCreateInput, "status" | "detailsUrl" | "output">
|
|
138
|
+
): Promise<CiCheck<TConclusion> & CiCheckRef> {
|
|
139
|
+
const ref = await writer({
|
|
140
|
+
status: input?.status ?? "in_progress",
|
|
141
|
+
startedAt: new Date().toISOString(),
|
|
142
|
+
detailsUrl: input?.detailsUrl,
|
|
143
|
+
output: input?.output,
|
|
144
|
+
});
|
|
145
|
+
return resumeCiCheck(writer, ref);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Rebuild a {@link CiCheck} handle around an existing run (e.g. after a
|
|
150
|
+
* process restart). No write happens until the first update.
|
|
151
|
+
*/
|
|
152
|
+
export function resumeCiCheck<TConclusion extends string = CiCheckConclusion>(
|
|
153
|
+
writer: CiCheckWriter<TConclusion>,
|
|
154
|
+
ref?: CiCheckRef
|
|
155
|
+
): CiCheck<TConclusion> & CiCheckRef {
|
|
156
|
+
let id = ref?.id;
|
|
157
|
+
let url = ref?.url;
|
|
158
|
+
const write = async (write: CiCheckWrite<TConclusion>): Promise<void> => {
|
|
159
|
+
const next = await writer(write);
|
|
160
|
+
id = next?.id ?? id;
|
|
161
|
+
url = next?.url ?? url;
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
get id() {
|
|
165
|
+
return id;
|
|
166
|
+
},
|
|
167
|
+
get url() {
|
|
168
|
+
return url;
|
|
169
|
+
},
|
|
170
|
+
update: (update) =>
|
|
171
|
+
write({
|
|
172
|
+
...update,
|
|
173
|
+
completedAt:
|
|
174
|
+
update.status === "completed" ? new Date().toISOString() : undefined,
|
|
175
|
+
}),
|
|
176
|
+
conclude: (conclude) =>
|
|
177
|
+
write({
|
|
178
|
+
status: "completed",
|
|
179
|
+
conclusion: conclude.conclusion,
|
|
180
|
+
completedAt: new Date().toISOString(),
|
|
181
|
+
detailsUrl: conclude.detailsUrl,
|
|
182
|
+
output: conclude.output,
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { defineChannel, localDevStrict, POST } from "../../channels.js";
|
|
4
|
+
import { resolveDeploymentSubscriptions } from "../../internal/cursor-deployment-relay.js";
|
|
5
|
+
import type { ChannelDefinition, ChannelHandlerArgs } from "../../types.js";
|
|
6
|
+
import {
|
|
7
|
+
DEPLOYMENT_EVENT_TYPES,
|
|
8
|
+
DEPLOYMENTS_CHANNEL_KIND,
|
|
9
|
+
type DeploymentEvent,
|
|
10
|
+
type DeploymentsChannelOptions,
|
|
11
|
+
} from "./types.js";
|
|
12
|
+
|
|
13
|
+
const deploymentEventSchema = z.object({
|
|
14
|
+
eventType: z.enum(DEPLOYMENT_EVENT_TYPES),
|
|
15
|
+
eventId: z.string().min(1),
|
|
16
|
+
teamId: z.number(),
|
|
17
|
+
repo: z.string().min(1),
|
|
18
|
+
deploymentId: z.string().min(1),
|
|
19
|
+
environment: z.string().min(1),
|
|
20
|
+
service: z.string().min(1).optional(),
|
|
21
|
+
sha: z.string().min(1),
|
|
22
|
+
status: z.enum(["pending", "in_progress", "success", "failed"]),
|
|
23
|
+
occurredAtMs: z.number(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/** Options used to construct a deployments channel — for host-level fan-out. */
|
|
27
|
+
const deploymentsChannelOptionsByDefinition = new WeakMap<
|
|
28
|
+
object,
|
|
29
|
+
DeploymentsChannelOptions
|
|
30
|
+
>();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Recover the {@link DeploymentsChannelOptions} used to build a channel
|
|
34
|
+
* definition, if it was created via {@link deploymentsChannel}.
|
|
35
|
+
*/
|
|
36
|
+
export function getDeploymentsChannelOptions(
|
|
37
|
+
definition: object
|
|
38
|
+
): DeploymentsChannelOptions | undefined {
|
|
39
|
+
return deploymentsChannelOptionsByDefinition.get(definition);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Core deployment-event dispatch, used by the channel's replay route and by
|
|
44
|
+
* the serve host's relay fan-out. Whether the event matches this channel's
|
|
45
|
+
* subscriptions is the caller's decision.
|
|
46
|
+
*/
|
|
47
|
+
export async function dispatchDeploymentEvent(input: {
|
|
48
|
+
options: DeploymentsChannelOptions;
|
|
49
|
+
event: DeploymentEvent;
|
|
50
|
+
args: ChannelHandlerArgs;
|
|
51
|
+
}): Promise<Response> {
|
|
52
|
+
await input.options.onEvent(input.event, input.args);
|
|
53
|
+
return Response.json({ ok: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Deployment events channel.
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { deploymentsChannel } from "@cursor/july/channels/deployments";
|
|
61
|
+
*
|
|
62
|
+
* export default deploymentsChannel({
|
|
63
|
+
* repos: ["repos/anysphere/everysphere"], // as your writer records it
|
|
64
|
+
* environments: ["prod"],
|
|
65
|
+
* onEvent: async (event, { send }) => {
|
|
66
|
+
* await send(`${event.repo}@${event.sha} shipped to ${event.environment}`);
|
|
67
|
+
* },
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* Declarative: the channel names what it wants and handles what arrives. The
|
|
72
|
+
* serve host discovers every mounted deployments channel, opens one
|
|
73
|
+
* `/v0/deployment-events` relay for the process, and routes each event to the
|
|
74
|
+
* channels subscribed to it — the same ownership the Slack and SCM relays
|
|
75
|
+
* have. Nothing is pushed to this deployment and no shared secret exists on
|
|
76
|
+
* either side.
|
|
77
|
+
*
|
|
78
|
+
* The route on this channel exists for local replay (`agent-sdk` and tests):
|
|
79
|
+
* POST an event body and it runs the same `onEvent`.
|
|
80
|
+
*/
|
|
81
|
+
export function deploymentsChannel(
|
|
82
|
+
options: DeploymentsChannelOptions
|
|
83
|
+
): ChannelDefinition {
|
|
84
|
+
// Throws here rather than at startup, so a malformed `repos` entry fails
|
|
85
|
+
// when the channel module is authored instead of when the host mounts it.
|
|
86
|
+
resolveDeploymentSubscriptions(options);
|
|
87
|
+
const routePath = options.path ?? "/";
|
|
88
|
+
|
|
89
|
+
const definition = defineChannel({
|
|
90
|
+
auth: options.auth ?? [localDevStrict()],
|
|
91
|
+
meta: { kind: DEPLOYMENTS_CHANNEL_KIND },
|
|
92
|
+
...(options.onStart === undefined ? {} : { onStart: options.onStart }),
|
|
93
|
+
...(options.onStop === undefined ? {} : { onStop: options.onStop }),
|
|
94
|
+
routes: [
|
|
95
|
+
POST(routePath, {
|
|
96
|
+
description: "Replay one deployment event onto this channel",
|
|
97
|
+
bodySchema: deploymentEventSchema,
|
|
98
|
+
handler: async (_request, args) =>
|
|
99
|
+
dispatchDeploymentEvent({
|
|
100
|
+
options,
|
|
101
|
+
event: args.body as DeploymentEvent,
|
|
102
|
+
args,
|
|
103
|
+
}),
|
|
104
|
+
}),
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
deploymentsChannelOptionsByDefinition.set(definition, options);
|
|
108
|
+
return definition;
|
|
109
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment events channel pack for agent-serve.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { deploymentsChannel } from "@cursor/july/channels/deployments";
|
|
6
|
+
*
|
|
7
|
+
* export default deploymentsChannel({
|
|
8
|
+
* repos: ["repos/anysphere/everysphere"], // as your writer records it
|
|
9
|
+
* environments: ["prod"],
|
|
10
|
+
* onEvent: (event, { send }) => send(`${event.sha} shipped`),
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The serve host owns the transport, as it does for Slack and SCM events: it
|
|
15
|
+
* discovers every mounted deployments channel, opens one `/v0/deployment-events`
|
|
16
|
+
* relay for the process with the host's Cursor credential, and routes each
|
|
17
|
+
* event to the channels subscribed to it. The relay long-polls with a durable
|
|
18
|
+
* client-owned offset, so a restart resumes rather than dropping events.
|
|
19
|
+
*
|
|
20
|
+
* An agent whose sessions live on another channel forwards from `onEvent`
|
|
21
|
+
* into that channel rather than starting sessions here, the way a GitHub
|
|
22
|
+
* relay channel dispatches into an origin channel.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export * from "./deployments-channel.js";
|
|
26
|
+
export * from "./types.js";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ChannelDefinition, ChannelHandlerArgs } from "../../types.js";
|
|
2
|
+
|
|
3
|
+
/** {@link ChannelMeta.kind} value used by {@link deploymentsChannel}. */
|
|
4
|
+
export const DEPLOYMENTS_CHANNEL_KIND = "deployments";
|
|
5
|
+
|
|
6
|
+
export const DEPLOYMENT_EVENT_TYPES = [
|
|
7
|
+
"deployment.created",
|
|
8
|
+
"deployment.succeeded",
|
|
9
|
+
] as const;
|
|
10
|
+
export type DeploymentEventType = (typeof DEPLOYMENT_EVENT_TYPES)[number];
|
|
11
|
+
|
|
12
|
+
export type DeploymentStatus = "pending" | "in_progress" | "success" | "failed";
|
|
13
|
+
|
|
14
|
+
/** One deployment event, as the control plane records it. */
|
|
15
|
+
export interface DeploymentEvent {
|
|
16
|
+
eventType: DeploymentEventType;
|
|
17
|
+
/** Stable id; dedupe on it if you act more than once per event. */
|
|
18
|
+
eventId: string;
|
|
19
|
+
/** Owning Cursor team. */
|
|
20
|
+
teamId: number;
|
|
21
|
+
/** The Deployment's repository string, case-folded. */
|
|
22
|
+
repo: string;
|
|
23
|
+
deploymentId: string;
|
|
24
|
+
/** Environment slug, e.g. `prod`. */
|
|
25
|
+
environment: string;
|
|
26
|
+
/** Absent when the deployment records no service. */
|
|
27
|
+
service?: string;
|
|
28
|
+
sha: string;
|
|
29
|
+
status: DeploymentStatus;
|
|
30
|
+
occurredAtMs: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** What a repository subscription asks for. */
|
|
34
|
+
export interface DeploymentSubscription {
|
|
35
|
+
/**
|
|
36
|
+
* The repository string as the factory records it on a Deployment. Opaque:
|
|
37
|
+
* whatever your deployment writer puts there, matched case-insensitively.
|
|
38
|
+
*/
|
|
39
|
+
repo: string;
|
|
40
|
+
/** Omit or leave empty for every environment. */
|
|
41
|
+
environments?: readonly string[];
|
|
42
|
+
/** Defaults to `deployment.succeeded`. */
|
|
43
|
+
events?: readonly DeploymentEventType[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DeploymentsChannelOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Repositories to watch, spelled exactly as your deployment writer records
|
|
49
|
+
* `Deployment.repository`. The field has no format — whatever the writer
|
|
50
|
+
* puts there is the key — and matching is case-insensitive but otherwise
|
|
51
|
+
* literal, so a value that does not match subscribes successfully and then
|
|
52
|
+
* receives nothing. A bare string takes the default environment and event
|
|
53
|
+
* filters.
|
|
54
|
+
*
|
|
55
|
+
* There is no team-wide subscription: every repository is named. At least
|
|
56
|
+
* one is required — an empty list is how a consumer *deregisters*, so it
|
|
57
|
+
* cannot also mean "everything".
|
|
58
|
+
*/
|
|
59
|
+
repos: readonly (string | DeploymentSubscription)[];
|
|
60
|
+
/**
|
|
61
|
+
* Applied to every subscription that does not set its own. Omit or leave
|
|
62
|
+
* empty for every environment.
|
|
63
|
+
*/
|
|
64
|
+
environments?: readonly string[];
|
|
65
|
+
/**
|
|
66
|
+
* Applied to every subscription that does not set its own. Defaults to
|
|
67
|
+
* `deployment.succeeded`.
|
|
68
|
+
*/
|
|
69
|
+
events?: readonly DeploymentEventType[];
|
|
70
|
+
/** Called for each matching event. Failures retry a few times, then skip. */
|
|
71
|
+
onEvent: (
|
|
72
|
+
event: DeploymentEvent,
|
|
73
|
+
args: ChannelHandlerArgs
|
|
74
|
+
) => Promise<unknown> | unknown;
|
|
75
|
+
/** Route path for the local replay POST. Defaults to `/`. */
|
|
76
|
+
path?: string;
|
|
77
|
+
auth?: ChannelDefinition["auth"];
|
|
78
|
+
onStart?: ChannelDefinition["onStart"];
|
|
79
|
+
onStop?: ChannelDefinition["onStop"];
|
|
80
|
+
}
|