paperclip-github-plugin 0.8.9 → 0.8.11
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/README.md +29 -1
- package/dist/manifest.js +63 -1
- package/dist/ui/index.js +20 -8
- package/dist/ui/index.js.map +4 -4
- package/dist/worker.js +348 -3
- package/package.json +3 -3
package/dist/worker.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/worker.ts
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
2
3
|
import { realpathSync } from "node:fs";
|
|
3
4
|
import { readFile } from "node:fs/promises";
|
|
4
5
|
import { homedir } from "node:os";
|
|
@@ -532,6 +533,58 @@ var GITHUB_AGENT_TOOLS = [
|
|
|
532
533
|
}
|
|
533
534
|
}
|
|
534
535
|
},
|
|
536
|
+
{
|
|
537
|
+
name: "upload_pull_request_asset",
|
|
538
|
+
displayName: "Upload Pull Request Asset",
|
|
539
|
+
description: "Upload a PR-visible asset such as an image, PDF, log, archive, or report to a non-merge artifact branch and return durable markdown that can be embedded in the PR body.",
|
|
540
|
+
parametersSchema: {
|
|
541
|
+
type: "object",
|
|
542
|
+
additionalProperties: false,
|
|
543
|
+
required: ["fileName"],
|
|
544
|
+
allOf: [pullRequestTargetSchema],
|
|
545
|
+
anyOf: [
|
|
546
|
+
{ required: ["contentBase64"] },
|
|
547
|
+
{ required: ["dataUrl"] }
|
|
548
|
+
],
|
|
549
|
+
properties: {
|
|
550
|
+
repository: repositoryProperty,
|
|
551
|
+
pullRequestNumber: pullRequestNumberProperty,
|
|
552
|
+
paperclipIssueId: paperclipIssueIdProperty,
|
|
553
|
+
fileName: {
|
|
554
|
+
type: "string",
|
|
555
|
+
description: "Asset filename. The plugin sanitizes it and preserves a safe extension."
|
|
556
|
+
},
|
|
557
|
+
label: {
|
|
558
|
+
type: "string",
|
|
559
|
+
description: "Human-readable link text for the returned Markdown. Defaults to the sanitized filename."
|
|
560
|
+
},
|
|
561
|
+
alt: {
|
|
562
|
+
type: "string",
|
|
563
|
+
description: "Backward-compatible alias for label, useful as image alt text."
|
|
564
|
+
},
|
|
565
|
+
caption: {
|
|
566
|
+
type: "string",
|
|
567
|
+
description: "Optional human-facing caption returned with the uploaded asset metadata."
|
|
568
|
+
},
|
|
569
|
+
contentBase64: {
|
|
570
|
+
type: "string",
|
|
571
|
+
description: "Base64-encoded asset bytes. Assets are limited to 10 MiB."
|
|
572
|
+
},
|
|
573
|
+
dataUrl: {
|
|
574
|
+
type: "string",
|
|
575
|
+
description: "Alternative base64 data URL input such as data:application/pdf;base64,... or data:image/png;base64,... ."
|
|
576
|
+
},
|
|
577
|
+
mimeType: {
|
|
578
|
+
type: "string",
|
|
579
|
+
description: "Optional MIME type such as application/pdf or image/png. If omitted, the plugin infers common types from fileName and otherwise uses application/octet-stream."
|
|
580
|
+
},
|
|
581
|
+
artifactBranch: {
|
|
582
|
+
type: "string",
|
|
583
|
+
description: "Optional artifact branch name. Defaults to paperclip-artifacts-pr-<pullRequestNumber>."
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
},
|
|
535
588
|
{
|
|
536
589
|
name: "link_github_item",
|
|
537
590
|
displayName: "Link GitHub Item",
|
|
@@ -637,6 +690,9 @@ var COMPANY_METRIC_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_ID}/a
|
|
|
637
690
|
var ISSUE_LINK_API_ROUTE_KEY = "link-github-item";
|
|
638
691
|
var ISSUE_LINK_API_ROUTE_PATH = "/issue-link";
|
|
639
692
|
var ISSUE_LINK_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_ID}/api${ISSUE_LINK_API_ROUTE_PATH}`;
|
|
693
|
+
var PULL_REQUEST_ASSET_API_ROUTE_KEY = "upload-pull-request-asset";
|
|
694
|
+
var PULL_REQUEST_ASSET_API_ROUTE_PATH = "/pull-request-assets";
|
|
695
|
+
var PULL_REQUEST_ASSET_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_ID}/api${PULL_REQUEST_ASSET_API_ROUTE_PATH}`;
|
|
640
696
|
|
|
641
697
|
// src/paperclip-health.ts
|
|
642
698
|
function normalizeOptionalString(value) {
|
|
@@ -739,6 +795,40 @@ var AI_AUTHORED_MARKDOWN_FOOTER_PATTERN = /\n\n---\n###### ✨ This (?:comment|i
|
|
|
739
795
|
var HIDDEN_GITHUB_IMPORT_MARKER_PREFIX = "<!-- paperclip-github-plugin-imported-from: ";
|
|
740
796
|
var HIDDEN_GITHUB_IMPORT_MARKER_SUFFIX = " -->";
|
|
741
797
|
var EMPTY_GITHUB_ISSUE_DESCRIPTION_PLACEHOLDER = "_No description provided on GitHub._";
|
|
798
|
+
var MAX_PULL_REQUEST_ASSET_BYTES = 10 * 1024 * 1024;
|
|
799
|
+
var DEFAULT_PULL_REQUEST_ASSET_MIME_TYPE = "application/octet-stream";
|
|
800
|
+
var PULL_REQUEST_ASSET_MIME_TYPE_BY_EXTENSION = {
|
|
801
|
+
png: "image/png",
|
|
802
|
+
jpg: "image/jpeg",
|
|
803
|
+
jpeg: "image/jpeg",
|
|
804
|
+
webp: "image/webp",
|
|
805
|
+
gif: "image/gif",
|
|
806
|
+
pdf: "application/pdf",
|
|
807
|
+
txt: "text/plain",
|
|
808
|
+
md: "text/markdown",
|
|
809
|
+
markdown: "text/markdown",
|
|
810
|
+
json: "application/json",
|
|
811
|
+
csv: "text/csv",
|
|
812
|
+
xml: "application/xml",
|
|
813
|
+
zip: "application/zip",
|
|
814
|
+
gz: "application/gzip",
|
|
815
|
+
tgz: "application/gzip"
|
|
816
|
+
};
|
|
817
|
+
var PULL_REQUEST_ASSET_EXTENSION_BY_MIME_TYPE = {
|
|
818
|
+
"image/png": "png",
|
|
819
|
+
"image/jpeg": "jpg",
|
|
820
|
+
"image/webp": "webp",
|
|
821
|
+
"image/gif": "gif",
|
|
822
|
+
"application/pdf": "pdf",
|
|
823
|
+
"text/plain": "txt",
|
|
824
|
+
"text/markdown": "md",
|
|
825
|
+
"application/json": "json",
|
|
826
|
+
"text/csv": "csv",
|
|
827
|
+
"application/xml": "xml",
|
|
828
|
+
"application/zip": "zip",
|
|
829
|
+
"application/gzip": "gz",
|
|
830
|
+
"application/octet-stream": "bin"
|
|
831
|
+
};
|
|
742
832
|
var pluginRuntimeContext = null;
|
|
743
833
|
function normalizeCompanyId(value) {
|
|
744
834
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -5816,8 +5906,13 @@ function isGitHubPullRequestActionRequiredForSync(pullRequest) {
|
|
|
5816
5906
|
function isGitHubPullRequestPendingExternalWaitForSync(pullRequest) {
|
|
5817
5907
|
return pullRequest.ciState === "unfinished" && !pullRequest.hasUnresolvedReviewThreads && pullRequest.mergeability !== "conflicting" && (pullRequest.mergeStateStatus === "blocked" || pullRequest.mergeStateStatus === "unstable");
|
|
5818
5908
|
}
|
|
5909
|
+
function isGitHubPullRequestBlockedMaintainerApprovalWaitForSync(pullRequest) {
|
|
5910
|
+
return pullRequest.ciState === "green" && !pullRequest.hasUnresolvedReviewThreads && pullRequest.mergeability !== "conflicting" && pullRequest.mergeStateStatus === "blocked" && (pullRequest.reviewDecision === "unknown" || pullRequest.reviewDecision === "review_required");
|
|
5911
|
+
}
|
|
5819
5912
|
function shouldPreserveBlockedExternalPullRequestWait(params) {
|
|
5820
|
-
return params.currentStatus === "blocked" && params.linkedPullRequests.length > 0 && params.linkedPullRequests.every(
|
|
5913
|
+
return params.currentStatus === "blocked" && params.linkedPullRequests.length > 0 && params.linkedPullRequests.every(
|
|
5914
|
+
(pullRequest) => isGitHubPullRequestPendingExternalWaitForSync(pullRequest) || isGitHubPullRequestBlockedMaintainerApprovalWaitForSync(pullRequest)
|
|
5915
|
+
);
|
|
5821
5916
|
}
|
|
5822
5917
|
function isGitHubPullRequestTransientUnknownMergeabilityWait(pullRequest) {
|
|
5823
5918
|
return pullRequest.ciState === "green" && !pullRequest.hasUnresolvedReviewThreads && pullRequest.mergeability !== "conflicting" && pullRequest.mergeStateStatus === "unknown";
|
|
@@ -10003,6 +10098,187 @@ function buildToolSuccessResult(content, data) {
|
|
|
10003
10098
|
data
|
|
10004
10099
|
};
|
|
10005
10100
|
}
|
|
10101
|
+
function normalizePullRequestAssetMimeType(value) {
|
|
10102
|
+
if (typeof value !== "string") {
|
|
10103
|
+
return void 0;
|
|
10104
|
+
}
|
|
10105
|
+
const normalized = value.trim().toLowerCase();
|
|
10106
|
+
if (!/^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*(?:;\s*[a-z0-9!#$&^_.+-]+=[a-z0-9!#$&^_.+-]+)*$/.test(normalized)) {
|
|
10107
|
+
return void 0;
|
|
10108
|
+
}
|
|
10109
|
+
return normalized.split(";", 1)[0];
|
|
10110
|
+
}
|
|
10111
|
+
function getPullRequestAssetMimeTypeFromFileName(fileName) {
|
|
10112
|
+
const extensionMatch = fileName.trim().toLowerCase().match(/\.([a-z0-9]+)$/);
|
|
10113
|
+
if (!extensionMatch) {
|
|
10114
|
+
return void 0;
|
|
10115
|
+
}
|
|
10116
|
+
return PULL_REQUEST_ASSET_MIME_TYPE_BY_EXTENSION[extensionMatch[1]];
|
|
10117
|
+
}
|
|
10118
|
+
function getPullRequestAssetExtension(fileName) {
|
|
10119
|
+
const extensionMatch = fileName.trim().toLowerCase().match(/\.([a-z0-9]+)$/);
|
|
10120
|
+
return extensionMatch?.[1];
|
|
10121
|
+
}
|
|
10122
|
+
function sanitizePullRequestAssetFileName(fileNameInput, mimeType) {
|
|
10123
|
+
const inferredExtension = PULL_REQUEST_ASSET_EXTENSION_BY_MIME_TYPE[mimeType] ?? "bin";
|
|
10124
|
+
const fallbackBaseName = `asset.${inferredExtension}`;
|
|
10125
|
+
const rawFileName = normalizeOptionalString2(fileNameInput) ?? fallbackBaseName;
|
|
10126
|
+
const lastSegment = rawFileName.split(/[\\/]+/).filter(Boolean).at(-1) ?? fallbackBaseName;
|
|
10127
|
+
const extension = getPullRequestAssetExtension(lastSegment) ?? inferredExtension;
|
|
10128
|
+
const withoutExtension = lastSegment.replace(/\.[A-Za-z0-9]+$/, "");
|
|
10129
|
+
const sanitizedBaseName = withoutExtension.normalize("NFKD").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/[-_.]+$/g, "").replace(/^[-_.]+/g, "").slice(0, 80) || "asset";
|
|
10130
|
+
return `${sanitizedBaseName}.${extension}`;
|
|
10131
|
+
}
|
|
10132
|
+
function sanitizePullRequestAssetLabel(value, fallbackFileName) {
|
|
10133
|
+
const normalized = normalizeOptionalString2(value);
|
|
10134
|
+
return (normalized ?? fallbackFileName.replace(/[-_.]+/g, " ")).slice(0, 200);
|
|
10135
|
+
}
|
|
10136
|
+
function sanitizePullRequestAssetArtifactBranch(value, pullRequestNumber) {
|
|
10137
|
+
const normalized = normalizeOptionalString2(value);
|
|
10138
|
+
const candidate = normalized ?? `paperclip-artifacts-pr-${pullRequestNumber}`;
|
|
10139
|
+
const sanitized = candidate.replace(/[^A-Za-z0-9._/-]+/g, "-").replace(/\.\.+/g, ".").replace(/\/{2,}/g, "/").replace(/^[-/.]+|[-/.]+$/g, "");
|
|
10140
|
+
if (!sanitized || sanitized.includes("..") || sanitized.startsWith("/") || sanitized.endsWith(".lock")) {
|
|
10141
|
+
throw new Error("artifactBranch must be a safe Git branch name.");
|
|
10142
|
+
}
|
|
10143
|
+
return sanitized;
|
|
10144
|
+
}
|
|
10145
|
+
function decodePullRequestAssetContent(payload) {
|
|
10146
|
+
const dataUrl = normalizeOptionalString2(payload.dataUrl);
|
|
10147
|
+
let contentBase64 = normalizeOptionalString2(payload.contentBase64);
|
|
10148
|
+
let mimeType = normalizePullRequestAssetMimeType(payload.mimeType);
|
|
10149
|
+
if (dataUrl) {
|
|
10150
|
+
const match = dataUrl.match(/^data:([^;,]+(?:;[^,]+)?);base64,(.+)$/is);
|
|
10151
|
+
if (!match) {
|
|
10152
|
+
throw new Error("dataUrl must be a base64 data URL.");
|
|
10153
|
+
}
|
|
10154
|
+
const dataUrlMimeType = normalizePullRequestAssetMimeType(match[1]);
|
|
10155
|
+
if (!dataUrlMimeType) {
|
|
10156
|
+
throw new Error("dataUrl MIME type must be a valid MIME type such as image/png or application/pdf.");
|
|
10157
|
+
}
|
|
10158
|
+
mimeType = dataUrlMimeType;
|
|
10159
|
+
contentBase64 = match[2].replace(/\s+/g, "");
|
|
10160
|
+
}
|
|
10161
|
+
if (!contentBase64) {
|
|
10162
|
+
throw new Error("contentBase64 or dataUrl is required.");
|
|
10163
|
+
}
|
|
10164
|
+
mimeType = mimeType ?? getPullRequestAssetMimeTypeFromFileName(normalizeOptionalString2(payload.fileName) ?? "") ?? DEFAULT_PULL_REQUEST_ASSET_MIME_TYPE;
|
|
10165
|
+
const normalizedBase64 = contentBase64.replace(/\s+/g, "");
|
|
10166
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(normalizedBase64) || normalizedBase64.length % 4 === 1) {
|
|
10167
|
+
throw new Error("Asset content must be valid base64.");
|
|
10168
|
+
}
|
|
10169
|
+
const bytes = Buffer.from(normalizedBase64, "base64");
|
|
10170
|
+
if (bytes.length === 0) {
|
|
10171
|
+
throw new Error("Asset content must not be empty.");
|
|
10172
|
+
}
|
|
10173
|
+
if (bytes.length > MAX_PULL_REQUEST_ASSET_BYTES) {
|
|
10174
|
+
throw new Error(`Asset content exceeds the ${MAX_PULL_REQUEST_ASSET_BYTES} byte limit.`);
|
|
10175
|
+
}
|
|
10176
|
+
return {
|
|
10177
|
+
bytes,
|
|
10178
|
+
mimeType
|
|
10179
|
+
};
|
|
10180
|
+
}
|
|
10181
|
+
function buildPullRequestAssetMarkdown(label, rawUrl, mimeType) {
|
|
10182
|
+
const normalizedLabel = label.replace(/[\]\n\r]/g, " ").trim();
|
|
10183
|
+
if (mimeType.startsWith("image/")) {
|
|
10184
|
+
return ``;
|
|
10185
|
+
}
|
|
10186
|
+
return `[${normalizedLabel}](${rawUrl})`;
|
|
10187
|
+
}
|
|
10188
|
+
async function uploadPullRequestAssetArtifact(params) {
|
|
10189
|
+
const { bytes, mimeType } = decodePullRequestAssetContent(params.payload);
|
|
10190
|
+
const fileName = sanitizePullRequestAssetFileName(params.payload.fileName, mimeType);
|
|
10191
|
+
const label = sanitizePullRequestAssetLabel(params.payload.label ?? params.payload.alt, fileName);
|
|
10192
|
+
const caption = normalizeOptionalString2(params.payload.caption);
|
|
10193
|
+
const artifactBranch = sanitizePullRequestAssetArtifactBranch(params.payload.artifactBranch, params.pullRequestNumber);
|
|
10194
|
+
const pullRequestResponse = await params.octokit.rest.pulls.get({
|
|
10195
|
+
owner: params.repository.owner,
|
|
10196
|
+
repo: params.repository.repo,
|
|
10197
|
+
pull_number: params.pullRequestNumber,
|
|
10198
|
+
headers: {
|
|
10199
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
10200
|
+
}
|
|
10201
|
+
});
|
|
10202
|
+
const headSha = pullRequestResponse.data.head.sha;
|
|
10203
|
+
const shortHeadSha = headSha.slice(0, 12);
|
|
10204
|
+
const contentPath = `assets/pr-${params.pullRequestNumber}/${shortHeadSha}/${fileName}`;
|
|
10205
|
+
let branchSha;
|
|
10206
|
+
try {
|
|
10207
|
+
const branchRefResponse = await params.octokit.rest.git.getRef({
|
|
10208
|
+
owner: params.repository.owner,
|
|
10209
|
+
repo: params.repository.repo,
|
|
10210
|
+
ref: `heads/${artifactBranch}`,
|
|
10211
|
+
headers: {
|
|
10212
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
10213
|
+
}
|
|
10214
|
+
});
|
|
10215
|
+
branchSha = branchRefResponse.data.object.sha;
|
|
10216
|
+
} catch (error) {
|
|
10217
|
+
if (getErrorStatus(error) !== 404) {
|
|
10218
|
+
throw error;
|
|
10219
|
+
}
|
|
10220
|
+
}
|
|
10221
|
+
if (!branchSha) {
|
|
10222
|
+
await params.octokit.rest.git.createRef({
|
|
10223
|
+
owner: params.repository.owner,
|
|
10224
|
+
repo: params.repository.repo,
|
|
10225
|
+
ref: `refs/heads/${artifactBranch}`,
|
|
10226
|
+
sha: pullRequestResponse.data.base.sha,
|
|
10227
|
+
headers: {
|
|
10228
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
10229
|
+
}
|
|
10230
|
+
});
|
|
10231
|
+
}
|
|
10232
|
+
let existingFileSha;
|
|
10233
|
+
try {
|
|
10234
|
+
const existingContentResponse = await params.octokit.rest.repos.getContent({
|
|
10235
|
+
owner: params.repository.owner,
|
|
10236
|
+
repo: params.repository.repo,
|
|
10237
|
+
path: contentPath,
|
|
10238
|
+
ref: artifactBranch,
|
|
10239
|
+
headers: {
|
|
10240
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
10241
|
+
}
|
|
10242
|
+
});
|
|
10243
|
+
if (!Array.isArray(existingContentResponse.data) && existingContentResponse.data.type === "file") {
|
|
10244
|
+
existingFileSha = existingContentResponse.data.sha;
|
|
10245
|
+
}
|
|
10246
|
+
} catch (error) {
|
|
10247
|
+
if (getErrorStatus(error) !== 404) {
|
|
10248
|
+
throw error;
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10251
|
+
const updateResponse = await params.octokit.rest.repos.createOrUpdateFileContents({
|
|
10252
|
+
owner: params.repository.owner,
|
|
10253
|
+
repo: params.repository.repo,
|
|
10254
|
+
path: contentPath,
|
|
10255
|
+
message: `Add asset for PR #${params.pullRequestNumber}`,
|
|
10256
|
+
content: bytes.toString("base64"),
|
|
10257
|
+
branch: artifactBranch,
|
|
10258
|
+
...existingFileSha ? { sha: existingFileSha } : {},
|
|
10259
|
+
headers: {
|
|
10260
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
10261
|
+
}
|
|
10262
|
+
});
|
|
10263
|
+
const commitSha = updateResponse.data.commit.sha;
|
|
10264
|
+
const rawUrl = `https://raw.githubusercontent.com/${params.repository.owner}/${params.repository.repo}/${commitSha}/${contentPath}`;
|
|
10265
|
+
const markdown = buildPullRequestAssetMarkdown(label, rawUrl, mimeType);
|
|
10266
|
+
return {
|
|
10267
|
+
repository: params.repository.url,
|
|
10268
|
+
pullRequestNumber: params.pullRequestNumber,
|
|
10269
|
+
artifactBranch,
|
|
10270
|
+
path: contentPath,
|
|
10271
|
+
fileName,
|
|
10272
|
+
mimeType,
|
|
10273
|
+
sizeBytes: bytes.length,
|
|
10274
|
+
commitSha,
|
|
10275
|
+
rawUrl,
|
|
10276
|
+
markdown,
|
|
10277
|
+
label,
|
|
10278
|
+
...mimeType.startsWith("image/") ? { alt: label } : {},
|
|
10279
|
+
...caption ? { caption } : {}
|
|
10280
|
+
};
|
|
10281
|
+
}
|
|
10006
10282
|
function buildToolErrorResult(error) {
|
|
10007
10283
|
const rateLimitPause = getGitHubRateLimitPauseDetails(error);
|
|
10008
10284
|
if (rateLimitPause) {
|
|
@@ -10183,12 +10459,15 @@ async function handleCompanyMetricApiRoute(ctx, input) {
|
|
|
10183
10459
|
}
|
|
10184
10460
|
};
|
|
10185
10461
|
}
|
|
10186
|
-
function
|
|
10462
|
+
function parsePluginApiRouteJsonObjectBody(input, routeLabel) {
|
|
10187
10463
|
if (!input.body || typeof input.body !== "object" || Array.isArray(input.body)) {
|
|
10188
|
-
throw new Error(
|
|
10464
|
+
throw new Error(`${routeLabel} body must be a JSON object.`);
|
|
10189
10465
|
}
|
|
10190
10466
|
return input.body;
|
|
10191
10467
|
}
|
|
10468
|
+
function parseIssueLinkApiRouteBody(input) {
|
|
10469
|
+
return parsePluginApiRouteJsonObjectBody(input, "Issue link route");
|
|
10470
|
+
}
|
|
10192
10471
|
function normalizeIssueLinkApiRouteKind(payload) {
|
|
10193
10472
|
const explicitKind = normalizeIssueGitHubLinkKind(payload.kind);
|
|
10194
10473
|
if (explicitKind) {
|
|
@@ -10203,6 +10482,47 @@ function normalizeIssueLinkApiRouteKind(payload) {
|
|
|
10203
10482
|
}
|
|
10204
10483
|
return null;
|
|
10205
10484
|
}
|
|
10485
|
+
async function handlePullRequestAssetApiRoute(ctx, input) {
|
|
10486
|
+
if (input.actor.actorType !== "agent") {
|
|
10487
|
+
throw new Error("Pull request assets must be uploaded by an authenticated Paperclip agent.");
|
|
10488
|
+
}
|
|
10489
|
+
const payload = parsePluginApiRouteJsonObjectBody(input, "Pull request asset route");
|
|
10490
|
+
const rawPullRequestUrl = normalizeOptionalString2(payload.pullRequestUrl);
|
|
10491
|
+
const pullRequestUrl = normalizeGitHubPullRequestHtmlUrl(rawPullRequestUrl);
|
|
10492
|
+
if (rawPullRequestUrl && !pullRequestUrl) {
|
|
10493
|
+
throw new Error("pullRequestUrl must be a valid GitHub pull request URL.");
|
|
10494
|
+
}
|
|
10495
|
+
const parsedPullRequestUrl = pullRequestUrl ? parseGitHubPullRequestHtmlUrl(pullRequestUrl) : void 0;
|
|
10496
|
+
const repositoryInput = normalizeOptionalString2(payload.repository) ?? parsedPullRequestUrl?.repositoryUrl;
|
|
10497
|
+
if (!repositoryInput) {
|
|
10498
|
+
throw new Error("repository is required unless pullRequestUrl is provided.");
|
|
10499
|
+
}
|
|
10500
|
+
const repository = requireRepositoryReference(repositoryInput);
|
|
10501
|
+
const pullRequestNumber = normalizeToolPositiveInteger(payload.pullRequestNumber) ?? parsedPullRequestUrl?.pullRequestNumber;
|
|
10502
|
+
if (!pullRequestNumber) {
|
|
10503
|
+
throw new Error("pullRequestNumber is required unless pullRequestUrl is provided.");
|
|
10504
|
+
}
|
|
10505
|
+
if (parsedPullRequestUrl && !areRepositoriesEqual(repository, requireRepositoryReference(parsedPullRequestUrl.repositoryUrl))) {
|
|
10506
|
+
throw new Error("repository must match pullRequestUrl.");
|
|
10507
|
+
}
|
|
10508
|
+
const octokit = await createGitHubToolOctokit(ctx, input.companyId, {
|
|
10509
|
+
toolName: PULL_REQUEST_ASSET_API_ROUTE_KEY,
|
|
10510
|
+
repositoryUrl: repository.url
|
|
10511
|
+
});
|
|
10512
|
+
const asset = await uploadPullRequestAssetArtifact({
|
|
10513
|
+
octokit,
|
|
10514
|
+
repository,
|
|
10515
|
+
pullRequestNumber,
|
|
10516
|
+
payload
|
|
10517
|
+
});
|
|
10518
|
+
return {
|
|
10519
|
+
status: 201,
|
|
10520
|
+
body: {
|
|
10521
|
+
status: "uploaded",
|
|
10522
|
+
asset
|
|
10523
|
+
}
|
|
10524
|
+
};
|
|
10525
|
+
}
|
|
10206
10526
|
async function handleIssueLinkApiRoute(ctx, input) {
|
|
10207
10527
|
if (input.actor.actorType !== "agent") {
|
|
10208
10528
|
throw new Error("GitHub issue links must be recorded by an authenticated Paperclip agent.");
|
|
@@ -15085,6 +15405,27 @@ function registerGitHubAgentTools(ctx) {
|
|
|
15085
15405
|
);
|
|
15086
15406
|
})
|
|
15087
15407
|
);
|
|
15408
|
+
ctx.tools.register(
|
|
15409
|
+
"upload_pull_request_asset",
|
|
15410
|
+
getGitHubAgentToolDeclaration("upload_pull_request_asset"),
|
|
15411
|
+
async (params, runCtx) => executeGitHubTool(async () => {
|
|
15412
|
+
const input = getToolInputRecord(params);
|
|
15413
|
+
const target = await resolveGitHubPullRequestToolTarget(ctx, runCtx, input);
|
|
15414
|
+
const octokit = await createAgentToolOctokit(runCtx, "upload_pull_request_asset", target.repository);
|
|
15415
|
+
const asset = await uploadPullRequestAssetArtifact({
|
|
15416
|
+
octokit,
|
|
15417
|
+
repository: target.repository,
|
|
15418
|
+
pullRequestNumber: target.pullRequestNumber,
|
|
15419
|
+
payload: input
|
|
15420
|
+
});
|
|
15421
|
+
return buildToolSuccessResult(
|
|
15422
|
+
`Uploaded asset ${asset.fileName} for pull request #${target.pullRequestNumber}.`,
|
|
15423
|
+
{
|
|
15424
|
+
asset
|
|
15425
|
+
}
|
|
15426
|
+
);
|
|
15427
|
+
})
|
|
15428
|
+
);
|
|
15088
15429
|
ctx.tools.register(
|
|
15089
15430
|
"link_github_item",
|
|
15090
15431
|
getGitHubAgentToolDeclaration("link_github_item"),
|
|
@@ -15139,6 +15480,7 @@ var __testing = {
|
|
|
15139
15480
|
formatPaperclipApiFetchErrorMessage,
|
|
15140
15481
|
hasUnresolvedPaperclipIssueBlocker,
|
|
15141
15482
|
isHealthyMaintainerWaitTransition,
|
|
15483
|
+
resolvePaperclipPullRequestIssueStatus,
|
|
15142
15484
|
resolveSyncTransitionAssignee
|
|
15143
15485
|
};
|
|
15144
15486
|
var plugin = definePlugin({
|
|
@@ -15559,6 +15901,9 @@ var plugin = definePlugin({
|
|
|
15559
15901
|
if (input.routeKey === ISSUE_LINK_API_ROUTE_KEY) {
|
|
15560
15902
|
return handleIssueLinkApiRoute(pluginRuntimeContext, input);
|
|
15561
15903
|
}
|
|
15904
|
+
if (input.routeKey === PULL_REQUEST_ASSET_API_ROUTE_KEY) {
|
|
15905
|
+
return handlePullRequestAssetApiRoute(pluginRuntimeContext, input);
|
|
15906
|
+
}
|
|
15562
15907
|
return {
|
|
15563
15908
|
status: 404,
|
|
15564
15909
|
body: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperclip-github-plugin",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.11",
|
|
4
4
|
"description": "Paperclip plugin for synchronizing GitHub issues into Paperclip projects.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@octokit/rest": "^22.0.1",
|
|
44
44
|
"@paperclipai/plugin-sdk": "^2026.428.0",
|
|
45
|
-
"react": "^19.2.
|
|
45
|
+
"react": "^19.2.6",
|
|
46
46
|
"react-markdown": "^10.1.0",
|
|
47
47
|
"rehype-raw": "^7.0.0",
|
|
48
48
|
"rehype-sanitize": "^6.0.0",
|
|
49
49
|
"remark-gfm": "^4.0.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@types/node": "24.12.
|
|
52
|
+
"@types/node": "24.12.3",
|
|
53
53
|
"@types/react": "19.2.14",
|
|
54
54
|
"esbuild": "0.28.0",
|
|
55
55
|
"playwright": "1.59.1",
|