@cursor/july 0.1.79 → 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.
Files changed (93) hide show
  1. package/AGENTS.md +0 -12
  2. package/dist/channels/github/api.d.ts.map +1 -1
  3. package/dist/channels/github/api.js +8 -1
  4. package/dist/channels/github/cursor-account.d.ts +28 -0
  5. package/dist/channels/github/cursor-account.d.ts.map +1 -1
  6. package/dist/channels/github/cursor-account.js +60 -0
  7. package/dist/channels/github/github-channel.d.ts.map +1 -1
  8. package/dist/channels/github/github-channel.js +29 -1
  9. package/dist/channels/github/types.d.ts +8 -0
  10. package/dist/channels/github/types.d.ts.map +1 -1
  11. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  12. package/dist/channels/origin/origin-channel.js +32 -10
  13. package/dist/channels/origin/types.d.ts +7 -0
  14. package/dist/channels/origin/types.d.ts.map +1 -1
  15. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  16. package/dist/channels/slack/cursor-account.js +2 -2
  17. package/dist/channels/slack/dispatch.d.ts +3 -0
  18. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  19. package/dist/channels/slack/dispatch.js +18 -18
  20. package/dist/channels/slack/interactive.d.ts +2 -0
  21. package/dist/channels/slack/interactive.d.ts.map +1 -1
  22. package/dist/channels/slack/interactive.js +4 -16
  23. package/dist/channels/slack/slack-channel.d.ts +1 -1
  24. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  25. package/dist/channels/slack/slack-channel.js +21 -10
  26. package/dist/channels/slack/types.d.ts +7 -0
  27. package/dist/channels/slack/types.d.ts.map +1 -1
  28. package/dist/channels.d.ts.map +1 -1
  29. package/dist/channels.js +4 -0
  30. package/dist/continuation.d.ts +20 -0
  31. package/dist/continuation.d.ts.map +1 -0
  32. package/dist/continuation.js +18 -0
  33. package/dist/files.d.ts +9 -3
  34. package/dist/files.d.ts.map +1 -1
  35. package/dist/files.js +9 -3
  36. package/dist/index.d.ts +5 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +4 -1
  39. package/dist/internal/continuation-channel.d.ts +29 -0
  40. package/dist/internal/continuation-channel.d.ts.map +1 -0
  41. package/dist/internal/continuation-channel.js +88 -0
  42. package/dist/internal/continuation-identity.d.ts +95 -0
  43. package/dist/internal/continuation-identity.d.ts.map +1 -0
  44. package/dist/internal/continuation-identity.js +445 -0
  45. package/dist/internal/deploy-manifest.d.ts +12 -2
  46. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  47. package/dist/internal/deploy-manifest.js +15 -7
  48. package/dist/internal/discovery.d.ts.map +1 -1
  49. package/dist/internal/discovery.js +32 -5
  50. package/dist/internal/review-comments.d.ts +37 -7
  51. package/dist/internal/review-comments.d.ts.map +1 -1
  52. package/dist/internal/review-comments.js +209 -10
  53. package/dist/internal/server.d.ts.map +1 -1
  54. package/dist/internal/server.js +117 -55
  55. package/dist/internal/session-engine.d.ts +24 -0
  56. package/dist/internal/session-engine.d.ts.map +1 -1
  57. package/dist/internal/session-engine.js +104 -34
  58. package/dist/internal/session-store.d.ts.map +1 -1
  59. package/dist/internal/session-store.js +6 -0
  60. package/dist/internal/skills-sync.d.ts +25 -0
  61. package/dist/internal/skills-sync.d.ts.map +1 -0
  62. package/dist/internal/skills-sync.js +96 -0
  63. package/dist/playground/assets/{index-DJzcIeGt.css → index-ChETdG-_.css} +1 -1
  64. package/dist/playground/assets/{index-K3Sxyx0I.js → index-CkuIrhg4.js} +3 -3
  65. package/dist/playground/index.html +2 -2
  66. package/dist/types.d.ts +44 -0
  67. package/dist/types.d.ts.map +1 -1
  68. package/package.json +1 -1
  69. package/src/channels/github/api.ts +8 -0
  70. package/src/channels/github/cursor-account.ts +76 -0
  71. package/src/channels/github/github-channel.ts +52 -1
  72. package/src/channels/github/types.ts +8 -0
  73. package/src/channels/origin/origin-channel.ts +48 -10
  74. package/src/channels/origin/types.ts +7 -0
  75. package/src/channels/slack/cursor-account.ts +1 -0
  76. package/src/channels/slack/dispatch.ts +44 -2
  77. package/src/channels/slack/interactive.ts +5 -0
  78. package/src/channels/slack/slack-channel.ts +23 -2
  79. package/src/channels/slack/types.ts +7 -0
  80. package/src/channels.ts +7 -0
  81. package/src/continuation.ts +49 -0
  82. package/src/files.ts +10 -3
  83. package/src/index.ts +30 -1
  84. package/src/internal/continuation-channel.ts +129 -0
  85. package/src/internal/continuation-identity.ts +597 -0
  86. package/src/internal/deploy-manifest.ts +36 -3
  87. package/src/internal/discovery.ts +40 -2
  88. package/src/internal/review-comments.ts +257 -16
  89. package/src/internal/server.ts +146 -68
  90. package/src/internal/session-engine.ts +132 -37
  91. package/src/internal/session-store.ts +6 -0
  92. package/src/internal/skills-sync.ts +107 -0
  93. package/src/types.ts +55 -0
@@ -0,0 +1,88 @@
1
+ import { getGitHubChannelOptions } from "../channels/github/github-channel.js";
2
+ import { getOriginChannelOptions } from "../channels/origin/origin-channel.js";
3
+ import { getSlackChannelOptions } from "../channels/slack/slack-channel.js";
4
+ import { defaultContinuationSelector, handshakeContinuation, normalizeIdentity, } from "./continuation-identity.js";
5
+ export function channelContinuationIdentity(definition) {
6
+ if (definition === undefined) {
7
+ return defaultContinuationSelector();
8
+ }
9
+ const github = getGitHubChannelOptions(definition);
10
+ if (github !== undefined) {
11
+ return normalizeIdentity(github.continuation, "github");
12
+ }
13
+ const slack = getSlackChannelOptions(definition);
14
+ if (slack !== undefined) {
15
+ return normalizeIdentity(slack.continuation, "slack");
16
+ }
17
+ const origin = getOriginChannelOptions(definition);
18
+ if (origin !== undefined) {
19
+ return normalizeIdentity(origin.continuation, "origin");
20
+ }
21
+ return defaultContinuationSelector();
22
+ }
23
+ export function httpContinuationIdentity(httpChannel) {
24
+ return normalizeIdentity(httpChannel === null || httpChannel === void 0 ? void 0 : httpChannel.continuation, "http");
25
+ }
26
+ /**
27
+ * Stamp `/v2` continuation defaults onto channel constructors that omitted
28
+ * `continuation` when the agent deploys onto v2 architecture. GitHub/Origin
29
+ * type (`pr` / `issue` / `none`) follows declared webhook events. Explicit
30
+ * per-channel selectors are left alone. Idempotent.
31
+ */
32
+ export function applyAgentContinuationDefaults(project) {
33
+ if (project.agent.architecture !== "v2") {
34
+ return;
35
+ }
36
+ for (const channel of project.channels) {
37
+ stampOmittedChannelContinuation(channel.definition, "v2");
38
+ }
39
+ if (project.httpChannel.continuation === undefined) {
40
+ project.httpChannel.continuation = defaultContinuationSelector("http", "v2");
41
+ }
42
+ }
43
+ function stampOmittedChannelContinuation(definition, version) {
44
+ const events = declaredChannelEventTypes(definition);
45
+ const github = getGitHubChannelOptions(definition);
46
+ if (github !== undefined) {
47
+ if (github.continuation === undefined) {
48
+ github.continuation = defaultContinuationSelector("github", version, events);
49
+ }
50
+ return;
51
+ }
52
+ const slack = getSlackChannelOptions(definition);
53
+ if (slack !== undefined) {
54
+ if (slack.continuation === undefined) {
55
+ slack.continuation = defaultContinuationSelector("slack", version);
56
+ }
57
+ return;
58
+ }
59
+ const origin = getOriginChannelOptions(definition);
60
+ if (origin !== undefined) {
61
+ if (origin.continuation === undefined) {
62
+ origin.continuation = defaultContinuationSelector("origin", version, events);
63
+ }
64
+ }
65
+ }
66
+ function declaredChannelEventTypes(definition) {
67
+ var _a;
68
+ const meta = definition.meta;
69
+ return (_a = meta === null || meta === void 0 ? void 0 : meta.githubWebhookEvents) !== null && _a !== void 0 ? _a : meta === null || meta === void 0 ? void 0 : meta.originWebhookEvents;
70
+ }
71
+ export function handshakeChannelContinuation(input) {
72
+ var _a, _b, _c;
73
+ var _d, _e;
74
+ const kind = (_a = input.definition.meta) === null || _a === void 0 ? void 0 : _a.kind;
75
+ const eventTypes = [
76
+ ...((_d = (_b = input.definition.meta) === null || _b === void 0 ? void 0 : _b.githubWebhookEvents) !== null && _d !== void 0 ? _d : []),
77
+ ...((_e = (_c = input.definition.meta) === null || _c === void 0 ? void 0 : _c.originWebhookEvents) !== null && _e !== void 0 ? _e : []),
78
+ ];
79
+ const result = handshakeContinuation({
80
+ kind,
81
+ eventTypes,
82
+ identity: channelContinuationIdentity(input.definition),
83
+ });
84
+ if (!result.ok) {
85
+ return { ok: false, error: `channel ${input.id}: ${result.error}` };
86
+ }
87
+ return result;
88
+ }
@@ -0,0 +1,95 @@
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
+ export type ConversationType = "pr" | "issue" | "thread" | "origin-pr";
16
+ export declare const CONTINUATION_SELECTORS: readonly ["github.pr/v1", "github.pr/v2", "github.issue/v1", "github.issue/v2", "github.none/v2", "slack.thread/v1", "slack.thread/v2", "slack.none/v2", "origin.pr/v1", "origin.pr/v2", "origin.none/v2", "none/v1", "none/v2"];
17
+ export type ContinuationSelector = (typeof CONTINUATION_SELECTORS)[number];
18
+ export type ContinuationIdentity = ContinuationSelector;
19
+ /** Agent-root protocol version. Omit / `"v1"` keeps engine minting. */
20
+ export type ContinuationProtocolVersion = "v1" | "v2";
21
+ export type ParsedContinuation = {
22
+ selector: ContinuationSelector;
23
+ version: 1 | 2;
24
+ type: ConversationType | "none";
25
+ kind: "github" | "slack" | "origin" | "http";
26
+ };
27
+ export type ContinuationClaim = {
28
+ type: ConversationType;
29
+ resource: {
30
+ owner: string;
31
+ repo: string;
32
+ number: number;
33
+ } | {
34
+ channelId: string;
35
+ threadTs: string;
36
+ } | {
37
+ fullName: string;
38
+ number: number;
39
+ };
40
+ };
41
+ export type MailboxContinuation = {
42
+ key: string;
43
+ };
44
+ export type ContinuationBuiltin = {
45
+ type: ConversationType;
46
+ kind: "github" | "slack" | "origin";
47
+ events: readonly string[];
48
+ format: string;
49
+ };
50
+ export declare const BUILTINS: readonly ContinuationBuiltin[];
51
+ export declare class V2EngineError extends Error {
52
+ constructor(message: string);
53
+ }
54
+ export declare function isContinuationSelector(value: string): value is ContinuationSelector;
55
+ export declare function defaultContinuationSelector(kind?: string, version?: ContinuationProtocolVersion, eventTypes?: readonly string[]): ContinuationSelector;
56
+ export declare function parseContinuationSelector(selector: string): ParsedContinuation | undefined;
57
+ export declare function resolveContinuation(raw?: ContinuationIdentity, kind?: string): ParsedContinuation;
58
+ export declare function normalizeIdentity(raw?: ContinuationIdentity, kind?: string): ContinuationIdentity;
59
+ export declare function continuationSource(identity?: ContinuationIdentity, kind?: string): "engine" | "control-plane";
60
+ export declare function isV2DurableContinuationKey(key: string): boolean;
61
+ export declare function newEvtKey(): string;
62
+ export declare function builtinForType(type: ConversationType): ContinuationBuiltin | undefined;
63
+ export declare function formatConversationKey(type: ConversationType, fields: {
64
+ fullName?: string;
65
+ number?: number;
66
+ channelId?: string;
67
+ threadTs?: string;
68
+ }): string;
69
+ export declare function mintClaim(claim: ContinuationClaim): string;
70
+ export declare function handshakeContinuation(input: {
71
+ kind?: string;
72
+ eventTypes?: readonly string[];
73
+ identity?: ContinuationIdentity;
74
+ }): {
75
+ ok: true;
76
+ } | {
77
+ ok: false;
78
+ error: string;
79
+ };
80
+ /**
81
+ * CP/pack extract. Returns a `cont:` key when the payload binds the
82
+ * identity type; undefined on membership miss or missing number (caller
83
+ * stamps `evt:` and does not join the conversation index).
84
+ */
85
+ export declare function extractContinuation(type: ConversationType, eventName: string, payload: unknown): string | undefined;
86
+ export declare function stampMailboxContinuation(identity: ContinuationIdentity, eventName: string, payload: unknown, kind?: string): MailboxContinuation;
87
+ export declare function resolveV2Key(identity: ContinuationIdentity, options: {
88
+ claim?: ContinuationClaim;
89
+ continuationToken?: string;
90
+ mailboxContinuation?: MailboxContinuation;
91
+ }, kind?: string): string;
92
+ export declare function bindMailboxContinuationSend<TSession, TOptions extends {
93
+ mailboxContinuation?: MailboxContinuation;
94
+ }>(send: (message: string, options?: TOptions) => Promise<TSession>, mailbox: MailboxContinuation | undefined): (message: string, options?: TOptions) => Promise<TSession>;
95
+ //# sourceMappingURL=continuation-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"continuation-identity.d.ts","sourceRoot":"","sources":["../../src/internal/continuation-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,445 @@
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
+ import { randomBytes } from "node:crypto";
16
+ export const CONTINUATION_SELECTORS = [
17
+ "github.pr/v1",
18
+ "github.pr/v2",
19
+ "github.issue/v1",
20
+ "github.issue/v2",
21
+ "github.none/v2",
22
+ "slack.thread/v1",
23
+ "slack.thread/v2",
24
+ "slack.none/v2",
25
+ "origin.pr/v1",
26
+ "origin.pr/v2",
27
+ "origin.none/v2",
28
+ "none/v1",
29
+ "none/v2",
30
+ ];
31
+ export const BUILTINS = [
32
+ {
33
+ type: "pr",
34
+ kind: "github",
35
+ events: [
36
+ "pull_request",
37
+ "issue_comment",
38
+ "pull_request_review",
39
+ "pull_request_review_comment",
40
+ "check_suite",
41
+ "check_run",
42
+ ],
43
+ format: "github-pr:{fullName}#{number}",
44
+ },
45
+ {
46
+ type: "issue",
47
+ kind: "github",
48
+ events: ["issues", "issue_comment"],
49
+ format: "github-issue:{fullName}#{number}",
50
+ },
51
+ {
52
+ type: "thread",
53
+ kind: "slack",
54
+ events: ["message", "app_mention"],
55
+ format: "{channelId}:{threadTs}",
56
+ },
57
+ {
58
+ type: "origin-pr",
59
+ kind: "origin",
60
+ events: [
61
+ "pull_request.created",
62
+ "pull_request.published",
63
+ "pull_request.head_ref.pushed",
64
+ "pull_request.base_ref.updated",
65
+ "pull_request.closed",
66
+ "pull_request.merged",
67
+ "pull_request.comment.created",
68
+ "pull_request.review.submitted",
69
+ ],
70
+ format: "origin-pr:{fullName}#{number}",
71
+ },
72
+ ];
73
+ export class V2EngineError extends Error {
74
+ constructor(message) {
75
+ super(message);
76
+ this.name = "V2EngineError";
77
+ }
78
+ }
79
+ const SELECTOR_FIELDS = {
80
+ "github.pr/v1": { version: 1, type: "pr", kind: "github" },
81
+ "github.pr/v2": { version: 2, type: "pr", kind: "github" },
82
+ "github.issue/v1": { version: 1, type: "issue", kind: "github" },
83
+ "github.issue/v2": { version: 2, type: "issue", kind: "github" },
84
+ "github.none/v2": { version: 2, type: "none", kind: "github" },
85
+ "slack.thread/v1": { version: 1, type: "thread", kind: "slack" },
86
+ "slack.thread/v2": { version: 2, type: "thread", kind: "slack" },
87
+ "slack.none/v2": { version: 2, type: "none", kind: "slack" },
88
+ "origin.pr/v1": { version: 1, type: "origin-pr", kind: "origin" },
89
+ "origin.pr/v2": { version: 2, type: "origin-pr", kind: "origin" },
90
+ "origin.none/v2": { version: 2, type: "none", kind: "origin" },
91
+ "none/v1": { version: 1, type: "none", kind: "http" },
92
+ "none/v2": { version: 2, type: "none", kind: "http" },
93
+ };
94
+ export function isContinuationSelector(value) {
95
+ return Object.hasOwn(SELECTOR_FIELDS, value);
96
+ }
97
+ export function defaultContinuationSelector(kind, version = "v1", eventTypes) {
98
+ if (version !== "v2") {
99
+ switch (kind) {
100
+ case "github":
101
+ return "github.pr/v1";
102
+ case "slack":
103
+ return "slack.thread/v1";
104
+ case "origin":
105
+ return "origin.pr/v1";
106
+ default:
107
+ return "none/v1";
108
+ }
109
+ }
110
+ switch (kind) {
111
+ case "github":
112
+ return githubV2Default(eventTypes);
113
+ case "slack":
114
+ return "slack.thread/v2";
115
+ case "origin":
116
+ return originV2Default(eventTypes);
117
+ default:
118
+ return "none/v2";
119
+ }
120
+ }
121
+ function githubV2Default(eventTypes) {
122
+ var _a, _b;
123
+ var _c, _d;
124
+ const events = eventTypes !== null && eventTypes !== void 0 ? eventTypes : [];
125
+ const issueEvents = new Set((_c = (_a = builtinForType("issue")) === null || _a === void 0 ? void 0 : _a.events) !== null && _c !== void 0 ? _c : []);
126
+ const prEvents = new Set((_d = (_b = builtinForType("pr")) === null || _b === void 0 ? void 0 : _b.events) !== null && _d !== void 0 ? _d : []);
127
+ if (events.length > 0 && events.every((event) => issueEvents.has(event))) {
128
+ return "github.issue/v2";
129
+ }
130
+ if (events.some((event) => !prEvents.has(event))) {
131
+ return "github.none/v2";
132
+ }
133
+ return "github.pr/v2";
134
+ }
135
+ function originV2Default(eventTypes) {
136
+ var _a;
137
+ var _b;
138
+ const events = eventTypes !== null && eventTypes !== void 0 ? eventTypes : [];
139
+ const prEvents = new Set((_b = (_a = builtinForType("origin-pr")) === null || _a === void 0 ? void 0 : _a.events) !== null && _b !== void 0 ? _b : []);
140
+ if (events.some((event) => !prEvents.has(event))) {
141
+ return "origin.none/v2";
142
+ }
143
+ return "origin.pr/v2";
144
+ }
145
+ export function parseContinuationSelector(selector) {
146
+ if (!isContinuationSelector(selector)) {
147
+ return undefined;
148
+ }
149
+ return Object.assign({ selector }, SELECTOR_FIELDS[selector]);
150
+ }
151
+ export function resolveContinuation(raw, kind) {
152
+ const selector = raw !== null && raw !== void 0 ? raw : defaultContinuationSelector(kind);
153
+ const parsed = parseContinuationSelector(selector);
154
+ if (parsed === undefined) {
155
+ throw new V2EngineError(`v2: invalid continuation selector ${JSON.stringify(selector)}`);
156
+ }
157
+ return parsed;
158
+ }
159
+ export function normalizeIdentity(raw, kind) {
160
+ return resolveContinuation(raw, kind).selector;
161
+ }
162
+ export function continuationSource(identity, kind) {
163
+ return resolveContinuation(identity, kind).version === 2
164
+ ? "control-plane"
165
+ : "engine";
166
+ }
167
+ export function isV2DurableContinuationKey(key) {
168
+ return key.startsWith("cont:") || key.startsWith("evt:");
169
+ }
170
+ export function newEvtKey() {
171
+ return `evt:${randomBytes(16).toString("hex")}`;
172
+ }
173
+ export function builtinForType(type) {
174
+ return BUILTINS.find((builtin) => builtin.type === type);
175
+ }
176
+ export function formatConversationKey(type, fields) {
177
+ switch (type) {
178
+ case "pr":
179
+ return `github-pr:${fields.fullName}#${fields.number}`;
180
+ case "issue":
181
+ return `github-issue:${fields.fullName}#${fields.number}`;
182
+ case "origin-pr":
183
+ return `origin-pr:${fields.fullName}#${fields.number}`;
184
+ case "thread":
185
+ return `${fields.channelId}:${fields.threadTs}`;
186
+ }
187
+ }
188
+ export function mintClaim(claim) {
189
+ const resource = claim.resource;
190
+ if ("channelId" in resource && "threadTs" in resource) {
191
+ if (claim.type !== "thread") {
192
+ throw new V2EngineError(`v2: claim type ${claim.type} does not match thread resource`);
193
+ }
194
+ return `cont:${formatConversationKey("thread", resource)}`;
195
+ }
196
+ const fullName = "fullName" in resource
197
+ ? resource.fullName
198
+ : `${resource.owner}/${resource.repo}`;
199
+ if (claim.type === "thread") {
200
+ throw new V2EngineError("v2: thread claim requires channelId and threadTs");
201
+ }
202
+ return `cont:${formatConversationKey(claim.type, {
203
+ fullName,
204
+ number: resource.number,
205
+ })}`;
206
+ }
207
+ export function handshakeContinuation(input) {
208
+ var _a;
209
+ if (input.identity === undefined) {
210
+ return { ok: true };
211
+ }
212
+ const parsed = parseContinuationSelector(input.identity);
213
+ if (parsed === undefined) {
214
+ return {
215
+ ok: false,
216
+ error: `v2: invalid continuation selector ${JSON.stringify(input.identity)}`,
217
+ };
218
+ }
219
+ if (parsed.version !== 2 || parsed.type === "none") {
220
+ return { ok: true };
221
+ }
222
+ if (input.kind !== undefined &&
223
+ parsed.kind !== "http" &&
224
+ input.kind !== parsed.kind) {
225
+ return {
226
+ ok: false,
227
+ error: `v2: continuation ${parsed.selector} does not match channel kind ${input.kind}`,
228
+ };
229
+ }
230
+ const builtin = builtinForType(parsed.type);
231
+ if (builtin === undefined) {
232
+ return {
233
+ ok: false,
234
+ error: `v2: unknown continuation type ${parsed.type}`,
235
+ };
236
+ }
237
+ const eventTypes = (_a = input.eventTypes) !== null && _a !== void 0 ? _a : [];
238
+ const allowed = new Set(builtin.events);
239
+ const unexpected = eventTypes.filter((event) => !allowed.has(event));
240
+ if (unexpected.length > 0) {
241
+ return {
242
+ ok: false,
243
+ error: `v2: eventTypes ${unexpected.join(", ")} are not in the ${parsed.type} membership`,
244
+ };
245
+ }
246
+ return { ok: true };
247
+ }
248
+ /**
249
+ * CP/pack extract. Returns a `cont:` key when the payload binds the
250
+ * identity type; undefined on membership miss or missing number (caller
251
+ * stamps `evt:` and does not join the conversation index).
252
+ */
253
+ export function extractContinuation(type, eventName, payload) {
254
+ const builtin = builtinForType(type);
255
+ if (builtin === undefined) {
256
+ return undefined;
257
+ }
258
+ if (!builtin.events.includes(eventName)) {
259
+ return undefined;
260
+ }
261
+ switch (type) {
262
+ case "pr":
263
+ case "issue":
264
+ return extractGitHub(type, eventName, payload);
265
+ case "thread":
266
+ return extractSlack(payload);
267
+ case "origin-pr":
268
+ return extractOrigin(payload);
269
+ }
270
+ }
271
+ export function stampMailboxContinuation(identity, eventName, payload, kind) {
272
+ const parsed = resolveContinuation(identity, kind);
273
+ if (parsed.version !== 2) {
274
+ throw new V2EngineError("v2: refusing to stamp a mailbox key for v1");
275
+ }
276
+ if (parsed.type === "none") {
277
+ return { key: newEvtKey() };
278
+ }
279
+ const extracted = extractContinuation(parsed.type, eventName, payload);
280
+ if (extracted === undefined) {
281
+ return { key: newEvtKey() };
282
+ }
283
+ return { key: extracted };
284
+ }
285
+ export function resolveV2Key(identity, options, kind) {
286
+ const parsed = resolveContinuation(identity, kind);
287
+ if (parsed.version !== 2) {
288
+ throw new V2EngineError("v2: resolveV2Key requires a /v2 selector");
289
+ }
290
+ if (options.continuationToken !== undefined) {
291
+ throw new V2EngineError("v2: do not pass continuationToken; use mailboxContinuation or claim");
292
+ }
293
+ if (options.claim !== undefined) {
294
+ if (parsed.type === "none") {
295
+ throw new V2EngineError("v2: type none cannot take a claim");
296
+ }
297
+ if (options.claim.type !== parsed.type) {
298
+ throw new V2EngineError(`v2: claim type ${options.claim.type} does not match identity type ${parsed.type}`);
299
+ }
300
+ return mintClaim(options.claim);
301
+ }
302
+ if (options.mailboxContinuation !== undefined) {
303
+ return options.mailboxContinuation.key;
304
+ }
305
+ if (parsed.type === "none") {
306
+ return newEvtKey();
307
+ }
308
+ throw new V2EngineError("v2: no CP key on mailbox and no claim; refusing to mint in the engine");
309
+ }
310
+ export function bindMailboxContinuationSend(send, mailbox) {
311
+ if (mailbox === undefined) {
312
+ return send;
313
+ }
314
+ return (message, options) => { var _a; return send(message, Object.assign(Object.assign({}, options), { mailboxContinuation: (_a = options === null || options === void 0 ? void 0 : options.mailboxContinuation) !== null && _a !== void 0 ? _a : mailbox })); };
315
+ }
316
+ function extractGitHub(type, eventName, payload) {
317
+ const record = asRecord(payload);
318
+ if (record === undefined) {
319
+ return undefined;
320
+ }
321
+ const fullName = githubFullName(record);
322
+ if (fullName === undefined) {
323
+ return undefined;
324
+ }
325
+ const bound = githubBoundConversation(eventName, record);
326
+ if (bound === undefined || bound.type !== type) {
327
+ return undefined;
328
+ }
329
+ return `cont:${formatConversationKey(type, {
330
+ fullName,
331
+ number: bound.number,
332
+ })}`;
333
+ }
334
+ function githubBoundConversation(eventName, payload) {
335
+ var _a, _b;
336
+ var _c;
337
+ switch (eventName) {
338
+ case "pull_request":
339
+ case "pull_request_review":
340
+ case "pull_request_review_comment":
341
+ case "pull_request_review_thread": {
342
+ const number = positiveInt((_a = asRecord(payload.pull_request)) === null || _a === void 0 ? void 0 : _a.number);
343
+ return number === undefined ? undefined : { type: "pr", number };
344
+ }
345
+ case "issues": {
346
+ const number = positiveInt((_b = asRecord(payload.issue)) === null || _b === void 0 ? void 0 : _b.number);
347
+ return number === undefined ? undefined : { type: "issue", number };
348
+ }
349
+ case "issue_comment": {
350
+ const issue = asRecord(payload.issue);
351
+ const number = positiveInt(issue === null || issue === void 0 ? void 0 : issue.number);
352
+ if (number === undefined) {
353
+ return undefined;
354
+ }
355
+ return (issue === null || issue === void 0 ? void 0 : issue.pull_request) !== undefined && issue.pull_request !== null
356
+ ? { type: "pr", number }
357
+ : { type: "issue", number };
358
+ }
359
+ case "check_suite": {
360
+ const number = firstPullRequestNumber(asRecord(payload.check_suite));
361
+ return number === undefined ? undefined : { type: "pr", number };
362
+ }
363
+ case "check_run": {
364
+ const checkRun = asRecord(payload.check_run);
365
+ const number = (_c = firstPullRequestNumber(checkRun)) !== null && _c !== void 0 ? _c : firstPullRequestNumber(asRecord(checkRun === null || checkRun === void 0 ? void 0 : checkRun.check_suite));
366
+ return number === undefined ? undefined : { type: "pr", number };
367
+ }
368
+ default:
369
+ return undefined;
370
+ }
371
+ }
372
+ function extractSlack(payload) {
373
+ var _a, _b, _c, _d;
374
+ const record = asRecord(payload);
375
+ if (record === undefined) {
376
+ return undefined;
377
+ }
378
+ const channelId = (_b = (_a = nonEmpty(record.channelId)) !== null && _a !== void 0 ? _a : nonEmpty(record.channel_id)) !== null && _b !== void 0 ? _b : nonEmpty(record.channel);
379
+ const threadTs = (_d = (_c = nonEmpty(record.threadTs)) !== null && _c !== void 0 ? _c : nonEmpty(record.thread_ts)) !== null && _d !== void 0 ? _d : nonEmpty(record.ts);
380
+ if (channelId === undefined || threadTs === undefined) {
381
+ return undefined;
382
+ }
383
+ return `cont:${formatConversationKey("thread", { channelId, threadTs })}`;
384
+ }
385
+ function extractOrigin(payload) {
386
+ var _a, _b, _c, _d;
387
+ var _e, _f, _g, _h;
388
+ const record = asRecord(payload);
389
+ if (record === undefined) {
390
+ return undefined;
391
+ }
392
+ const inner = (_e = asRecord(record.pullRequest)) !== null && _e !== void 0 ? _e : asRecord(record.pull_request);
393
+ const number = positiveInt(inner === null || inner === void 0 ? void 0 : inner.number);
394
+ const repository = (_f = asRecord(record.repository)) !== null && _f !== void 0 ? _f : asRecord((_b = asRecord((_a = asRecord(record.event)) === null || _a === void 0 ? void 0 : _a.payload)) === null || _b === void 0 ? void 0 : _b.repository);
395
+ const fullName = (_h = (_g = nonEmpty((_c = asRecord(repository)) === null || _c === void 0 ? void 0 : _c.fullName)) !== null && _g !== void 0 ? _g : nonEmpty((_d = asRecord(repository)) === null || _d === void 0 ? void 0 : _d.full_name)) !== null && _h !== void 0 ? _h : originFullName(repository);
396
+ if (number === undefined || fullName === undefined) {
397
+ return undefined;
398
+ }
399
+ return `cont:${formatConversationKey("origin-pr", { fullName, number })}`;
400
+ }
401
+ function githubFullName(payload) {
402
+ var _a;
403
+ const repository = asRecord(payload.repository);
404
+ return (_a = nonEmpty(repository === null || repository === void 0 ? void 0 : repository.full_name)) !== null && _a !== void 0 ? _a : originFullName(repository);
405
+ }
406
+ function originFullName(repository) {
407
+ var _a, _b;
408
+ var _c, _d;
409
+ if (repository === undefined) {
410
+ return undefined;
411
+ }
412
+ const owner = (_d = (_c = nonEmpty(repository.owner)) !== null && _c !== void 0 ? _c : nonEmpty((_a = asRecord(repository.owner)) === null || _a === void 0 ? void 0 : _a.login)) !== null && _d !== void 0 ? _d : nonEmpty((_b = asRecord(repository.owner)) === null || _b === void 0 ? void 0 : _b.name);
413
+ const name = nonEmpty(repository.name);
414
+ if (owner === undefined || name === undefined) {
415
+ return undefined;
416
+ }
417
+ return `${owner}/${name}`;
418
+ }
419
+ function firstPullRequestNumber(record) {
420
+ var _a;
421
+ const pullRequests = record === null || record === void 0 ? void 0 : record.pull_requests;
422
+ if (!Array.isArray(pullRequests) || pullRequests.length === 0) {
423
+ return undefined;
424
+ }
425
+ return positiveInt((_a = asRecord(pullRequests[0])) === null || _a === void 0 ? void 0 : _a.number);
426
+ }
427
+ function asRecord(value) {
428
+ return typeof value === "object" && value !== null && !Array.isArray(value)
429
+ ? value
430
+ : undefined;
431
+ }
432
+ function nonEmpty(value) {
433
+ return typeof value === "string" && value.trim() !== ""
434
+ ? value.trim()
435
+ : undefined;
436
+ }
437
+ function positiveInt(value) {
438
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
439
+ return value;
440
+ }
441
+ if (typeof value === "string" && /^[1-9]\d*$/.test(value)) {
442
+ return Number(value);
443
+ }
444
+ return undefined;
445
+ }
@@ -28,6 +28,7 @@
28
28
  * Keep that capability per-channel: do not reintroduce a provider-wide event
29
29
  * union for control-plane consumers.
30
30
  */
31
+ import type { ContinuationIdentity } from "../continuation.js";
31
32
  import type { AgentProject, AgentProjectInfo } from "../types.js";
32
33
  export declare const DEPLOY_MANIFEST_VERSION: 1;
33
34
  export interface AgentDeployManifestDeclarations {
@@ -35,16 +36,25 @@ export interface AgentDeployManifestDeclarations {
35
36
  publicRoutePaths: string[];
36
37
  /** Where this runtime mirrors framework-owned sessions and event chunks. */
37
38
  frameworkStorage?: "cursor-hosted" | "other" | "none";
39
+ /**
40
+ * Hosted architecture this release deploys onto. `"v2"` selects the v2
41
+ * control plane; omitted channel constructors inherit kind `/v2`
42
+ * continuation defaults. Omit or `"v1"` keeps engine minting.
43
+ */
44
+ architecture?: "v1" | "v2";
38
45
  /**
39
46
  * Mounted channels with their kinds and per-channel event capability.
40
47
  * Scope keys are installation config and are never extracted here.
41
- * Continuation is opt-in: without a deterministic selector key, every
42
- * inbound event starts a fresh session. Selector encoding remains open.
48
+ * Omitted channel `continuation` is `/v1` (engine minting) unless the
49
+ * agent set `architecture: "v2"`, which stamps kind `/v2` defaults
50
+ * (`github.pr/v2`, `slack.thread/v2`, `origin.pr/v2`). Per-channel
51
+ * selectors still override type.
43
52
  */
44
53
  channels: Array<{
45
54
  id: string;
46
55
  kind?: string;
47
56
  eventTypes?: string[];
57
+ continuation: ContinuationIdentity;
48
58
  }>;
49
59
  }
50
60
  export interface AgentDeployManifestV1 {
@@ -1 +1 @@
1
- {"version":3,"file":"deploy-manifest.d.ts","sourceRoot":"","sources":["../../src/internal/deploy-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIlE,eAAO,MAAM,uBAAuB,EAAE,CAAK,CAAC;AA6B5C,MAAM,WAAW,+BAA+B;IAC9C,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;IACtD;;;;;OAKG;IACH,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,CAAC,CAAC;IACnB,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,+BAA+B,CAAC;IAC9C;;;OAGG;IACH,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,YAAY,EACrB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD,qBAAqB,CA2CvB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,OAAO,GACX,qBAAqB,GAAG,SAAS,CAOnC"}
1
+ {"version":3,"file":"deploy-manifest.d.ts","sourceRoot":"","sources":["../../src/internal/deploy-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAUlE,eAAO,MAAM,uBAAuB,EAAE,CAAK,CAAC;AAiC5C,MAAM,WAAW,+BAA+B;IAC9C,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;IACtD;;;;OAIG;IACH,YAAY,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,EAAE,KAAK,CAAC;QACd,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB,YAAY,EAAE,oBAAoB,CAAC;KACpC,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,CAAC,CAAC;IACnB,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,+BAA+B,CAAC;IAC9C;;;OAGG;IACH,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,YAAY,EACrB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD,qBAAqB,CAoDvB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,OAAO,GACX,qBAAqB,GAAG,SAAS,CAOnC"}