paperclip-github-plugin 0.8.7 → 0.8.9

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
@@ -72,6 +72,12 @@ Because GitHub alone cannot tell which pull requests came from a Paperclip compa
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
 
75
+ ### Third-party issue links
76
+
77
+ Sometimes a Paperclip issue is implemented through a GitHub issue or pull request in a repository that is not mapped to any Paperclip project. GitHub Sync can now track those targeted links without enrolling the whole repository in sync. The `link_github_item` agent tool records the durable link on local-trusted Paperclip instances, and authenticated agent runs can post the same link to `/api/plugins/paperclip-github-plugin/api/issue-link` with `PAPERCLIP_API_KEY` when plugin tools are blocked by host board-auth gates.
78
+
79
+ Third-party links are company-scoped and sync only the linked GitHub issue or pull request. Future manual or scheduled company sync runs refresh those external records and update the Paperclip issue status from the same CI, mergeability, review, thread, and issue-state rules used for mapped repositories.
80
+
75
81
  ### Project pull request command center
76
82
 
77
83
  Each mapped project can expose a **Pull Requests** entry in the sidebar that opens a live GitHub queue page for that repository. The sidebar badge uses a lightweight total-count read, while the queue keeps the default view fast by loading only the current 10-row page, uses a repo-wide metrics read for the summary cards, reuses that cached metrics scan to keep filtered views fast by fetching only the visible filtered rows, keeps repo-scoped count, metrics, and per-PR review/check insight caches warm for repeat visits, lets operators explicitly bust those caches with Refresh when they want a live reread, shows total, mergeable, reviewable, and failing cards that filter the table, only treats a pull request as mergeable when it targets the current default branch with green checks, at least one approval, no outstanding change requests, and no unresolved review threads, includes an **Up to date** column that distinguishes current branches, clean update candidates, conflict cases, and unknown freshness when GitHub cannot confirm the comparison, shows the PR target branch with a highlighted default-branch badge, keeps the list sorted by most recently updated first, paginates larger repositories, keeps a compact bottom detail pane with markdown-and-HTML-rendered conversation plus an inline comment composer, supports deterministic **Update branch** actions for clean behind-base pull requests, adds Copilot quick actions that post `@copilot` requests for **Fix CI**, **Rebase**, and **Address review feedback**, requests Copilot through GitHub’s native reviewer flow for **Review**, keeps comment, review, quick approve/request-changes, re-run CI, merge, and close actions available, lets the review modal submit comment-only, approve, or request-changes reviews, hides any pull request action whose required GitHub permission is not verified for the saved token, and opens linked Paperclip issues in a plugin-provided right drawer so operators can stay on the queue page.
@@ -93,6 +99,7 @@ Linked Paperclip issues can also be unlinked from the GitHub detail surface. Unl
93
99
  ### Agent workflows built in
94
100
 
95
101
  Paperclip agents can search GitHub for duplicates, read and update issues, assign issues to the saved token owner, 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.
102
+ They can also link a Paperclip issue to a GitHub issue or pull request in any accessible repository with `link_github_item`, including third-party repositories that are not mapped to a Paperclip project.
96
103
 
97
104
  ## Requirements
98
105
 
@@ -208,6 +215,12 @@ Notes:
208
215
  - The raw token is never persisted back into plugin state or plugin config.
209
216
  - A GitHub token secret saved through the settings UI takes precedence over the local file.
210
217
 
218
+ ### Worker-facing Paperclip API URL
219
+
220
+ GitHub Sync uses direct worker-side Paperclip REST calls for host paths that are not fully covered by the plugin SDK, such as label reconciliation and some issue repair paths. By default, manual setup and sync actions use the current browser origin. If that origin is not reachable from the plugin worker, set **Worker Paperclip API URL** in GitHub Sync settings; the value is saved internally as plugin config `paperclipApiBaseUrl`.
221
+
222
+ For private LAN, Docker, Kubernetes, custom DNS, or self-signed-certificate deployments, set that field to a local route the plugin worker can reach, such as `http://localhost:3100`, and port-forward or route that address to the Paperclip API when needed. Do not rely on exporting a process environment variable named `PAPERCLIP_API_URL` to Paperclip; Paperclip `2026.428.0` does not pass that value through to plugin worker runtime.
223
+
211
224
  ## GitHub agent tools
212
225
 
213
226
  The plugin exposes GitHub workflow tools to Paperclip agents, including:
@@ -259,16 +272,40 @@ Current host caveat: on authenticated Paperclip deployments, the Paperclip host
259
272
 
260
273
  Because the KPI attribution endpoint is a native plugin JSON route rather than a plugin tool, authenticated agent runs can still call it directly with `PAPERCLIP_API_KEY` even while that host bug blocks the GitHub Sync tool surface.
261
274
 
275
+ ### Issue link API route
276
+
277
+ Authenticated agent runs can link the current Paperclip issue to a GitHub issue or pull request by posting to `/api/plugins/paperclip-github-plugin/api/issue-link`. This is useful after creating a PR with `gh` in a repository that is not mapped to a Paperclip project.
278
+
279
+ Supported payload fields:
280
+
281
+ - `paperclipIssueId` required: the Paperclip issue id to link
282
+ - `kind` optional: `issue` or `pull_request`; omitted values are inferred from full GitHub URLs when possible
283
+ - `reference` optional: a GitHub issue or pull request number, or a full GitHub URL
284
+ - `repository` optional: `owner/repo` or `https://github.com/owner/repo`, required for number-only references when the issue project is not mapped to that repository
285
+ - `issueNumber`, `pullRequestNumber`, or `pullRequestUrl` optional alternatives to `reference`
286
+
287
+ Example:
288
+
289
+ ```bash
290
+ payload='{"paperclipIssueId":"iss_123","pullRequestUrl":"https://github.com/third-party/external/pull/77"}'
291
+
292
+ curl -X POST "${PAPERCLIP_API_URL%/}/api/plugins/paperclip-github-plugin/api/issue-link" \
293
+ -H "content-type: application/json" \
294
+ -H "authorization: Bearer ${PAPERCLIP_API_KEY}" \
295
+ -d "${payload}"
296
+ ```
297
+
262
298
  ## Troubleshooting
263
299
 
264
300
  - If an older GitHub Sync build fails upgrade with `requires host version 2026.427.0 or newer, but this server is running 0.0.0`, upgrade to a build that removes the strict manifest host-version gate. The host is reporting a development-version sentinel, so the plugin now relies on declared capabilities and runtime fallbacks instead.
265
- - If setup is reported as incomplete, confirm that a GitHub token has been saved or that `${PAPERCLIP_HOME:-~/.paperclip}/plugins/github-sync/config.json` contains `githubToken`, and make sure at least one mapping has a created Paperclip project.
301
+ - If setup is reported as incomplete, confirm that a GitHub token has been saved or that `${PAPERCLIP_HOME:-~/.paperclip}/plugins/github-sync/config.json` contains `githubToken`, and make sure at least one mapping has a created Paperclip project or at least one Paperclip issue has been linked to GitHub.
266
302
  - If Paperclip says board access is required, open plugin settings inside the affected company and complete the Paperclip board access flow before retrying sync.
267
303
  - If GitHub Sync agent tools fail with `403 {"error":"Board access required"}` on `/api/plugins/tools` or `/api/plugins/tools/execute`, the current Paperclip host rejected the request before the plugin worker ran. Re-run from a board-authenticated session or agent run that has board access to the target company.
268
304
  - If a KPI API route call is rejected, make sure the request includes `Authorization: Bearer ${PAPERCLIP_API_KEY}`, that the token is still valid for the current run, and that any `companyId` in the payload matches the calling agent's company.
269
- - If the worker reaches an authenticated HTML page instead of the Paperclip API JSON responses it expects, connect Paperclip board access for that company or set `PAPERCLIP_API_URL` to a worker-accessible Paperclip API origin.
305
+ - If the worker reaches an authenticated HTML page instead of the Paperclip API JSON responses it expects, connect Paperclip board access for that company or set **Worker Paperclip API URL** in GitHub Sync settings to a worker-accessible Paperclip API origin.
306
+ - If a Paperclip API fetch fails before any HTTP response is returned, the saved diagnostics include the method, URL, primary error, nested cause, and cause code when Node exposes them.
270
307
  - If a sync run finishes with partial failures, open the saved troubleshooting panel in GitHub Sync to inspect the repository, issue number, raw error, and suggested fix for each recorded failure.
271
- - If sync says the Paperclip API URL is not trusted, reopen the plugin from the current Paperclip host so the settings UI can refresh the saved origin, or set `PAPERCLIP_API_URL` for the worker.
308
+ - If sync says the Paperclip API URL is not trusted, set **Worker Paperclip API URL** in GitHub Sync settings to the worker-accessible Paperclip API origin and retry.
272
309
  - If a pull request comment or review action is rejected, read the full toast message. Fine-grained GitHub tokens need write access to that repository, and GitHub requires a review summary when requesting changes.
273
310
  - If a GitHub-linked project does not show the **Pull requests** sidebar entry, reopen the plugin settings and re-save the mapping. The project pull request surfaces also recover older mappings when saved ids are missing, and they can fall back to the active project's bound GitHub repository when the project already has a GitHub workspace configured.
274
311
  - If GitHub rate limiting is hit, the plugin pauses sync until the reported reset time instead of retrying pointlessly.
package/dist/manifest.js CHANGED
@@ -522,6 +522,55 @@ var GITHUB_AGENT_TOOLS = [
522
522
  projectNumber: projectNumberProperty
523
523
  }
524
524
  }
525
+ },
526
+ {
527
+ name: "link_github_item",
528
+ displayName: "Link GitHub Item",
529
+ description: "Link a Paperclip issue to a GitHub issue or pull request so GitHub Sync can monitor status even when the repository is not mapped to a Paperclip project.",
530
+ parametersSchema: {
531
+ type: "object",
532
+ additionalProperties: false,
533
+ required: ["kind", "paperclipIssueId"],
534
+ anyOf: [
535
+ {
536
+ required: ["reference"]
537
+ },
538
+ {
539
+ required: ["issueNumber"]
540
+ },
541
+ {
542
+ required: ["pullRequestNumber"]
543
+ },
544
+ {
545
+ required: ["pullRequestUrl"]
546
+ }
547
+ ],
548
+ properties: {
549
+ kind: {
550
+ type: "string",
551
+ enum: ["issue", "pull_request"],
552
+ description: "Whether to link a GitHub issue or pull request."
553
+ },
554
+ paperclipIssueId: {
555
+ type: "string",
556
+ description: "Paperclip issue id that should receive the GitHub link."
557
+ },
558
+ repository: {
559
+ type: "string",
560
+ description: "GitHub repository as owner/repo or https://github.com/owner/repo. Required when using a number instead of a full GitHub URL and the Paperclip issue project is not mapped to that repository."
561
+ },
562
+ reference: {
563
+ type: "string",
564
+ description: "GitHub issue or pull request number, or a full GitHub issue or pull request URL."
565
+ },
566
+ issueNumber: issueNumberProperty,
567
+ pullRequestNumber: pullRequestNumberProperty,
568
+ pullRequestUrl: {
569
+ type: "string",
570
+ description: "Full GitHub pull request URL."
571
+ }
572
+ }
573
+ }
525
574
  }
526
575
  ];
527
576
 
@@ -530,12 +579,15 @@ var GITHUB_SYNC_PLUGIN_ID = "paperclip-github-plugin";
530
579
  var COMPANY_METRIC_API_ROUTE_KEY = "record-company-metric-event";
531
580
  var COMPANY_METRIC_API_ROUTE_PATH = "/company-metrics/events";
532
581
  var COMPANY_METRIC_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_ID}/api${COMPANY_METRIC_API_ROUTE_PATH}`;
582
+ var ISSUE_LINK_API_ROUTE_KEY = "link-github-item";
583
+ var ISSUE_LINK_API_ROUTE_PATH = "/issue-link";
584
+ var ISSUE_LINK_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_ID}/api${ISSUE_LINK_API_ROUTE_PATH}`;
533
585
 
534
586
  // src/manifest.ts
535
587
  var require2 = createRequire(import.meta.url);
536
588
  var packageJson = require2("../package.json");
537
589
  var SCHEDULE_TICK_CRON = "* * * * *";
538
- var MANIFEST_VERSION = "0.8.7"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
590
+ var MANIFEST_VERSION = "0.8.9"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
539
591
  var manifest = {
540
592
  id: GITHUB_SYNC_PLUGIN_ID,
541
593
  apiVersion: 1,
@@ -607,6 +659,13 @@ var manifest = {
607
659
  path: COMPANY_METRIC_API_ROUTE_PATH,
608
660
  auth: "agent",
609
661
  capability: "api.routes.register"
662
+ },
663
+ {
664
+ routeKey: ISSUE_LINK_API_ROUTE_KEY,
665
+ method: "POST",
666
+ path: ISSUE_LINK_API_ROUTE_PATH,
667
+ auth: "agent",
668
+ capability: "api.routes.register"
610
669
  }
611
670
  ],
612
671
  tools: GITHUB_AGENT_TOOLS,
package/dist/ui/index.js CHANGED
@@ -22694,6 +22694,9 @@ function normalizePluginConfig(value) {
22694
22694
  }
22695
22695
  return record;
22696
22696
  }
22697
+ function resolvePaperclipApiBaseUrlForPluginAction(value, fallbackOrigin) {
22698
+ return normalizePluginConfig(value).paperclipApiBaseUrl ?? normalizePaperclipApiBaseUrl(fallbackOrigin);
22699
+ }
22697
22700
  function mergePluginConfig(currentValue, patch5) {
22698
22701
  const current = normalizePluginConfig(currentValue);
22699
22702
  const currentGitHubTokenRefs = normalizePluginConfigGitHubTokenRefs(current.githubTokenRefs);
@@ -27577,7 +27580,7 @@ function shouldAutofillProjectName(mapping) {
27577
27580
  const previousSuggestedProjectName = formatProjectNameFromRepository(mapping.repositoryUrl);
27578
27581
  return previousSuggestedProjectName !== "" && currentProjectName === previousSuggestedProjectName;
27579
27582
  }
27580
- function getPaperclipApiBaseUrl() {
27583
+ function getPaperclipApiBrowserOrigin() {
27581
27584
  if (typeof window === "undefined" || !window.location?.origin) {
27582
27585
  return void 0;
27583
27586
  }
@@ -27616,23 +27619,22 @@ async function fetchPluginDataResult(params) {
27616
27619
  return response.data;
27617
27620
  }
27618
27621
  async function syncTrustedPaperclipApiBaseUrl(pluginId) {
27619
- const paperclipApiBaseUrl = getPaperclipApiBaseUrl();
27620
- if (!paperclipApiBaseUrl) {
27621
- return void 0;
27622
- }
27623
27622
  const resolvedPluginId = await resolveCurrentPluginId(pluginId);
27624
27623
  if (!resolvedPluginId) {
27625
27624
  throw new Error(
27626
27625
  "Unable to sync the trusted Paperclip API origin because the plugin ID is missing. Reload the plugin and try again before saving or syncing."
27627
27626
  );
27628
27627
  }
27628
+ const currentConfigResponse = await fetchJson(`/api/plugins/${resolvedPluginId}/config`);
27629
+ const currentConfig = normalizePluginConfig(currentConfigResponse?.configJson);
27630
+ const paperclipApiBaseUrl = resolvePaperclipApiBaseUrlForPluginAction(currentConfig, getPaperclipApiBrowserOrigin());
27631
+ if (!paperclipApiBaseUrl) {
27632
+ return void 0;
27633
+ }
27629
27634
  const lastSyncedPaperclipApiBaseUrl = syncedPaperclipApiBaseUrlsByPluginId.get(resolvedPluginId);
27630
27635
  if (lastSyncedPaperclipApiBaseUrl === paperclipApiBaseUrl) {
27631
27636
  return paperclipApiBaseUrl;
27632
27637
  }
27633
- await patchPluginConfig(resolvedPluginId, {
27634
- paperclipApiBaseUrl
27635
- });
27636
27638
  syncedPaperclipApiBaseUrlsByPluginId.set(resolvedPluginId, paperclipApiBaseUrl);
27637
27639
  return paperclipApiBaseUrl;
27638
27640
  }
@@ -31964,6 +31966,7 @@ function GitHubSyncSettingsPage() {
31964
31966
  const [cancellingSync, setCancellingSync] = useState2(false);
31965
31967
  const [manualSyncRequestError, setManualSyncRequestError] = useState2(null);
31966
31968
  const [scheduleFrequencyDraft, setScheduleFrequencyDraft] = useState2(String(DEFAULT_SCHEDULE_FREQUENCY_MINUTES));
31969
+ const [paperclipApiBaseUrlDraft, setPaperclipApiBaseUrlDraft] = useState2("");
31967
31970
  const [ignoredAuthorsDraft, setIgnoredAuthorsDraft] = useState2(DEFAULT_ADVANCED_SETTINGS.ignoredIssueAuthorUsernames.join(", "));
31968
31971
  const [tokenStatusOverride, setTokenStatusOverride] = useState2(null);
31969
31972
  const [validatedLogin, setValidatedLogin] = useState2(null);
@@ -32012,6 +32015,9 @@ function GitHubSyncSettingsPage() {
32012
32015
  updatedAt: settings.data.updatedAt
32013
32016
  });
32014
32017
  setScheduleFrequencyDraft(String(nextScheduleFrequencyMinutes));
32018
+ setPaperclipApiBaseUrlDraft(
32019
+ settings.data.paperclipApiBaseUrlConfigured ? settings.data.paperclipApiBaseUrl ?? "" : getPaperclipApiBrowserOrigin() ?? ""
32020
+ );
32015
32021
  setIgnoredAuthorsDraft(normalizeAdvancedSettings(settings.data.advancedSettings).ignoredIssueAuthorUsernames.join(", "));
32016
32022
  setTokenDraft("");
32017
32023
  setValidatedLogin(tokenUiState.validatedLogin);
@@ -32283,12 +32289,21 @@ function GitHubSyncSettingsPage() {
32283
32289
  const scheduleFrequencyMinutes = parseScheduleFrequencyDraft(scheduleFrequencyDraft) ?? form.scheduleFrequencyMinutes;
32284
32290
  const savedScheduleFrequencyMinutes = normalizeScheduleFrequencyMinutes(currentSettings?.scheduleFrequencyMinutes);
32285
32291
  const scheduleDirty = scheduleFrequencyError === null && scheduleFrequencyMinutes !== savedScheduleFrequencyMinutes;
32292
+ const browserPaperclipApiBaseUrl = getPaperclipApiBrowserOrigin();
32293
+ const normalizedPaperclipApiBaseUrlDraft = normalizePaperclipApiBaseUrl(paperclipApiBaseUrlDraft);
32294
+ const paperclipApiBaseUrlError = paperclipApiBaseUrlDraft.trim() && !normalizedPaperclipApiBaseUrlDraft ? "Enter a valid URL." : null;
32295
+ const effectivePaperclipApiBaseUrl = normalizedPaperclipApiBaseUrlDraft ?? browserPaperclipApiBaseUrl;
32296
+ const paperclipApiBaseUrlIsOverride = Boolean(
32297
+ normalizedPaperclipApiBaseUrlDraft && browserPaperclipApiBaseUrl && normalizedPaperclipApiBaseUrlDraft !== browserPaperclipApiBaseUrl
32298
+ );
32299
+ const savedPaperclipApiBaseUrl = currentSettings?.paperclipApiBaseUrlConfigured ? currentSettings.paperclipApiBaseUrl ?? "" : browserPaperclipApiBaseUrl ?? "";
32300
+ const paperclipApiBaseUrlDirty = paperclipApiBaseUrlError === null && (normalizedPaperclipApiBaseUrlDraft ?? "") !== savedPaperclipApiBaseUrl;
32286
32301
  const mappings = form.mappings.length > 0 ? form.mappings : [createEmptyMapping(0)];
32287
32302
  const settingsMutationsLocked = syncInFlight;
32288
32303
  const settingsMutationsLockReason = settingsMutationsLocked ? "Settings are temporarily locked while a sync is running to avoid overwriting local edits." : null;
32289
32304
  const syncStatus = getSyncStatus(displaySyncState, runningSync, syncUnlocked);
32290
32305
  const canSaveToken = hasCompanyContext && !settingsMutationsLocked && !submittingToken && !showInitialLoadingState && tokenDraft.trim().length > 0;
32291
- const canSaveSetup = hasCompanyContext && repositoriesUnlocked && !settingsMutationsLocked && !submittingSetup && !showInitialLoadingState && scheduleFrequencyError === null && (mappingsDirty || advancedSettingsDirty || scheduleDirty);
32306
+ const canSaveSetup = hasCompanyContext && repositoriesUnlocked && !settingsMutationsLocked && !submittingSetup && !showInitialLoadingState && scheduleFrequencyError === null && paperclipApiBaseUrlError === null && (mappingsDirty || advancedSettingsDirty || scheduleDirty || paperclipApiBaseUrlDirty);
32292
32307
  const canConnectBoardAccess = hasCompanyContext && !settingsMutationsLocked && !connectingBoardAccess && !showInitialLoadingState;
32293
32308
  const boardAccessStatusLabel = !hasCompanyContext ? "Unavailable" : boardAccessBannerLabel;
32294
32309
  const boardAccessStatusTone = !hasCompanyContext ? boardAccessRequired ? "warning" : "neutral" : boardAccessTone;
@@ -32693,6 +32708,9 @@ function GitHubSyncSettingsPage() {
32693
32708
  if (scheduleFrequencyError) {
32694
32709
  throw new Error(scheduleFrequencyError);
32695
32710
  }
32711
+ if (paperclipApiBaseUrlError) {
32712
+ throw new Error(paperclipApiBaseUrlError);
32713
+ }
32696
32714
  const resolvedMappings = [];
32697
32715
  for (const mapping of form.mappings) {
32698
32716
  const repositoryInput = mapping.repositoryUrl.trim();
@@ -32717,7 +32735,18 @@ function GitHubSyncSettingsPage() {
32717
32735
  companyId
32718
32736
  });
32719
32737
  }
32720
- const trustedPaperclipApiBaseUrl = await syncTrustedPaperclipApiBaseUrl(pluginIdFromLocation);
32738
+ const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
32739
+ if (!pluginId) {
32740
+ throw new Error("Plugin id is required to save setup.");
32741
+ }
32742
+ const trustedPaperclipApiBaseUrl = effectivePaperclipApiBaseUrl;
32743
+ if (!trustedPaperclipApiBaseUrl) {
32744
+ throw new Error("Could not resolve the current browser origin for Paperclip API calls.");
32745
+ }
32746
+ const nextConfiguredPaperclipApiBaseUrl = paperclipApiBaseUrlIsOverride ? normalizedPaperclipApiBaseUrlDraft ?? "" : "";
32747
+ await patchPluginConfig(pluginId, {
32748
+ paperclipApiBaseUrl: nextConfiguredPaperclipApiBaseUrl
32749
+ });
32721
32750
  const result = await saveRegistration({
32722
32751
  companyId,
32723
32752
  mappings: resolvedMappings,
@@ -32743,9 +32772,11 @@ function GitHubSyncSettingsPage() {
32743
32772
  advancedSettings: normalizeAdvancedSettings(result.advancedSettings),
32744
32773
  availableAssignees: result.availableAssignees ?? current.availableAssignees,
32745
32774
  paperclipApiBaseUrl: result.paperclipApiBaseUrl,
32775
+ paperclipApiBaseUrlConfigured: paperclipApiBaseUrlIsOverride,
32746
32776
  updatedAt: result.updatedAt
32747
32777
  }));
32748
32778
  setScheduleFrequencyDraft(String(normalizeScheduleFrequencyMinutes(result.scheduleFrequencyMinutes)));
32779
+ setPaperclipApiBaseUrlDraft(paperclipApiBaseUrlIsOverride ? nextConfiguredPaperclipApiBaseUrl : trustedPaperclipApiBaseUrl);
32749
32780
  toast({
32750
32781
  title: "GitHub sync setup saved",
32751
32782
  body: `Advanced defaults, mappings, and automatic sync are saved for ${currentCompanyName}.`,
@@ -33423,6 +33454,32 @@ function GitHubSyncSettingsPage() {
33423
33454
  ] })
33424
33455
  ] })
33425
33456
  ] }),
33457
+ /* @__PURE__ */ jsxs2("div", { className: "ghsync__schedule-card", children: [
33458
+ /* @__PURE__ */ jsxs2("div", { className: "ghsync__field", children: [
33459
+ /* @__PURE__ */ jsx2("label", { htmlFor: "paperclip-api-base-url", children: "Worker Paperclip API URL" }),
33460
+ /* @__PURE__ */ jsx2(
33461
+ "input",
33462
+ {
33463
+ id: "paperclip-api-base-url",
33464
+ className: "ghsync__input",
33465
+ type: "url",
33466
+ value: paperclipApiBaseUrlDraft,
33467
+ disabled: settingsMutationsLocked || !hasCompanyContext,
33468
+ onChange: (event) => {
33469
+ setPaperclipApiBaseUrlDraft(event.currentTarget.value);
33470
+ },
33471
+ placeholder: browserPaperclipApiBaseUrl ?? "https://paperclip.example.com",
33472
+ autoComplete: "off"
33473
+ }
33474
+ ),
33475
+ /* @__PURE__ */ jsx2("p", { className: `ghsync__hint${paperclipApiBaseUrlError ? " ghsync__hint--error" : ""}`, children: paperclipApiBaseUrlError ?? "Edit this only when the plugin worker needs a different Paperclip API origin." })
33476
+ ] }),
33477
+ /* @__PURE__ */ jsxs2("div", { className: "ghsync__schedule-meta", children: [
33478
+ /* @__PURE__ */ jsx2("span", { className: "ghsync__scope-pill ghsync__scope-pill--global", children: "Worker" }),
33479
+ /* @__PURE__ */ jsx2("strong", { children: effectivePaperclipApiBaseUrl ?? "Browser origin unavailable" }),
33480
+ /* @__PURE__ */ jsx2("span", { children: paperclipApiBaseUrlIsOverride ? "Configured override." : "Using browser origin." })
33481
+ ] })
33482
+ ] }),
33426
33483
  !syncUnlocked ? /* @__PURE__ */ jsxs2("div", { className: "ghsync__locked", children: [
33427
33484
  /* @__PURE__ */ jsxs2("div", { children: [
33428
33485
  /* @__PURE__ */ jsx2("strong", { children: syncSetupIssue === "missing_board_access" ? "Paperclip board access is required" : "Manual sync is locked" }),
@@ -33544,6 +33601,10 @@ function GitHubSyncSettingsPage() {
33544
33601
  /* @__PURE__ */ jsx2("span", { className: "ghsync__detail-label", children: "Auto-sync" }),
33545
33602
  /* @__PURE__ */ jsx2("strong", { className: "ghsync__detail-value", children: scheduleDescription })
33546
33603
  ] }),
33604
+ /* @__PURE__ */ jsxs2("div", { className: "ghsync__detail", children: [
33605
+ /* @__PURE__ */ jsx2("span", { className: "ghsync__detail-label", children: "Worker API" }),
33606
+ /* @__PURE__ */ jsx2("strong", { className: "ghsync__detail-value", children: effectivePaperclipApiBaseUrl })
33607
+ ] }),
33547
33608
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__detail", children: [
33548
33609
  /* @__PURE__ */ jsx2("span", { className: "ghsync__detail-label", children: "Last sync" }),
33549
33610
  /* @__PURE__ */ jsx2("strong", { className: "ghsync__detail-value", children: lastSync })