@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.
- package/AGENTS.md +0 -12
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +8 -1
- package/dist/channels/github/cursor-account.d.ts +28 -0
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +60 -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/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/review-comments.d.ts +37 -7
- package/dist/internal/review-comments.d.ts.map +1 -1
- package/dist/internal/review-comments.js +209 -10
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +117 -55
- 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/api.ts +8 -0
- package/src/channels/github/cursor-account.ts +76 -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/review-comments.ts +257 -16
- package/src/internal/server.ts +146 -68
- 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
|
@@ -23,6 +23,7 @@ import { httpChannel } from "../channels.js";
|
|
|
23
23
|
import type { OtelDefinition } from "../otel.js";
|
|
24
24
|
import type { StorageDefinition } from "../storage.js";
|
|
25
25
|
import {
|
|
26
|
+
type AgentArchitecture,
|
|
26
27
|
type AgentConcurrencyOptions,
|
|
27
28
|
type AgentConfigDefinition,
|
|
28
29
|
type AgentHostingOptions,
|
|
@@ -68,6 +69,7 @@ import {
|
|
|
68
69
|
builtinTools,
|
|
69
70
|
TAG_ARTIFACT_TOOL_NAME,
|
|
70
71
|
} from "./builtin-tools/index.js";
|
|
72
|
+
import { applyAgentContinuationDefaults } from "./continuation-channel.js";
|
|
71
73
|
import { isValidCron } from "./cron.js";
|
|
72
74
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
73
75
|
import {
|
|
@@ -311,12 +313,19 @@ export async function loadAgentProject(
|
|
|
311
313
|
await warnUnknownSlots(ctx, agentDir);
|
|
312
314
|
}
|
|
313
315
|
|
|
316
|
+
const resolvedHttpChannel = httpChannelDefinition ?? httpChannel();
|
|
317
|
+
applyAgentContinuationDefaults({
|
|
318
|
+
agent,
|
|
319
|
+
channels,
|
|
320
|
+
httpChannel: resolvedHttpChannel,
|
|
321
|
+
});
|
|
322
|
+
|
|
314
323
|
return {
|
|
315
324
|
rootDir,
|
|
316
325
|
agentDir,
|
|
317
326
|
name: agent.name,
|
|
318
327
|
agent,
|
|
319
|
-
httpChannel:
|
|
328
|
+
httpChannel: resolvedHttpChannel,
|
|
320
329
|
channels,
|
|
321
330
|
schedules,
|
|
322
331
|
hooks,
|
|
@@ -387,6 +396,13 @@ async function loadAgent(
|
|
|
387
396
|
"tools is not supported on subagents (they keep the harness task-tool toolset); ignoring.",
|
|
388
397
|
});
|
|
389
398
|
}
|
|
399
|
+
if (config?.architecture !== undefined) {
|
|
400
|
+
ctx.diagnostics.push({
|
|
401
|
+
severity: "warning",
|
|
402
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
403
|
+
message: "architecture is not supported on subagents; ignoring.",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
390
406
|
const artifactsPath = await findModuleFile(agentDir, "artifacts");
|
|
391
407
|
if (artifactsPath !== undefined) {
|
|
392
408
|
ctx.diagnostics.push({
|
|
@@ -505,6 +521,7 @@ async function loadAgent(
|
|
|
505
521
|
};
|
|
506
522
|
const hosting = resolveHosting(ctx, agentDir, config?.hosting);
|
|
507
523
|
const concurrency = resolveConcurrency(ctx, agentDir, config?.concurrency);
|
|
524
|
+
const architecture = resolveArchitecture(ctx, agentDir, config?.architecture);
|
|
508
525
|
|
|
509
526
|
if (runtime === "cloud") {
|
|
510
527
|
warnCloudRuntimeLimitations(ctx, agentDir, {
|
|
@@ -529,6 +546,7 @@ async function loadAgent(
|
|
|
529
546
|
...(allowedTools === undefined ? {} : { allowedTools }),
|
|
530
547
|
hosting,
|
|
531
548
|
concurrency,
|
|
549
|
+
...(architecture === undefined ? {} : { architecture }),
|
|
532
550
|
tools,
|
|
533
551
|
skills,
|
|
534
552
|
connections,
|
|
@@ -537,6 +555,26 @@ async function loadAgent(
|
|
|
537
555
|
};
|
|
538
556
|
}
|
|
539
557
|
|
|
558
|
+
function resolveArchitecture(
|
|
559
|
+
ctx: DiscoveryContext,
|
|
560
|
+
agentDir: string,
|
|
561
|
+
architecture: AgentArchitecture | undefined
|
|
562
|
+
): AgentArchitecture | undefined {
|
|
563
|
+
if (
|
|
564
|
+
architecture === undefined ||
|
|
565
|
+
architecture === "v1" ||
|
|
566
|
+
architecture === "v2"
|
|
567
|
+
) {
|
|
568
|
+
return architecture;
|
|
569
|
+
}
|
|
570
|
+
ctx.diagnostics.push({
|
|
571
|
+
severity: "error",
|
|
572
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
573
|
+
message: `architecture must be "v1" or "v2"; got ${JSON.stringify(architecture)}.`,
|
|
574
|
+
});
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
|
|
540
578
|
/** Absolute harness cwd parent, or undefined when unset / blank. */
|
|
541
579
|
function resolveAgentCwd(
|
|
542
580
|
ctx: DiscoveryContext,
|
|
@@ -1549,7 +1587,7 @@ function warnCloudRuntimeLimitations(
|
|
|
1549
1587
|
severity: "warning",
|
|
1550
1588
|
path: displayPath(ctx, join(agentDir, "skills")),
|
|
1551
1589
|
message:
|
|
1552
|
-
"Authored skills
|
|
1590
|
+
"Authored skills reach cloud agents on hosted deployments via the deployment store's skills/ directory (native skill discovery). Cloud turns started outside hosting (dev serve/run) see only skills already in the cloud repo.",
|
|
1553
1591
|
});
|
|
1554
1592
|
}
|
|
1555
1593
|
if (counts.seedFiles > 0) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import type { HostGitHubClient } from "../types.js";
|
|
3
3
|
|
|
4
|
-
export type ReviewCommentPublisher = "local_gh" | "host_github";
|
|
4
|
+
export type ReviewCommentPublisher = "local_gh" | "host_github" | "host_origin";
|
|
5
5
|
|
|
6
6
|
export type ReviewCommentPostResult = {
|
|
7
7
|
posted: boolean;
|
|
@@ -57,12 +57,25 @@ export type ReviewCommentPublisherImplementation = {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* SCMs this module can
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
60
|
+
* SCMs this module can address. Same growth seam as `shallow-clone.ts`'s
|
|
61
|
+
* {@link import("./shallow-clone.js").ScmProvider}: adding a forge means a
|
|
62
|
+
* resolver case plus a publisher (and, when the public API supports it, a
|
|
63
|
+
* reader / thread resolver). Callers above this line stay provider-neutral.
|
|
64
64
|
*/
|
|
65
|
-
export type ScmReviewProvider = "github";
|
|
65
|
+
export type ScmReviewProvider = "github" | "origin";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Origin public REST client used to post PR reviews (`ctx.origin` /
|
|
69
|
+
* {@link import("../channels/origin/api.js").createOriginClient}). Narrower
|
|
70
|
+
* than the full Origin handle so tests and CLI paths can inject `request()`.
|
|
71
|
+
*/
|
|
72
|
+
export type HostOriginReviewClient = {
|
|
73
|
+
request(input: {
|
|
74
|
+
method: "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
|
|
75
|
+
path: string;
|
|
76
|
+
body?: unknown;
|
|
77
|
+
}): Promise<unknown>;
|
|
78
|
+
};
|
|
66
79
|
|
|
67
80
|
/** Which SCM's PR to read review comments from. */
|
|
68
81
|
export type ScmReviewTarget = {
|
|
@@ -147,18 +160,12 @@ export type ReviewThreadResolverImplementation = {
|
|
|
147
160
|
) => Promise<ResolveReviewThreadsResult>;
|
|
148
161
|
};
|
|
149
162
|
|
|
150
|
-
/** Parse a PR URL into the SCM target to read comments
|
|
163
|
+
/** Parse a PR URL into the SCM target to read or post comments on. */
|
|
151
164
|
export function resolveScmReviewTarget(
|
|
152
165
|
prUrl: string
|
|
153
166
|
): ScmReviewTarget | undefined {
|
|
154
|
-
const parsed =
|
|
155
|
-
return parsed.kind === "valid"
|
|
156
|
-
? {
|
|
157
|
-
provider: "github",
|
|
158
|
-
repo: parsed.value.repo,
|
|
159
|
-
prNumber: parsed.value.prNumber,
|
|
160
|
-
}
|
|
161
|
-
: undefined;
|
|
167
|
+
const parsed = parseReviewPrUrl(prUrl);
|
|
168
|
+
return parsed.kind === "valid" ? parsed.value : undefined;
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
/**
|
|
@@ -175,6 +182,10 @@ export function createReviewCommentReader(args: {
|
|
|
175
182
|
return args.github === undefined
|
|
176
183
|
? undefined
|
|
177
184
|
: createHostGithubReviewCommentReader(args.github);
|
|
185
|
+
case "origin":
|
|
186
|
+
// Public Origin REST lists discussion comments, not diff-anchored
|
|
187
|
+
// review threads. Stay undefined until a reader can fetch by id.
|
|
188
|
+
return undefined;
|
|
178
189
|
}
|
|
179
190
|
}
|
|
180
191
|
|
|
@@ -191,6 +202,8 @@ export function createReviewThreadResolver(args: {
|
|
|
191
202
|
return args.github === undefined
|
|
192
203
|
? undefined
|
|
193
204
|
: createHostGithubReviewThreadResolver(args.github);
|
|
205
|
+
case "origin":
|
|
206
|
+
return undefined;
|
|
194
207
|
}
|
|
195
208
|
}
|
|
196
209
|
|
|
@@ -222,7 +235,11 @@ export async function resolveReviewThreadsForComments(args: {
|
|
|
222
235
|
github: args.github,
|
|
223
236
|
});
|
|
224
237
|
if (resolver === undefined) {
|
|
225
|
-
return skippedResolve(
|
|
238
|
+
return skippedResolve(
|
|
239
|
+
target.provider === "github"
|
|
240
|
+
? "no GitHub client bound to resolve threads"
|
|
241
|
+
: `no thread resolver for ${target.provider}`
|
|
242
|
+
);
|
|
226
243
|
}
|
|
227
244
|
return resolver.resolveReviewThreadsForComments({
|
|
228
245
|
target,
|
|
@@ -560,6 +577,49 @@ export function createHostGithubReviewPublisher(
|
|
|
560
577
|
};
|
|
561
578
|
}
|
|
562
579
|
|
|
580
|
+
/**
|
|
581
|
+
* Pick the publisher for `prUrl`'s SCM. GitHub falls back to local `gh`
|
|
582
|
+
* when no host client is bound (CLI). Origin has no local CLI publisher —
|
|
583
|
+
* returns undefined unless an Origin REST client is provided.
|
|
584
|
+
*/
|
|
585
|
+
export function createReviewCommentPublisher(args: {
|
|
586
|
+
prUrl: string;
|
|
587
|
+
github?: HostGitHubClient;
|
|
588
|
+
origin?: HostOriginReviewClient;
|
|
589
|
+
}): ReviewCommentPublisherImplementation | undefined {
|
|
590
|
+
const target = resolveScmReviewTarget(args.prUrl);
|
|
591
|
+
if (target === undefined) {
|
|
592
|
+
return undefined;
|
|
593
|
+
}
|
|
594
|
+
switch (target.provider) {
|
|
595
|
+
case "github":
|
|
596
|
+
return args.github !== undefined
|
|
597
|
+
? createHostGithubReviewPublisher(args.github)
|
|
598
|
+
: createLocalGhReviewPublisher();
|
|
599
|
+
case "origin":
|
|
600
|
+
return args.origin === undefined
|
|
601
|
+
? undefined
|
|
602
|
+
: createHostOriginReviewPublisher(args.origin);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Posts a COMMENT-verdict Origin review through the public REST API.
|
|
608
|
+
*
|
|
609
|
+
* Public Origin REST has no diff-anchored review comments (those are an
|
|
610
|
+
* internal CreateCommentThread RPC). Inline comments in the request are
|
|
611
|
+
* folded into the review body so findings still land on the PR.
|
|
612
|
+
*/
|
|
613
|
+
export function createHostOriginReviewPublisher(
|
|
614
|
+
origin: HostOriginReviewClient
|
|
615
|
+
): ReviewCommentPublisherImplementation {
|
|
616
|
+
return {
|
|
617
|
+
async postReview(args) {
|
|
618
|
+
return postReviewViaHostOrigin(origin, args);
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
563
623
|
async function postReviewViaHostGithub(
|
|
564
624
|
github: HostGitHubClient,
|
|
565
625
|
args: ReviewCommentPostArgs
|
|
@@ -779,6 +839,76 @@ function skippedHost(detail: string): ReviewCommentPostResult {
|
|
|
779
839
|
};
|
|
780
840
|
}
|
|
781
841
|
|
|
842
|
+
function skippedOrigin(detail: string): ReviewCommentPostResult {
|
|
843
|
+
return {
|
|
844
|
+
posted: false,
|
|
845
|
+
publisher: "host_origin",
|
|
846
|
+
mode: "skipped",
|
|
847
|
+
inlineCommentCount: 0,
|
|
848
|
+
detail,
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async function postReviewViaHostOrigin(
|
|
853
|
+
origin: HostOriginReviewClient,
|
|
854
|
+
args: ReviewCommentPostArgs
|
|
855
|
+
): Promise<ReviewCommentPostResult> {
|
|
856
|
+
const target = resolveScmReviewTarget(args.prUrl);
|
|
857
|
+
if (target === undefined || target.provider !== "origin") {
|
|
858
|
+
return skippedOrigin("host Origin publisher only supports Origin PRs");
|
|
859
|
+
}
|
|
860
|
+
const [owner, repo] = target.repo.split("/", 2);
|
|
861
|
+
if (owner === undefined || repo === undefined || repo === "") {
|
|
862
|
+
return skippedOrigin("unparseable owner/repo from PR URL");
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
try {
|
|
866
|
+
args.signal?.throwIfAborted();
|
|
867
|
+
await origin.request({
|
|
868
|
+
method: "POST",
|
|
869
|
+
path: `/v1/origin/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${target.prNumber}/reviews`,
|
|
870
|
+
body: {
|
|
871
|
+
verdict: "comment",
|
|
872
|
+
body: originReviewBody(args),
|
|
873
|
+
},
|
|
874
|
+
});
|
|
875
|
+
return {
|
|
876
|
+
posted: true,
|
|
877
|
+
publisher: "host_origin",
|
|
878
|
+
mode: "review_body_only",
|
|
879
|
+
inlineCommentCount: 0,
|
|
880
|
+
detail: `posted Origin review on ${target.repo}#${target.prNumber}`,
|
|
881
|
+
};
|
|
882
|
+
} catch (error) {
|
|
883
|
+
if (args.signal?.aborted) {
|
|
884
|
+
throw error;
|
|
885
|
+
}
|
|
886
|
+
return skippedOrigin(
|
|
887
|
+
`host Origin review post failed: ${error instanceof Error ? error.message : String(error)}`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Origin's public CreatePullRequestReview has a body, not inline comments.
|
|
894
|
+
* Fold attempted anchors into the summary so findings are not dropped.
|
|
895
|
+
*/
|
|
896
|
+
function originReviewBody(args: ReviewCommentPostArgs): string {
|
|
897
|
+
if (args.comments.length === 0) {
|
|
898
|
+
return args.message;
|
|
899
|
+
}
|
|
900
|
+
const sections = args.comments.map((comment) => {
|
|
901
|
+
const location =
|
|
902
|
+
comment.start_line !== undefined &&
|
|
903
|
+
comment.start_line > 0 &&
|
|
904
|
+
comment.start_line !== comment.line
|
|
905
|
+
? `${comment.file}:${comment.start_line}-${comment.line}`
|
|
906
|
+
: `${comment.file}:${comment.line}`;
|
|
907
|
+
return `### \`${location}\`\n\n${comment.body}`;
|
|
908
|
+
});
|
|
909
|
+
return [args.message, ...sections].join("\n\n");
|
|
910
|
+
}
|
|
911
|
+
|
|
782
912
|
function skippedResolve(detail: string): ResolveReviewThreadsResult {
|
|
783
913
|
return {
|
|
784
914
|
requested: 0,
|
|
@@ -790,6 +920,117 @@ function skippedResolve(detail: string): ResolveReviewThreadsResult {
|
|
|
790
920
|
};
|
|
791
921
|
}
|
|
792
922
|
|
|
923
|
+
const ORIGIN_PR_PAGE_HOST_PATTERN = /^origin(-[a-z0-9]+)?\.cursor\.com$/i;
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Provider-neutral PR URL parse. GitHub.com plus the Origin page shapes
|
|
927
|
+
* `parsePrTarget` in security-review-lib accepts. Duplicated here so
|
|
928
|
+
* agent-serve does not import that package.
|
|
929
|
+
*/
|
|
930
|
+
function parseReviewPrUrl(
|
|
931
|
+
prUrl: string
|
|
932
|
+
):
|
|
933
|
+
| { kind: "valid"; value: ScmReviewTarget }
|
|
934
|
+
| { kind: "invalid"; detail: string } {
|
|
935
|
+
let url: URL;
|
|
936
|
+
try {
|
|
937
|
+
url = new URL(prUrl);
|
|
938
|
+
} catch (error) {
|
|
939
|
+
return {
|
|
940
|
+
kind: "invalid",
|
|
941
|
+
detail: `unparseable PR URL: ${error instanceof Error ? error.message : String(error)}`,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
946
|
+
const hostname = url.hostname;
|
|
947
|
+
|
|
948
|
+
if (hostname === "github.com" && parts.length >= 4 && parts[2] === "pull") {
|
|
949
|
+
const prNumber = Number(parts[3]);
|
|
950
|
+
if (Number.isInteger(prNumber) && prNumber > 0) {
|
|
951
|
+
return {
|
|
952
|
+
kind: "valid",
|
|
953
|
+
value: {
|
|
954
|
+
provider: "github",
|
|
955
|
+
repo: `${parts[0]}/${parts[1]}`,
|
|
956
|
+
prNumber,
|
|
957
|
+
},
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (
|
|
963
|
+
ORIGIN_PR_PAGE_HOST_PATTERN.test(hostname) &&
|
|
964
|
+
parts.length >= 4 &&
|
|
965
|
+
parts[2] === "github"
|
|
966
|
+
) {
|
|
967
|
+
const prNumber = Number(parts[3]);
|
|
968
|
+
if (Number.isInteger(prNumber) && prNumber > 0) {
|
|
969
|
+
return {
|
|
970
|
+
kind: "valid",
|
|
971
|
+
value: {
|
|
972
|
+
provider: "origin",
|
|
973
|
+
repo: `${parts[0]}/${parts[1]}`,
|
|
974
|
+
prNumber,
|
|
975
|
+
},
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (
|
|
981
|
+
/^(www\.)?cursor\.com$/i.test(hostname) &&
|
|
982
|
+
parts[0] === "codebase" &&
|
|
983
|
+
parts.length >= 5 &&
|
|
984
|
+
parts[3] === "pull"
|
|
985
|
+
) {
|
|
986
|
+
const prNumber = Number(parts[4]);
|
|
987
|
+
if (Number.isInteger(prNumber) && prNumber > 0) {
|
|
988
|
+
return {
|
|
989
|
+
kind: "valid",
|
|
990
|
+
value: {
|
|
991
|
+
provider: "origin",
|
|
992
|
+
repo: `${parts[1]}/${parts[2]}`,
|
|
993
|
+
prNumber,
|
|
994
|
+
},
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (hostname === "review.cursor.com" && parts[0] === "pr") {
|
|
1000
|
+
if (parts.length >= 5 && parts[3] === "github") {
|
|
1001
|
+
const prNumber = Number(parts[4]);
|
|
1002
|
+
if (Number.isInteger(prNumber) && prNumber > 0) {
|
|
1003
|
+
return {
|
|
1004
|
+
kind: "valid",
|
|
1005
|
+
value: {
|
|
1006
|
+
provider: "origin",
|
|
1007
|
+
repo: `${parts[1]}/${parts[2]}`,
|
|
1008
|
+
prNumber,
|
|
1009
|
+
},
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (parts.length >= 4) {
|
|
1014
|
+
const prNumber = Number(parts[3]);
|
|
1015
|
+
if (Number.isInteger(prNumber) && prNumber > 0) {
|
|
1016
|
+
return {
|
|
1017
|
+
kind: "valid",
|
|
1018
|
+
value: {
|
|
1019
|
+
provider: "origin",
|
|
1020
|
+
repo: `${parts[1]}/${parts[2]}`,
|
|
1021
|
+
prNumber,
|
|
1022
|
+
},
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
return {
|
|
1029
|
+
kind: "invalid",
|
|
1030
|
+
detail: "unparseable PR URL: not a GitHub or Origin pull request",
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
|
|
793
1034
|
function parseGithubPrUrl(
|
|
794
1035
|
prUrl: string
|
|
795
1036
|
):
|