@cursor/july 0.1.80 → 0.1.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +0 -12
- package/dist/channels/github/cursor-account.d.ts +11 -0
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +13 -0
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +29 -1
- package/dist/channels/github/types.d.ts +8 -0
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +32 -10
- package/dist/channels/origin/types.d.ts +7 -0
- package/dist/channels/origin/types.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +2 -2
- package/dist/channels/slack/dispatch.d.ts +3 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +18 -18
- package/dist/channels/slack/interactive.d.ts +2 -0
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +4 -16
- package/dist/channels/slack/slack-channel.d.ts +1 -1
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +21 -10
- package/dist/channels/slack/types.d.ts +7 -0
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +4 -0
- package/dist/continuation.d.ts +20 -0
- package/dist/continuation.d.ts.map +1 -0
- package/dist/continuation.js +18 -0
- package/dist/docs/404.html +1 -1
- package/dist/docs/ab.html +2 -2
- package/dist/docs/assets/{app.e4_wtjUS.js → app.BEHVaPKt.js} +1 -1
- package/dist/docs/assets/chunks/{@localSearchIndexroot.BGiMJpnL.js → @localSearchIndexroot.Vvio4stV.js} +1 -1
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DbdzyEie.js → VPLocalSearchBox.D2-5PKih.js} +1 -1
- package/dist/docs/assets/chunks/{theme.D8HpLrLK.js → theme.uNqJ4MUO.js} +2 -2
- package/dist/docs/building-with-agents.html +2 -2
- package/dist/docs/concepts.html +2 -2
- package/dist/docs/deployment.html +2 -2
- package/dist/docs/evals.html +2 -2
- package/dist/docs/example-agents/approval-buddy.html +2 -2
- package/dist/docs/example-agents/benny.html +2 -2
- package/dist/docs/example-agents/bugbot.html +2 -2
- package/dist/docs/example-agents/codebase-wiki.html +2 -2
- package/dist/docs/example-agents/codeowners-review.html +2 -2
- package/dist/docs/example-agents/concierge.html +2 -2
- package/dist/docs/example-agents/fsd.html +2 -2
- package/dist/docs/example-agents/index.html +2 -2
- package/dist/docs/example-agents/knowledge-base.html +2 -2
- package/dist/docs/example-agents/oncall.html +2 -2
- package/dist/docs/example-agents/security-reviewer.html +2 -2
- package/dist/docs/example-agents/slack-agent.html +2 -2
- package/dist/docs/example-agents/weather-agent.html +2 -2
- package/dist/docs/guides/agent-to-agent.html +2 -2
- package/dist/docs/guides/cloud-runtime.html +2 -2
- package/dist/docs/guides/convert-automation.html +2 -2
- package/dist/docs/guides/github.html +2 -2
- package/dist/docs/guides/human-in-the-loop.html +2 -2
- package/dist/docs/guides/mcp-oauth.html +2 -2
- package/dist/docs/guides/opentelemetry.html +2 -2
- package/dist/docs/guides/slack.html +2 -2
- package/dist/docs/guides/webhooks.html +2 -2
- package/dist/docs/hillclimbing.html +2 -2
- package/dist/docs/index.html +2 -2
- package/dist/docs/quickstart.html +2 -2
- package/dist/docs/reference/agent-config.html +2 -2
- package/dist/docs/reference/artifacts.html +2 -2
- package/dist/docs/reference/channels.html +2 -2
- package/dist/docs/reference/cli.html +2 -2
- package/dist/docs/reference/connections.html +2 -2
- package/dist/docs/reference/hooks.html +2 -2
- package/dist/docs/reference/http-api.html +2 -2
- package/dist/docs/reference/instructions.html +2 -2
- package/dist/docs/reference/playground.html +2 -2
- package/dist/docs/reference/project-layout.html +2 -2
- package/dist/docs/reference/prompt.html +2 -2
- package/dist/docs/reference/schedules.html +2 -2
- package/dist/docs/reference/sessions.html +2 -2
- package/dist/docs/reference/skills.html +2 -2
- package/dist/docs/reference/subagents.html +2 -2
- package/dist/docs/reference/tools.html +2 -2
- package/dist/docs/scaffolding-agents.html +2 -2
- package/dist/docs/storage.html +2 -2
- package/dist/docs/templates/agentic-owners.html +2 -2
- package/dist/docs/templates/demo.html +2 -2
- package/dist/docs/templates/pr-autofixer.html +2 -2
- package/dist/docs/templates/security-reviewer.html +2 -2
- package/dist/docs/templates/triage.html +2 -2
- package/dist/docs/troubleshooting.html +2 -2
- package/dist/files.d.ts +9 -3
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +9 -3
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/internal/continuation-channel.d.ts +29 -0
- package/dist/internal/continuation-channel.d.ts.map +1 -0
- package/dist/internal/continuation-channel.js +88 -0
- package/dist/internal/continuation-identity.d.ts +95 -0
- package/dist/internal/continuation-identity.d.ts.map +1 -0
- package/dist/internal/continuation-identity.js +445 -0
- package/dist/internal/deploy-manifest.d.ts +12 -2
- package/dist/internal/deploy-manifest.d.ts.map +1 -1
- package/dist/internal/deploy-manifest.js +15 -7
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +32 -5
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +18 -8
- package/dist/internal/session-engine.d.ts +24 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +104 -34
- package/dist/internal/session-store.d.ts.map +1 -1
- package/dist/internal/session-store.js +6 -0
- package/dist/internal/skills-sync.d.ts +25 -0
- package/dist/internal/skills-sync.d.ts.map +1 -0
- package/dist/internal/skills-sync.js +96 -0
- package/dist/playground/assets/{index-DJzcIeGt.css → index-ChETdG-_.css} +1 -1
- package/dist/playground/assets/{index-K3Sxyx0I.js → index-CkuIrhg4.js} +3 -3
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/channels/github/cursor-account.ts +20 -0
- package/src/channels/github/github-channel.ts +52 -1
- package/src/channels/github/types.ts +8 -0
- package/src/channels/origin/origin-channel.ts +48 -10
- package/src/channels/origin/types.ts +7 -0
- package/src/channels/slack/cursor-account.ts +1 -0
- package/src/channels/slack/dispatch.ts +44 -2
- package/src/channels/slack/interactive.ts +5 -0
- package/src/channels/slack/slack-channel.ts +23 -2
- package/src/channels/slack/types.ts +7 -0
- package/src/channels.ts +7 -0
- package/src/continuation.ts +49 -0
- package/src/files.ts +10 -3
- package/src/index.ts +30 -1
- package/src/internal/continuation-channel.ts +129 -0
- package/src/internal/continuation-identity.ts +597 -0
- package/src/internal/deploy-manifest.ts +36 -3
- package/src/internal/discovery.ts +40 -2
- package/src/internal/server.ts +23 -9
- package/src/internal/session-engine.ts +132 -37
- package/src/internal/session-store.ts +6 -0
- package/src/internal/skills-sync.ts +107 -0
- package/src/types.ts +55 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuation identity is a closed selector string. Omitted `continuation`
|
|
3
|
+
* on a channel constructor stays `/v1` (engine pack minting) so existing
|
|
4
|
+
* agents do not change: `githubChannel()` → `github.pr/v1`, `slackChannel()`
|
|
5
|
+
* → `slack.thread/v1`, `originChannel()` → `origin.pr/v1`, `httpChannel()`
|
|
6
|
+
* → `none/v1`.
|
|
7
|
+
*
|
|
8
|
+
* Opt the whole agent into the v2 control plane with
|
|
9
|
+
* `defineAgent({ architecture: "v2" })`. That stamps `/v2` defaults from
|
|
10
|
+
* the channel kind and declared events (`github.pr/v2`, `github.issue/v2`,
|
|
11
|
+
* `github.none/v2`, `slack.thread/v2`, …) onto every channel that omitted
|
|
12
|
+
* a selector. An explicit per-channel selector still overrides. JSONPath
|
|
13
|
+
* and per-agent format strings are rejected.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { randomBytes } from "node:crypto";
|
|
17
|
+
|
|
18
|
+
export type ConversationType = "pr" | "issue" | "thread" | "origin-pr";
|
|
19
|
+
|
|
20
|
+
export const CONTINUATION_SELECTORS = [
|
|
21
|
+
"github.pr/v1",
|
|
22
|
+
"github.pr/v2",
|
|
23
|
+
"github.issue/v1",
|
|
24
|
+
"github.issue/v2",
|
|
25
|
+
"github.none/v2",
|
|
26
|
+
"slack.thread/v1",
|
|
27
|
+
"slack.thread/v2",
|
|
28
|
+
"slack.none/v2",
|
|
29
|
+
"origin.pr/v1",
|
|
30
|
+
"origin.pr/v2",
|
|
31
|
+
"origin.none/v2",
|
|
32
|
+
"none/v1",
|
|
33
|
+
"none/v2",
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
export type ContinuationSelector = (typeof CONTINUATION_SELECTORS)[number];
|
|
37
|
+
export type ContinuationIdentity = ContinuationSelector;
|
|
38
|
+
/** Agent-root protocol version. Omit / `"v1"` keeps engine minting. */
|
|
39
|
+
export type ContinuationProtocolVersion = "v1" | "v2";
|
|
40
|
+
|
|
41
|
+
export type ParsedContinuation = {
|
|
42
|
+
selector: ContinuationSelector;
|
|
43
|
+
version: 1 | 2;
|
|
44
|
+
type: ConversationType | "none";
|
|
45
|
+
kind: "github" | "slack" | "origin" | "http";
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type ContinuationClaim = {
|
|
49
|
+
type: ConversationType;
|
|
50
|
+
resource:
|
|
51
|
+
| { owner: string; repo: string; number: number }
|
|
52
|
+
| { channelId: string; threadTs: string }
|
|
53
|
+
| { fullName: string; number: number };
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type MailboxContinuation = {
|
|
57
|
+
key: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type ContinuationBuiltin = {
|
|
61
|
+
type: ConversationType;
|
|
62
|
+
kind: "github" | "slack" | "origin";
|
|
63
|
+
events: readonly string[];
|
|
64
|
+
format: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const BUILTINS: readonly ContinuationBuiltin[] = [
|
|
68
|
+
{
|
|
69
|
+
type: "pr",
|
|
70
|
+
kind: "github",
|
|
71
|
+
events: [
|
|
72
|
+
"pull_request",
|
|
73
|
+
"issue_comment",
|
|
74
|
+
"pull_request_review",
|
|
75
|
+
"pull_request_review_comment",
|
|
76
|
+
"check_suite",
|
|
77
|
+
"check_run",
|
|
78
|
+
],
|
|
79
|
+
format: "github-pr:{fullName}#{number}",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "issue",
|
|
83
|
+
kind: "github",
|
|
84
|
+
events: ["issues", "issue_comment"],
|
|
85
|
+
format: "github-issue:{fullName}#{number}",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "thread",
|
|
89
|
+
kind: "slack",
|
|
90
|
+
events: ["message", "app_mention"],
|
|
91
|
+
format: "{channelId}:{threadTs}",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: "origin-pr",
|
|
95
|
+
kind: "origin",
|
|
96
|
+
events: [
|
|
97
|
+
"pull_request.created",
|
|
98
|
+
"pull_request.published",
|
|
99
|
+
"pull_request.head_ref.pushed",
|
|
100
|
+
"pull_request.base_ref.updated",
|
|
101
|
+
"pull_request.closed",
|
|
102
|
+
"pull_request.merged",
|
|
103
|
+
"pull_request.comment.created",
|
|
104
|
+
"pull_request.review.submitted",
|
|
105
|
+
],
|
|
106
|
+
format: "origin-pr:{fullName}#{number}",
|
|
107
|
+
},
|
|
108
|
+
] as const;
|
|
109
|
+
|
|
110
|
+
export class V2EngineError extends Error {
|
|
111
|
+
constructor(message: string) {
|
|
112
|
+
super(message);
|
|
113
|
+
this.name = "V2EngineError";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const SELECTOR_FIELDS: {
|
|
118
|
+
[K in ContinuationSelector]: Omit<ParsedContinuation, "selector">;
|
|
119
|
+
} = {
|
|
120
|
+
"github.pr/v1": { version: 1, type: "pr", kind: "github" },
|
|
121
|
+
"github.pr/v2": { version: 2, type: "pr", kind: "github" },
|
|
122
|
+
"github.issue/v1": { version: 1, type: "issue", kind: "github" },
|
|
123
|
+
"github.issue/v2": { version: 2, type: "issue", kind: "github" },
|
|
124
|
+
"github.none/v2": { version: 2, type: "none", kind: "github" },
|
|
125
|
+
"slack.thread/v1": { version: 1, type: "thread", kind: "slack" },
|
|
126
|
+
"slack.thread/v2": { version: 2, type: "thread", kind: "slack" },
|
|
127
|
+
"slack.none/v2": { version: 2, type: "none", kind: "slack" },
|
|
128
|
+
"origin.pr/v1": { version: 1, type: "origin-pr", kind: "origin" },
|
|
129
|
+
"origin.pr/v2": { version: 2, type: "origin-pr", kind: "origin" },
|
|
130
|
+
"origin.none/v2": { version: 2, type: "none", kind: "origin" },
|
|
131
|
+
"none/v1": { version: 1, type: "none", kind: "http" },
|
|
132
|
+
"none/v2": { version: 2, type: "none", kind: "http" },
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export function isContinuationSelector(
|
|
136
|
+
value: string
|
|
137
|
+
): value is ContinuationSelector {
|
|
138
|
+
return Object.hasOwn(SELECTOR_FIELDS, value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function defaultContinuationSelector(
|
|
142
|
+
kind?: string,
|
|
143
|
+
version: ContinuationProtocolVersion = "v1",
|
|
144
|
+
eventTypes?: readonly string[]
|
|
145
|
+
): ContinuationSelector {
|
|
146
|
+
if (version !== "v2") {
|
|
147
|
+
switch (kind) {
|
|
148
|
+
case "github":
|
|
149
|
+
return "github.pr/v1";
|
|
150
|
+
case "slack":
|
|
151
|
+
return "slack.thread/v1";
|
|
152
|
+
case "origin":
|
|
153
|
+
return "origin.pr/v1";
|
|
154
|
+
default:
|
|
155
|
+
return "none/v1";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
switch (kind) {
|
|
159
|
+
case "github":
|
|
160
|
+
return githubV2Default(eventTypes);
|
|
161
|
+
case "slack":
|
|
162
|
+
return "slack.thread/v2";
|
|
163
|
+
case "origin":
|
|
164
|
+
return originV2Default(eventTypes);
|
|
165
|
+
default:
|
|
166
|
+
return "none/v2";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function githubV2Default(eventTypes?: readonly string[]): ContinuationSelector {
|
|
171
|
+
const events = eventTypes ?? [];
|
|
172
|
+
const issueEvents = new Set(builtinForType("issue")?.events ?? []);
|
|
173
|
+
const prEvents = new Set(builtinForType("pr")?.events ?? []);
|
|
174
|
+
if (events.length > 0 && events.every((event) => issueEvents.has(event))) {
|
|
175
|
+
return "github.issue/v2";
|
|
176
|
+
}
|
|
177
|
+
if (events.some((event) => !prEvents.has(event))) {
|
|
178
|
+
return "github.none/v2";
|
|
179
|
+
}
|
|
180
|
+
return "github.pr/v2";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function originV2Default(eventTypes?: readonly string[]): ContinuationSelector {
|
|
184
|
+
const events = eventTypes ?? [];
|
|
185
|
+
const prEvents = new Set(builtinForType("origin-pr")?.events ?? []);
|
|
186
|
+
if (events.some((event) => !prEvents.has(event))) {
|
|
187
|
+
return "origin.none/v2";
|
|
188
|
+
}
|
|
189
|
+
return "origin.pr/v2";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function parseContinuationSelector(
|
|
193
|
+
selector: string
|
|
194
|
+
): ParsedContinuation | undefined {
|
|
195
|
+
if (!isContinuationSelector(selector)) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
return { selector, ...SELECTOR_FIELDS[selector] };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function resolveContinuation(
|
|
202
|
+
raw?: ContinuationIdentity,
|
|
203
|
+
kind?: string
|
|
204
|
+
): ParsedContinuation {
|
|
205
|
+
const selector = raw ?? defaultContinuationSelector(kind);
|
|
206
|
+
const parsed = parseContinuationSelector(selector);
|
|
207
|
+
if (parsed === undefined) {
|
|
208
|
+
throw new V2EngineError(
|
|
209
|
+
`v2: invalid continuation selector ${JSON.stringify(selector)}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return parsed;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function normalizeIdentity(
|
|
216
|
+
raw?: ContinuationIdentity,
|
|
217
|
+
kind?: string
|
|
218
|
+
): ContinuationIdentity {
|
|
219
|
+
return resolveContinuation(raw, kind).selector;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function continuationSource(
|
|
223
|
+
identity?: ContinuationIdentity,
|
|
224
|
+
kind?: string
|
|
225
|
+
): "engine" | "control-plane" {
|
|
226
|
+
return resolveContinuation(identity, kind).version === 2
|
|
227
|
+
? "control-plane"
|
|
228
|
+
: "engine";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function isV2DurableContinuationKey(key: string): boolean {
|
|
232
|
+
return key.startsWith("cont:") || key.startsWith("evt:");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function newEvtKey(): string {
|
|
236
|
+
return `evt:${randomBytes(16).toString("hex")}`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function builtinForType(
|
|
240
|
+
type: ConversationType
|
|
241
|
+
): ContinuationBuiltin | undefined {
|
|
242
|
+
return BUILTINS.find((builtin) => builtin.type === type);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function formatConversationKey(
|
|
246
|
+
type: ConversationType,
|
|
247
|
+
fields: {
|
|
248
|
+
fullName?: string;
|
|
249
|
+
number?: number;
|
|
250
|
+
channelId?: string;
|
|
251
|
+
threadTs?: string;
|
|
252
|
+
}
|
|
253
|
+
): string {
|
|
254
|
+
switch (type) {
|
|
255
|
+
case "pr":
|
|
256
|
+
return `github-pr:${fields.fullName}#${fields.number}`;
|
|
257
|
+
case "issue":
|
|
258
|
+
return `github-issue:${fields.fullName}#${fields.number}`;
|
|
259
|
+
case "origin-pr":
|
|
260
|
+
return `origin-pr:${fields.fullName}#${fields.number}`;
|
|
261
|
+
case "thread":
|
|
262
|
+
return `${fields.channelId}:${fields.threadTs}`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function mintClaim(claim: ContinuationClaim): string {
|
|
267
|
+
const resource = claim.resource;
|
|
268
|
+
if ("channelId" in resource && "threadTs" in resource) {
|
|
269
|
+
if (claim.type !== "thread") {
|
|
270
|
+
throw new V2EngineError(
|
|
271
|
+
`v2: claim type ${claim.type} does not match thread resource`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return `cont:${formatConversationKey("thread", resource)}`;
|
|
275
|
+
}
|
|
276
|
+
const fullName =
|
|
277
|
+
"fullName" in resource
|
|
278
|
+
? resource.fullName
|
|
279
|
+
: `${resource.owner}/${resource.repo}`;
|
|
280
|
+
if (claim.type === "thread") {
|
|
281
|
+
throw new V2EngineError("v2: thread claim requires channelId and threadTs");
|
|
282
|
+
}
|
|
283
|
+
return `cont:${formatConversationKey(claim.type, {
|
|
284
|
+
fullName,
|
|
285
|
+
number: resource.number,
|
|
286
|
+
})}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function handshakeContinuation(input: {
|
|
290
|
+
kind?: string;
|
|
291
|
+
eventTypes?: readonly string[];
|
|
292
|
+
identity?: ContinuationIdentity;
|
|
293
|
+
}): { ok: true } | { ok: false; error: string } {
|
|
294
|
+
if (input.identity === undefined) {
|
|
295
|
+
return { ok: true };
|
|
296
|
+
}
|
|
297
|
+
const parsed = parseContinuationSelector(input.identity);
|
|
298
|
+
if (parsed === undefined) {
|
|
299
|
+
return {
|
|
300
|
+
ok: false,
|
|
301
|
+
error: `v2: invalid continuation selector ${JSON.stringify(input.identity)}`,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (parsed.version !== 2 || parsed.type === "none") {
|
|
305
|
+
return { ok: true };
|
|
306
|
+
}
|
|
307
|
+
if (
|
|
308
|
+
input.kind !== undefined &&
|
|
309
|
+
parsed.kind !== "http" &&
|
|
310
|
+
input.kind !== parsed.kind
|
|
311
|
+
) {
|
|
312
|
+
return {
|
|
313
|
+
ok: false,
|
|
314
|
+
error: `v2: continuation ${parsed.selector} does not match channel kind ${input.kind}`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const builtin = builtinForType(parsed.type);
|
|
318
|
+
if (builtin === undefined) {
|
|
319
|
+
return {
|
|
320
|
+
ok: false,
|
|
321
|
+
error: `v2: unknown continuation type ${parsed.type}`,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
const eventTypes = input.eventTypes ?? [];
|
|
325
|
+
const allowed = new Set(builtin.events);
|
|
326
|
+
const unexpected = eventTypes.filter((event) => !allowed.has(event));
|
|
327
|
+
if (unexpected.length > 0) {
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
error: `v2: eventTypes ${unexpected.join(", ")} are not in the ${parsed.type} membership`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return { ok: true };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* CP/pack extract. Returns a `cont:` key when the payload binds the
|
|
338
|
+
* identity type; undefined on membership miss or missing number (caller
|
|
339
|
+
* stamps `evt:` and does not join the conversation index).
|
|
340
|
+
*/
|
|
341
|
+
export function extractContinuation(
|
|
342
|
+
type: ConversationType,
|
|
343
|
+
eventName: string,
|
|
344
|
+
payload: unknown
|
|
345
|
+
): string | undefined {
|
|
346
|
+
const builtin = builtinForType(type);
|
|
347
|
+
if (builtin === undefined) {
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
if (!builtin.events.includes(eventName)) {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
switch (type) {
|
|
354
|
+
case "pr":
|
|
355
|
+
case "issue":
|
|
356
|
+
return extractGitHub(type, eventName, payload);
|
|
357
|
+
case "thread":
|
|
358
|
+
return extractSlack(payload);
|
|
359
|
+
case "origin-pr":
|
|
360
|
+
return extractOrigin(payload);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function stampMailboxContinuation(
|
|
365
|
+
identity: ContinuationIdentity,
|
|
366
|
+
eventName: string,
|
|
367
|
+
payload: unknown,
|
|
368
|
+
kind?: string
|
|
369
|
+
): MailboxContinuation {
|
|
370
|
+
const parsed = resolveContinuation(identity, kind);
|
|
371
|
+
if (parsed.version !== 2) {
|
|
372
|
+
throw new V2EngineError("v2: refusing to stamp a mailbox key for v1");
|
|
373
|
+
}
|
|
374
|
+
if (parsed.type === "none") {
|
|
375
|
+
return { key: newEvtKey() };
|
|
376
|
+
}
|
|
377
|
+
const extracted = extractContinuation(parsed.type, eventName, payload);
|
|
378
|
+
if (extracted === undefined) {
|
|
379
|
+
return { key: newEvtKey() };
|
|
380
|
+
}
|
|
381
|
+
return { key: extracted };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export function resolveV2Key(
|
|
385
|
+
identity: ContinuationIdentity,
|
|
386
|
+
options: {
|
|
387
|
+
claim?: ContinuationClaim;
|
|
388
|
+
continuationToken?: string;
|
|
389
|
+
mailboxContinuation?: MailboxContinuation;
|
|
390
|
+
},
|
|
391
|
+
kind?: string
|
|
392
|
+
): string {
|
|
393
|
+
const parsed = resolveContinuation(identity, kind);
|
|
394
|
+
if (parsed.version !== 2) {
|
|
395
|
+
throw new V2EngineError("v2: resolveV2Key requires a /v2 selector");
|
|
396
|
+
}
|
|
397
|
+
if (options.continuationToken !== undefined) {
|
|
398
|
+
throw new V2EngineError(
|
|
399
|
+
"v2: do not pass continuationToken; use mailboxContinuation or claim"
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
if (options.claim !== undefined) {
|
|
403
|
+
if (parsed.type === "none") {
|
|
404
|
+
throw new V2EngineError("v2: type none cannot take a claim");
|
|
405
|
+
}
|
|
406
|
+
if (options.claim.type !== parsed.type) {
|
|
407
|
+
throw new V2EngineError(
|
|
408
|
+
`v2: claim type ${options.claim.type} does not match identity type ${parsed.type}`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
return mintClaim(options.claim);
|
|
412
|
+
}
|
|
413
|
+
if (options.mailboxContinuation !== undefined) {
|
|
414
|
+
return options.mailboxContinuation.key;
|
|
415
|
+
}
|
|
416
|
+
if (parsed.type === "none") {
|
|
417
|
+
return newEvtKey();
|
|
418
|
+
}
|
|
419
|
+
throw new V2EngineError(
|
|
420
|
+
"v2: no CP key on mailbox and no claim; refusing to mint in the engine"
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function bindMailboxContinuationSend<
|
|
425
|
+
TSession,
|
|
426
|
+
TOptions extends { mailboxContinuation?: MailboxContinuation },
|
|
427
|
+
>(
|
|
428
|
+
send: (message: string, options?: TOptions) => Promise<TSession>,
|
|
429
|
+
mailbox: MailboxContinuation | undefined
|
|
430
|
+
): (message: string, options?: TOptions) => Promise<TSession> {
|
|
431
|
+
if (mailbox === undefined) {
|
|
432
|
+
return send;
|
|
433
|
+
}
|
|
434
|
+
return (message, options) =>
|
|
435
|
+
send(message, {
|
|
436
|
+
...(options as TOptions),
|
|
437
|
+
mailboxContinuation: options?.mailboxContinuation ?? mailbox,
|
|
438
|
+
} as TOptions);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function extractGitHub(
|
|
442
|
+
type: "pr" | "issue",
|
|
443
|
+
eventName: string,
|
|
444
|
+
payload: unknown
|
|
445
|
+
): string | undefined {
|
|
446
|
+
const record = asRecord(payload);
|
|
447
|
+
if (record === undefined) {
|
|
448
|
+
return undefined;
|
|
449
|
+
}
|
|
450
|
+
const fullName = githubFullName(record);
|
|
451
|
+
if (fullName === undefined) {
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
const bound = githubBoundConversation(eventName, record);
|
|
455
|
+
if (bound === undefined || bound.type !== type) {
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
return `cont:${formatConversationKey(type, {
|
|
459
|
+
fullName,
|
|
460
|
+
number: bound.number,
|
|
461
|
+
})}`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function githubBoundConversation(
|
|
465
|
+
eventName: string,
|
|
466
|
+
payload: Record<string, unknown>
|
|
467
|
+
): { type: "pr" | "issue"; number: number } | undefined {
|
|
468
|
+
switch (eventName) {
|
|
469
|
+
case "pull_request":
|
|
470
|
+
case "pull_request_review":
|
|
471
|
+
case "pull_request_review_comment":
|
|
472
|
+
case "pull_request_review_thread": {
|
|
473
|
+
const number = positiveInt(asRecord(payload.pull_request)?.number);
|
|
474
|
+
return number === undefined ? undefined : { type: "pr", number };
|
|
475
|
+
}
|
|
476
|
+
case "issues": {
|
|
477
|
+
const number = positiveInt(asRecord(payload.issue)?.number);
|
|
478
|
+
return number === undefined ? undefined : { type: "issue", number };
|
|
479
|
+
}
|
|
480
|
+
case "issue_comment": {
|
|
481
|
+
const issue = asRecord(payload.issue);
|
|
482
|
+
const number = positiveInt(issue?.number);
|
|
483
|
+
if (number === undefined) {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
return issue?.pull_request !== undefined && issue.pull_request !== null
|
|
487
|
+
? { type: "pr", number }
|
|
488
|
+
: { type: "issue", number };
|
|
489
|
+
}
|
|
490
|
+
case "check_suite": {
|
|
491
|
+
const number = firstPullRequestNumber(asRecord(payload.check_suite));
|
|
492
|
+
return number === undefined ? undefined : { type: "pr", number };
|
|
493
|
+
}
|
|
494
|
+
case "check_run": {
|
|
495
|
+
const checkRun = asRecord(payload.check_run);
|
|
496
|
+
const number =
|
|
497
|
+
firstPullRequestNumber(checkRun) ??
|
|
498
|
+
firstPullRequestNumber(asRecord(checkRun?.check_suite));
|
|
499
|
+
return number === undefined ? undefined : { type: "pr", number };
|
|
500
|
+
}
|
|
501
|
+
default:
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function extractSlack(payload: unknown): string | undefined {
|
|
507
|
+
const record = asRecord(payload);
|
|
508
|
+
if (record === undefined) {
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
const channelId =
|
|
512
|
+
nonEmpty(record.channelId) ??
|
|
513
|
+
nonEmpty(record.channel_id) ??
|
|
514
|
+
nonEmpty(record.channel);
|
|
515
|
+
const threadTs =
|
|
516
|
+
nonEmpty(record.threadTs) ??
|
|
517
|
+
nonEmpty(record.thread_ts) ??
|
|
518
|
+
nonEmpty(record.ts);
|
|
519
|
+
if (channelId === undefined || threadTs === undefined) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
return `cont:${formatConversationKey("thread", { channelId, threadTs })}`;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function extractOrigin(payload: unknown): string | undefined {
|
|
526
|
+
const record = asRecord(payload);
|
|
527
|
+
if (record === undefined) {
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
const inner = asRecord(record.pullRequest) ?? asRecord(record.pull_request);
|
|
531
|
+
const number = positiveInt(inner?.number);
|
|
532
|
+
const repository =
|
|
533
|
+
asRecord(record.repository) ??
|
|
534
|
+
asRecord(asRecord(asRecord(record.event)?.payload)?.repository);
|
|
535
|
+
const fullName =
|
|
536
|
+
nonEmpty(asRecord(repository)?.fullName) ??
|
|
537
|
+
nonEmpty(asRecord(repository)?.full_name) ??
|
|
538
|
+
originFullName(repository);
|
|
539
|
+
if (number === undefined || fullName === undefined) {
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
return `cont:${formatConversationKey("origin-pr", { fullName, number })}`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function githubFullName(payload: Record<string, unknown>): string | undefined {
|
|
546
|
+
const repository = asRecord(payload.repository);
|
|
547
|
+
return nonEmpty(repository?.full_name) ?? originFullName(repository);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function originFullName(
|
|
551
|
+
repository: Record<string, unknown> | undefined
|
|
552
|
+
): string | undefined {
|
|
553
|
+
if (repository === undefined) {
|
|
554
|
+
return undefined;
|
|
555
|
+
}
|
|
556
|
+
const owner =
|
|
557
|
+
nonEmpty(repository.owner) ??
|
|
558
|
+
nonEmpty(asRecord(repository.owner)?.login) ??
|
|
559
|
+
nonEmpty(asRecord(repository.owner)?.name);
|
|
560
|
+
const name = nonEmpty(repository.name);
|
|
561
|
+
if (owner === undefined || name === undefined) {
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
return `${owner}/${name}`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function firstPullRequestNumber(
|
|
568
|
+
record: Record<string, unknown> | undefined
|
|
569
|
+
): number | undefined {
|
|
570
|
+
const pullRequests = record?.pull_requests;
|
|
571
|
+
if (!Array.isArray(pullRequests) || pullRequests.length === 0) {
|
|
572
|
+
return undefined;
|
|
573
|
+
}
|
|
574
|
+
return positiveInt(asRecord(pullRequests[0])?.number);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
578
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
579
|
+
? (value as Record<string, unknown>)
|
|
580
|
+
: undefined;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function nonEmpty(value: unknown): string | undefined {
|
|
584
|
+
return typeof value === "string" && value.trim() !== ""
|
|
585
|
+
? value.trim()
|
|
586
|
+
: undefined;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function positiveInt(value: unknown): number | undefined {
|
|
590
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
591
|
+
return value;
|
|
592
|
+
}
|
|
593
|
+
if (typeof value === "string" && /^[1-9]\d*$/.test(value)) {
|
|
594
|
+
return Number(value);
|
|
595
|
+
}
|
|
596
|
+
return undefined;
|
|
597
|
+
}
|
|
@@ -31,8 +31,15 @@
|
|
|
31
31
|
|
|
32
32
|
import { z } from "zod";
|
|
33
33
|
import { isPublicEndpointPolicy } from "../channels.js";
|
|
34
|
+
import type { ContinuationIdentity } from "../continuation.js";
|
|
34
35
|
import { isCursorHostedStorage } from "../storage-backends/cursor-hosted.js";
|
|
35
36
|
import type { AgentProject, AgentProjectInfo } from "../types.js";
|
|
37
|
+
import {
|
|
38
|
+
applyAgentContinuationDefaults,
|
|
39
|
+
channelContinuationIdentity,
|
|
40
|
+
handshakeChannelContinuation,
|
|
41
|
+
} from "./continuation-channel.js";
|
|
42
|
+
import { CONTINUATION_SELECTORS } from "./continuation-identity.js";
|
|
36
43
|
import { projectInfo } from "./discovery.js";
|
|
37
44
|
import { packageVersion } from "./distribution.js";
|
|
38
45
|
|
|
@@ -43,6 +50,8 @@ export const DEPLOY_MANIFEST_VERSION: 1 = 1;
|
|
|
43
50
|
* module-private (the package builds with isolated declarations); consumers
|
|
44
51
|
* use {@link parseAgentDeployManifest} and the explicit interfaces below.
|
|
45
52
|
*/
|
|
53
|
+
const continuationIdentitySchema = z.enum(CONTINUATION_SELECTORS);
|
|
54
|
+
|
|
46
55
|
const agentDeployManifestV1Schema = z.object({
|
|
47
56
|
manifestVersion: z.literal(DEPLOY_MANIFEST_VERSION),
|
|
48
57
|
generatedAtMs: z.number().int().nonnegative(),
|
|
@@ -51,12 +60,14 @@ const agentDeployManifestV1Schema = z.object({
|
|
|
51
60
|
egressDomains: z.array(z.string().min(1)).max(64),
|
|
52
61
|
publicRoutePaths: z.array(z.string().min(1)).max(64),
|
|
53
62
|
frameworkStorage: z.enum(["cursor-hosted", "other", "none"]).optional(),
|
|
63
|
+
architecture: z.enum(["v1", "v2"]).optional(),
|
|
54
64
|
channels: z
|
|
55
65
|
.array(
|
|
56
66
|
z.object({
|
|
57
67
|
id: z.string().min(1).max(128),
|
|
58
68
|
kind: z.string().min(1).max(32).optional(),
|
|
59
69
|
eventTypes: z.array(z.string().min(1).max(64)).max(64).optional(),
|
|
70
|
+
continuation: continuationIdentitySchema,
|
|
60
71
|
})
|
|
61
72
|
)
|
|
62
73
|
.max(64),
|
|
@@ -70,13 +81,26 @@ export interface AgentDeployManifestDeclarations {
|
|
|
70
81
|
publicRoutePaths: string[];
|
|
71
82
|
/** Where this runtime mirrors framework-owned sessions and event chunks. */
|
|
72
83
|
frameworkStorage?: "cursor-hosted" | "other" | "none";
|
|
84
|
+
/**
|
|
85
|
+
* Hosted architecture this release deploys onto. `"v2"` selects the v2
|
|
86
|
+
* control plane; omitted channel constructors inherit kind `/v2`
|
|
87
|
+
* continuation defaults. Omit or `"v1"` keeps engine minting.
|
|
88
|
+
*/
|
|
89
|
+
architecture?: "v1" | "v2";
|
|
73
90
|
/**
|
|
74
91
|
* Mounted channels with their kinds and per-channel event capability.
|
|
75
92
|
* Scope keys are installation config and are never extracted here.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
93
|
+
* Omitted channel `continuation` is `/v1` (engine minting) unless the
|
|
94
|
+
* agent set `architecture: "v2"`, which stamps kind `/v2` defaults
|
|
95
|
+
* (`github.pr/v2`, `slack.thread/v2`, `origin.pr/v2`). Per-channel
|
|
96
|
+
* selectors still override type.
|
|
78
97
|
*/
|
|
79
|
-
channels: Array<{
|
|
98
|
+
channels: Array<{
|
|
99
|
+
id: string;
|
|
100
|
+
kind?: string;
|
|
101
|
+
eventTypes?: string[];
|
|
102
|
+
continuation: ContinuationIdentity;
|
|
103
|
+
}>;
|
|
80
104
|
}
|
|
81
105
|
|
|
82
106
|
export interface AgentDeployManifestV1 {
|
|
@@ -101,7 +125,12 @@ export function buildAgentDeployManifest(
|
|
|
101
125
|
project: AgentProject,
|
|
102
126
|
options?: { nowMs?: number; cliVersion?: string }
|
|
103
127
|
): AgentDeployManifestV1 {
|
|
128
|
+
applyAgentContinuationDefaults(project);
|
|
104
129
|
const channels = project.channels.map((channel) => {
|
|
130
|
+
const handshake = handshakeChannelContinuation(channel);
|
|
131
|
+
if (!handshake.ok) {
|
|
132
|
+
throw new Error(handshake.error);
|
|
133
|
+
}
|
|
105
134
|
const kind = channel.definition.meta?.kind;
|
|
106
135
|
const githubEvents = channel.definition.meta?.githubWebhookEvents;
|
|
107
136
|
const originEvents = channel.definition.meta?.originWebhookEvents;
|
|
@@ -115,6 +144,7 @@ export function buildAgentDeployManifest(
|
|
|
115
144
|
id: channel.id,
|
|
116
145
|
kind,
|
|
117
146
|
eventTypes,
|
|
147
|
+
continuation: channelContinuationIdentity(channel.definition),
|
|
118
148
|
};
|
|
119
149
|
});
|
|
120
150
|
|
|
@@ -139,6 +169,9 @@ export function buildAgentDeployManifest(
|
|
|
139
169
|
: isCursorHostedStorage(project.storage)
|
|
140
170
|
? "cursor-hosted"
|
|
141
171
|
: "other",
|
|
172
|
+
...(project.agent.architecture === undefined
|
|
173
|
+
? {}
|
|
174
|
+
: { architecture: project.agent.architecture }),
|
|
142
175
|
channels,
|
|
143
176
|
},
|
|
144
177
|
info: projectInfo(project),
|