paperclip-github-plugin 0.7.2 → 0.7.4

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 CHANGED
@@ -35,7 +35,7 @@ The plugin adds a full in-host workflow instead of a one-off import script:
35
35
  - saved sync diagnostics that let operators inspect the latest per-issue failures, raw errors, and suggested next steps
36
36
  - a project sidebar item that opens a live project-scoped Pull Requests page for the mapped repository and can show the open PR count through a lightweight badge read
37
37
  - manual sync actions from global, project, and issue surfaces
38
- - a GitHub detail tab on synced Paperclip issues that stays hidden for Paperclip issues with no linked GitHub issue and includes GitHub-marked action buttons plus the GitHub issue creator with avatar
38
+ - a GitHub detail tab on synced Paperclip issues that includes GitHub-marked action buttons plus the GitHub issue creator with avatar, and lets operators manually link an unlinked Paperclip issue to a GitHub issue or pull request
39
39
  - GitHub link annotations on sync-generated status transition comments when the host supports comment annotations
40
40
 
41
41
  ## How it works
@@ -68,7 +68,7 @@ The plugin does more than mirror issue text. It looks at linked pull requests, m
68
68
 
69
69
  GitHub Sync exposes a dedicated KPI dashboard widget alongside the operational sync widget. During full company syncs, the worker snapshots the current open GitHub backlog and records when already-imported GitHub issues move from open to closed. The KPI widget turns that worker-owned state into backlog, issue-closure, and Paperclip PR-creation cards with recent history and comparisons against older periods.
70
70
 
71
- Because GitHub alone cannot tell which pull requests came from a Paperclip company, the plugin uses explicit Paperclip attribution for delivery activity. `create_pull_request` automatically records a Paperclip-created PR event, and agents that use `gh` or another non-plugin GitHub client can post pull-request-created events to the plugin API route so the KPI history stays specific to Paperclip work.
71
+ Because GitHub alone cannot tell which pull requests came from a Paperclip company, the plugin uses explicit Paperclip attribution for delivery activity. `create_pull_request` automatically records a Paperclip-created PR event, and agents that use `gh` or another non-plugin GitHub client can post pull-request-created events to the plugin API route so the KPI history stays specific to Paperclip work. When either path includes the Paperclip issue id, GitHub Sync also records the pull request link so later sync runs can move that issue based on PR CI, merge state, and review activity.
72
72
 
73
73
  That API route path matters on authenticated Paperclip deployments today because a current host bug blocks agents from calling plugin tools unless the instance runs in `local_trusted` mode. Those agents can still use `gh` with the propagated `GITHUB_TOKEN`, then call the agent-authenticated plugin API route from the shell after they create a PR. The Paperclip host authenticates `Authorization: Bearer <PAPERCLIP_API_KEY>`, scopes the request to the calling agent's company, and rejects anonymous or non-agent calls before dispatching to the worker.
74
74
 
@@ -78,10 +78,16 @@ Each mapped project can expose a **Pull Requests** entry in the sidebar that ope
78
78
 
79
79
  Paperclip issue linkage on the queue prefers the GitHub issue that the pull request closes, so imported GitHub issues and delivery work stay connected in the same project view. If a pull request has no closing-issue-backed link yet, the queue falls back to the Paperclip issue created directly from that pull request and updates the table immediately when that create action returns.
80
80
 
81
- Those pull-request-created Paperclip issues also stay in the scheduled/manual sync loop even when the pull request does not close a GitHub issue. GitHub Sync checks their CI, merge state, and review threads so new failures or unresolved feedback move the Paperclip issue back into active work.
81
+ Those pull-request-created Paperclip issues also stay in the scheduled/manual sync loop even when the pull request does not close a GitHub issue. GitHub Sync checks their CI, merge state, and review threads so new failures or unresolved feedback move the Paperclip issue back into active work. The same durable PR link is written when an agent creates a PR through the plugin tool with `paperclipIssueId`, when an authenticated agent records a `gh`-created PR through the agent API route with `paperclipIssueId`, or when an operator manually links an unlinked issue from the issue page.
82
82
 
83
83
  The issue detail panel and sync-created comment annotations also preserve cross-repository linked pull requests, showing those PRs with their real repository path so operators land in the right place on GitHub.
84
84
 
85
+ ### Manual GitHub links
86
+
87
+ If a Paperclip issue was created locally or by an agent workflow before GitHub Sync saw the matching GitHub item, the issue detail surface shows a **Link GitHub item** action. The modal accepts either a GitHub issue number or full issue URL, or a pull request number or full pull request URL. Number-only entries use the issue's mapped Paperclip project repository; full URLs can point at any repository mapped to that project.
88
+
89
+ Manual GitHub issue links are added to the same import registry and issue-link entity used by normal sync, so future syncs update the Paperclip issue from the GitHub issue. Manual pull request links are added to the PR-link entity used by the project Pull Requests page, so future syncs monitor PR status even when there is no closing GitHub issue.
90
+
85
91
  ### Agent workflows built in
86
92
 
87
93
  Paperclip agents can search GitHub for duplicates, read and update issues, post comments, create pull requests, inspect changed files and CI, reply to review threads, resolve or unresolve threads, request reviewers, search org-level GitHub Projects, and associate pull requests with those projects without leaving the Paperclip plugin surface.
@@ -219,6 +225,7 @@ Supported payload fields:
219
225
  - `repository` optional: `owner/repo` or `https://github.com/owner/repo`
220
226
  - `pullRequestNumber` optional
221
227
  - `pullRequestUrl` optional
228
+ - `paperclipIssueId` optional: the Paperclip issue id that should be linked to the pull request for future PR-status sync
222
229
  - `occurredAt` optional ISO timestamp
223
230
  - `eventKey` optional custom dedupe key
224
231
  - `count` optional positive integer
@@ -232,7 +239,7 @@ The Paperclip host validates that bearer token and passes the authenticated agen
232
239
  Example:
233
240
 
234
241
  ```bash
235
- payload='{"metric":"pull_request_created","repository":"paperclipai/example-repo","pullRequestNumber":21}'
242
+ payload='{"metric":"pull_request_created","repository":"paperclipai/example-repo","pullRequestNumber":21,"paperclipIssueId":"iss_123"}'
236
243
 
237
244
  curl -X POST "${PAPERCLIP_API_URL%/}/api/plugins/paperclip-github-plugin/api/company-metrics/events" \
238
245
  -H "content-type: application/json" \
@@ -240,7 +247,7 @@ curl -X POST "${PAPERCLIP_API_URL%/}/api/plugins/paperclip-github-plugin/api/com
240
247
  -d "${payload}"
241
248
  ```
242
249
 
243
- The worker deduplicates repeated PR events by preferring the pull request URL, then `repository + pullRequestNumber`, before falling back to the explicit `eventKey`.
250
+ The worker deduplicates repeated PR events by preferring the pull request URL, then `repository + pullRequestNumber`, before falling back to the explicit `eventKey`. When `paperclipIssueId` is present, the worker verifies the live pull request and persists the same PR-link metadata used by scheduled/manual status syncs.
244
251
 
245
252
  Current host caveat: on authenticated Paperclip deployments, the Paperclip host currently guards `GET /api/plugins/tools` and `POST /api/plugins/tools/execute` with board authentication before dispatching to any plugin worker. If an agent run does not have board access for the target company, GitHub Sync tool discovery and execution fail with `403 {"error":"Board access required"}` before this plugin's worker code runs.
246
253
 
package/dist/manifest.js CHANGED
@@ -246,6 +246,10 @@ var GITHUB_AGENT_TOOLS = [
246
246
  required: ["head", "base", "title"],
247
247
  properties: {
248
248
  repository: repositoryProperty,
249
+ paperclipIssueId: {
250
+ type: "string",
251
+ description: "Optional Paperclip issue id to link with the created pull request so GitHub Sync can monitor PR status for that issue."
252
+ },
249
253
  head: {
250
254
  type: "string",
251
255
  description: "Head branch name or owner:branch."
@@ -516,7 +520,7 @@ var COMPANY_METRIC_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_ID}/a
516
520
  var require2 = createRequire(import.meta.url);
517
521
  var packageJson = require2("../package.json");
518
522
  var SCHEDULE_TICK_CRON = "* * * * *";
519
- var MANIFEST_VERSION = "0.7.2"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
523
+ var MANIFEST_VERSION = "0.7.4"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
520
524
  var manifest = {
521
525
  id: GITHUB_SYNC_PLUGIN_ID,
522
526
  apiVersion: 1,
package/dist/ui/index.js CHANGED
@@ -26778,6 +26778,113 @@ var EXTENSION_SURFACE_STYLES = `
26778
26778
  margin: 0;
26779
26779
  }
26780
26780
 
26781
+ .ghsync__field {
26782
+ display: grid;
26783
+ gap: 8px;
26784
+ }
26785
+
26786
+ .ghsync__field label {
26787
+ color: var(--ghsync-title);
26788
+ font-size: 12px;
26789
+ font-weight: 600;
26790
+ }
26791
+
26792
+ .ghsync__input {
26793
+ width: 100%;
26794
+ min-height: 40px;
26795
+ border-radius: 8px;
26796
+ border: 1px solid var(--ghsync-input-border);
26797
+ background: var(--ghsync-input-bg);
26798
+ color: var(--ghsync-input-text);
26799
+ padding: 0 12px;
26800
+ outline: none;
26801
+ }
26802
+
26803
+ .ghsync__input::placeholder {
26804
+ color: var(--ghsync-muted);
26805
+ }
26806
+
26807
+ .ghsync__input:focus {
26808
+ border-color: var(--ghsync-border);
26809
+ }
26810
+
26811
+ .ghsync-link-modal-backdrop {
26812
+ position: fixed;
26813
+ inset: 0;
26814
+ z-index: 50;
26815
+ display: flex;
26816
+ align-items: center;
26817
+ justify-content: center;
26818
+ padding: 24px;
26819
+ background: rgba(10, 10, 12, 0.48);
26820
+ backdrop-filter: blur(10px);
26821
+ }
26822
+
26823
+ .ghsync-link-modal {
26824
+ width: min(520px, 100%);
26825
+ display: grid;
26826
+ gap: 16px;
26827
+ padding: 18px;
26828
+ border-radius: 8px;
26829
+ border: 1px solid var(--ghsync-border);
26830
+ background: var(--ghsync-surface);
26831
+ color: var(--ghsync-text);
26832
+ box-shadow: 0 28px 80px rgba(2, 6, 23, 0.34);
26833
+ }
26834
+
26835
+ .ghsync-prs-modal__header {
26836
+ display: grid;
26837
+ gap: 6px;
26838
+ }
26839
+
26840
+ .ghsync-prs-modal__header h3 {
26841
+ margin: 0;
26842
+ color: var(--ghsync-title);
26843
+ font-size: 18px;
26844
+ }
26845
+
26846
+ .ghsync-prs-modal__header p {
26847
+ margin: 0;
26848
+ color: var(--ghsync-muted);
26849
+ font-size: 12px;
26850
+ line-height: 1.5;
26851
+ }
26852
+
26853
+ .ghsync-prs-modal__actions {
26854
+ display: flex;
26855
+ justify-content: flex-end;
26856
+ gap: 8px;
26857
+ flex-wrap: wrap;
26858
+ }
26859
+
26860
+ .ghsync-link-kind {
26861
+ display: grid;
26862
+ grid-template-columns: repeat(2, minmax(0, 1fr));
26863
+ gap: 6px;
26864
+ padding: 4px;
26865
+ border-radius: 8px;
26866
+ border: 1px solid var(--ghsync-border-soft);
26867
+ background: var(--ghsync-surfaceAlt);
26868
+ }
26869
+
26870
+ .ghsync-link-kind__button {
26871
+ min-height: 34px;
26872
+ border: 0;
26873
+ border-radius: 6px;
26874
+ background: transparent;
26875
+ color: var(--ghsync-muted);
26876
+ font: inherit;
26877
+ font-size: 13px;
26878
+ font-weight: 600;
26879
+ cursor: pointer;
26880
+ }
26881
+
26882
+ .ghsync-link-kind__button--active {
26883
+ background: var(--ghsync-surface);
26884
+ color: var(--ghsync-title);
26885
+ box-shadow: inset 0 0 0 1px var(--ghsync-border);
26886
+ }
26887
+
26781
26888
  ${SHARED_LOADING_STYLES}
26782
26889
  `;
26783
26890
  function createEmptyMapping(index2) {
@@ -34523,7 +34630,8 @@ function GitHubSyncIssueDetailTabContent(props) {
34523
34630
  loadingIssueId: props.loadingIssueId,
34524
34631
  detailsLoading: details.loading,
34525
34632
  detailsError: Boolean(details.error),
34526
- issueDetails
34633
+ issueDetails,
34634
+ canLinkManually: Boolean(props.companyId && props.issueId)
34527
34635
  });
34528
34636
  const issueSyncButton = useGitHubSyncButtonController({
34529
34637
  companyId: props.companyId,
@@ -34532,6 +34640,12 @@ function GitHubSyncIssueDetailTabContent(props) {
34532
34640
  resolvedIssueId: props.issueId,
34533
34641
  forceVisible: true
34534
34642
  });
34643
+ const linkGitHubItem = usePluginAction("issue.linkGitHubItem");
34644
+ const toast = usePluginToast();
34645
+ const [manualLinkOpen, setManualLinkOpen] = useState2(false);
34646
+ const [manualLinkKind, setManualLinkKind] = useState2("issue");
34647
+ const [manualLinkReference, setManualLinkReference] = useState2("");
34648
+ const [manualLinkPending, setManualLinkPending] = useState2(false);
34535
34649
  useEffect2(() => {
34536
34650
  if (!props.companyId || !props.issueId) {
34537
34651
  return;
@@ -34546,18 +34660,77 @@ function GitHubSyncIssueDetailTabContent(props) {
34546
34660
  return null;
34547
34661
  }
34548
34662
  const linkedPullRequests = issueDetails ? getLinkedPullRequestsForIssueDetails(issueDetails) : [];
34663
+ const issueDetailsKind = issueDetails?.kind ?? "issue";
34664
+ const githubUrl = issueDetailsKind === "pull_request" ? issueDetails?.githubPullRequestUrl : issueDetails?.githubIssueUrl;
34665
+ const githubStateLabel = issueDetailsKind === "pull_request" ? issueDetails?.githubPullRequestState === "closed" ? "Closed" : "Open" : formatGitHubIssueState(issueDetails?.githubIssueState, issueDetails?.githubIssueStateReason);
34666
+ function closeManualLinkModal() {
34667
+ if (manualLinkPending) {
34668
+ return;
34669
+ }
34670
+ setManualLinkOpen(false);
34671
+ setManualLinkReference("");
34672
+ setManualLinkKind("issue");
34673
+ }
34674
+ async function handleManualLinkSubmit() {
34675
+ if (!props.companyId || !props.issueId || !manualLinkReference.trim()) {
34676
+ return;
34677
+ }
34678
+ setManualLinkPending(true);
34679
+ try {
34680
+ const result = await linkGitHubItem({
34681
+ companyId: props.companyId,
34682
+ issueId: props.issueId,
34683
+ kind: manualLinkKind,
34684
+ reference: manualLinkReference.trim()
34685
+ });
34686
+ setManualLinkOpen(false);
34687
+ setManualLinkReference("");
34688
+ await details.refresh();
34689
+ notifyGitHubSyncPullRequestsChanged();
34690
+ toast({
34691
+ title: "GitHub link saved",
34692
+ body: result.kind === "pull_request" ? `Linked pull request #${result.githubPullRequestNumber ?? manualLinkReference.trim()}.` : `Linked issue #${result.githubIssueNumber ?? manualLinkReference.trim()}.`,
34693
+ tone: "success"
34694
+ });
34695
+ } catch (error) {
34696
+ toast({
34697
+ title: "Unable to link GitHub item",
34698
+ body: getActionErrorMessage(error, "GitHub Sync could not save this link."),
34699
+ tone: "error"
34700
+ });
34701
+ } finally {
34702
+ setManualLinkPending(false);
34703
+ }
34704
+ }
34549
34705
  return /* @__PURE__ */ jsxs2("section", { className: "ghsync-issue-detail", style: props.themeVars, children: [
34550
34706
  /* @__PURE__ */ jsx2("style", { children: EXTENSION_SURFACE_STYLES }),
34551
34707
  detailTabState === "loading" ? /* @__PURE__ */ jsx2("p", { className: "ghsync-extension-empty", children: "Loading GitHub sync details\u2026" }) : null,
34552
34708
  detailTabState === "error" && details.error ? /* @__PURE__ */ jsx2("p", { className: "ghsync-extension-empty", children: details.error.message }) : null,
34709
+ detailTabState === "unlinked" ? /* @__PURE__ */ jsx2("div", { className: "ghsync-issue-detail__intro", children: /* @__PURE__ */ jsxs2("div", { className: "ghsync-extension-heading", children: [
34710
+ /* @__PURE__ */ jsxs2("div", { className: "ghsync-issue-detail__headline", children: [
34711
+ /* @__PURE__ */ jsx2("h4", { children: "GitHub link" }),
34712
+ /* @__PURE__ */ jsx2("p", { children: "No GitHub issue or pull request is linked to this Paperclip issue yet." })
34713
+ ] }),
34714
+ /* @__PURE__ */ jsx2("div", { className: "ghsync-issue-detail__actions", children: /* @__PURE__ */ jsx2(
34715
+ "button",
34716
+ {
34717
+ type: "button",
34718
+ className: getPluginActionClassName({
34719
+ variant: "secondary",
34720
+ size: "sm",
34721
+ extraClassName: "ghsync-extension-link"
34722
+ }),
34723
+ onClick: () => setManualLinkOpen(true),
34724
+ children: /* @__PURE__ */ jsx2(GitHubButtonLabel, { label: "Link GitHub item" })
34725
+ }
34726
+ ) })
34727
+ ] }) }) : null,
34553
34728
  detailTabState === "ready" && issueDetails ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
34554
34729
  /* @__PURE__ */ jsxs2("div", { className: "ghsync-extension-heading", children: [
34555
34730
  /* @__PURE__ */ jsxs2("div", { className: "ghsync-issue-detail__headline", children: [
34556
- /* @__PURE__ */ jsxs2("h4", { children: [
34557
- "Issue #",
34558
- issueDetails.githubIssueNumber
34559
- ] }),
34731
+ /* @__PURE__ */ jsx2("h4", { children: issueDetailsKind === "pull_request" ? `Pull request #${issueDetails.githubPullRequestNumber ?? ""}` : `Issue #${issueDetails.githubIssueNumber ?? ""}` }),
34560
34732
  /* @__PURE__ */ jsx2("p", { children: formatGitHubRepositoryLabel(issueDetails.repositoryUrl) }),
34733
+ issueDetailsKind === "pull_request" && issueDetails.title ? /* @__PURE__ */ jsx2("p", { children: issueDetails.title }) : null,
34561
34734
  issueDetails.creator ? /* @__PURE__ */ jsxs2("div", { className: "ghsync-issue-detail__creator-row", children: [
34562
34735
  /* @__PURE__ */ jsx2("span", { className: "ghsync-issue-detail__creator-label", children: "Creator" }),
34563
34736
  /* @__PURE__ */ jsxs2(
@@ -34599,10 +34772,10 @@ function GitHubSyncIssueDetailTabContent(props) {
34599
34772
  )
34600
34773
  }
34601
34774
  ) : null,
34602
- /* @__PURE__ */ jsx2(
34775
+ githubUrl ? /* @__PURE__ */ jsx2(
34603
34776
  "a",
34604
34777
  {
34605
- href: issueDetails.githubIssueUrl,
34778
+ href: githubUrl,
34606
34779
  target: "_blank",
34607
34780
  rel: "noreferrer",
34608
34781
  className: getPluginActionClassName({
@@ -34612,17 +34785,17 @@ function GitHubSyncIssueDetailTabContent(props) {
34612
34785
  }),
34613
34786
  children: /* @__PURE__ */ jsx2(GitHubButtonLabel, { label: "Open on GitHub" })
34614
34787
  }
34615
- )
34788
+ ) : null
34616
34789
  ] })
34617
34790
  ] }),
34618
34791
  /* @__PURE__ */ jsxs2("div", { className: "ghsync-extension-grid", children: [
34619
34792
  /* @__PURE__ */ jsxs2("div", { className: "ghsync-extension-metric", children: [
34620
34793
  /* @__PURE__ */ jsx2("span", { children: "State" }),
34621
- /* @__PURE__ */ jsx2("strong", { children: formatGitHubIssueState(issueDetails.githubIssueState, issueDetails.githubIssueStateReason) })
34794
+ /* @__PURE__ */ jsx2("strong", { children: githubStateLabel })
34622
34795
  ] }),
34623
34796
  /* @__PURE__ */ jsxs2("div", { className: "ghsync-extension-metric", children: [
34624
- /* @__PURE__ */ jsx2("span", { children: "Comments" }),
34625
- /* @__PURE__ */ jsx2("strong", { children: issueDetails.commentsCount ?? "Unknown" })
34797
+ /* @__PURE__ */ jsx2("span", { children: "Type" }),
34798
+ /* @__PURE__ */ jsx2("strong", { children: issueDetailsKind === "pull_request" ? "Pull request" : "Issue" })
34626
34799
  ] }),
34627
34800
  /* @__PURE__ */ jsxs2("div", { className: "ghsync-extension-metric", children: [
34628
34801
  /* @__PURE__ */ jsx2("span", { children: "Linked PRs" }),
@@ -34656,7 +34829,7 @@ function GitHubSyncIssueDetailTabContent(props) {
34656
34829
  `${pullRequest.repositoryUrl}:${pullRequest.number}`
34657
34830
  )) })
34658
34831
  ] }) : null,
34659
- issueDetails.labels && issueDetails.labels.length > 0 ? /* @__PURE__ */ jsxs2("div", { className: "ghsync-issue-detail__section", children: [
34832
+ issueDetails.kind !== "pull_request" && issueDetails.labels && issueDetails.labels.length > 0 ? /* @__PURE__ */ jsxs2("div", { className: "ghsync-issue-detail__section", children: [
34660
34833
  /* @__PURE__ */ jsx2("div", { className: "ghsync-issue-detail__section-heading", children: "Labels" }),
34661
34834
  /* @__PURE__ */ jsx2("div", { className: "ghsync-extension-labels", children: issueDetails.labels.map((label) => /* @__PURE__ */ jsx2(
34662
34835
  "span",
@@ -34668,8 +34841,84 @@ function GitHubSyncIssueDetailTabContent(props) {
34668
34841
  `${label.name}:${label.color ?? "none"}`
34669
34842
  )) })
34670
34843
  ] }) : null,
34671
- issueDetails.source !== "entity" ? /* @__PURE__ */ jsx2("div", { className: "ghsync-extension-note", children: "GitHub Sync recovered this link from older sync metadata. Run sync once to refresh the creator, GitHub state, labels, and linked PRs in this panel." }) : null
34672
- ] }) : null
34844
+ issueDetails.kind !== "pull_request" && issueDetails.source !== "entity" ? /* @__PURE__ */ jsx2("div", { className: "ghsync-extension-note", children: "GitHub Sync recovered this link from older sync metadata. Run sync once to refresh the creator, GitHub state, labels, and linked PRs in this panel." }) : null
34845
+ ] }) : null,
34846
+ manualLinkOpen ? /* @__PURE__ */ jsx2("div", { className: "ghsync-link-modal-backdrop", onClick: closeManualLinkModal, children: /* @__PURE__ */ jsxs2(
34847
+ "div",
34848
+ {
34849
+ className: "ghsync-link-modal",
34850
+ role: "dialog",
34851
+ "aria-modal": "true",
34852
+ "aria-labelledby": "ghsync-link-modal-title",
34853
+ onClick: (event) => event.stopPropagation(),
34854
+ children: [
34855
+ /* @__PURE__ */ jsxs2("div", { className: "ghsync-prs-modal__header", children: [
34856
+ /* @__PURE__ */ jsx2("h3", { id: "ghsync-link-modal-title", children: "Link GitHub item" }),
34857
+ /* @__PURE__ */ jsx2("p", { children: "Enter a GitHub issue or pull request number from the mapped repository, or paste the full GitHub URL." })
34858
+ ] }),
34859
+ /* @__PURE__ */ jsxs2("div", { className: "ghsync-link-kind", role: "group", "aria-label": "GitHub item type", children: [
34860
+ /* @__PURE__ */ jsx2(
34861
+ "button",
34862
+ {
34863
+ type: "button",
34864
+ className: manualLinkKind === "issue" ? "ghsync-link-kind__button ghsync-link-kind__button--active" : "ghsync-link-kind__button",
34865
+ onClick: () => setManualLinkKind("issue"),
34866
+ disabled: manualLinkPending,
34867
+ children: "Issue"
34868
+ }
34869
+ ),
34870
+ /* @__PURE__ */ jsx2(
34871
+ "button",
34872
+ {
34873
+ type: "button",
34874
+ className: manualLinkKind === "pull_request" ? "ghsync-link-kind__button ghsync-link-kind__button--active" : "ghsync-link-kind__button",
34875
+ onClick: () => setManualLinkKind("pull_request"),
34876
+ disabled: manualLinkPending,
34877
+ children: "Pull request"
34878
+ }
34879
+ )
34880
+ ] }),
34881
+ /* @__PURE__ */ jsxs2("div", { className: "ghsync__field", children: [
34882
+ /* @__PURE__ */ jsx2("label", { htmlFor: "ghsync-link-reference", children: manualLinkKind === "pull_request" ? "Pull request number or URL" : "Issue number or URL" }),
34883
+ /* @__PURE__ */ jsx2(
34884
+ "input",
34885
+ {
34886
+ id: "ghsync-link-reference",
34887
+ className: "ghsync__input",
34888
+ value: manualLinkReference,
34889
+ onChange: (event) => setManualLinkReference(event.currentTarget.value),
34890
+ placeholder: manualLinkKind === "pull_request" ? "89 or https://github.com/owner/repo/pull/89" : "88 or https://github.com/owner/repo/issues/88",
34891
+ disabled: manualLinkPending
34892
+ }
34893
+ )
34894
+ ] }),
34895
+ /* @__PURE__ */ jsxs2("div", { className: "ghsync-prs-modal__actions", children: [
34896
+ /* @__PURE__ */ jsx2(
34897
+ "button",
34898
+ {
34899
+ type: "button",
34900
+ className: getPluginActionClassName({ variant: "secondary", size: "sm" }),
34901
+ onClick: closeManualLinkModal,
34902
+ disabled: manualLinkPending,
34903
+ children: "Cancel"
34904
+ }
34905
+ ),
34906
+ /* @__PURE__ */ jsx2(
34907
+ "button",
34908
+ {
34909
+ type: "button",
34910
+ className: getPluginActionClassName({ variant: "primary", size: "sm" }),
34911
+ onClick: () => {
34912
+ void handleManualLinkSubmit();
34913
+ },
34914
+ disabled: !manualLinkReference.trim() || manualLinkPending,
34915
+ children: /* @__PURE__ */ jsx2(LoadingButtonContent, { busy: manualLinkPending, label: "Link", busyLabel: "Linking" })
34916
+ }
34917
+ )
34918
+ ] })
34919
+ ]
34920
+ }
34921
+ ) }) : null
34673
34922
  ] });
34674
34923
  }
34675
34924
  function resolveGitHubIssueDetailTabState(params) {
@@ -34682,6 +34931,9 @@ function resolveGitHubIssueDetailTabState(params) {
34682
34931
  if (params.detailsError) {
34683
34932
  return "error";
34684
34933
  }
34934
+ if (params.canLinkManually) {
34935
+ return "unlinked";
34936
+ }
34685
34937
  return "hidden";
34686
34938
  }
34687
34939
  function GitHubSyncIssueTaskDetailView() {