paperclip-github-plugin 0.2.1 → 0.2.2

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/dist/ui/index.js CHANGED
@@ -67,6 +67,32 @@ function requiresPaperclipBoardAccess(value) {
67
67
  return health?.deploymentMode?.toLowerCase() === "authenticated";
68
68
  }
69
69
 
70
+ // src/ui/assignees.ts
71
+ function normalizeCompanyAssigneeOptionsResponse(response) {
72
+ if (!Array.isArray(response)) {
73
+ throw new Error("Unexpected company agents response: expected an array.");
74
+ }
75
+ return response.map((entry) => {
76
+ if (!entry || typeof entry !== "object") {
77
+ return null;
78
+ }
79
+ const record = entry;
80
+ const id = typeof record.id === "string" ? record.id.trim() : "";
81
+ const name = typeof record.name === "string" ? record.name.trim() : "";
82
+ const status = typeof record.status === "string" ? record.status.trim() : "";
83
+ const title = typeof record.title === "string" ? record.title.trim() : "";
84
+ if (!id || !name || status === "terminated") {
85
+ return null;
86
+ }
87
+ return {
88
+ id,
89
+ name,
90
+ ...title ? { title } : {},
91
+ ...status ? { status } : {}
92
+ };
93
+ }).filter((entry) => entry !== null).sort((left, right) => left.name.localeCompare(right.name));
94
+ }
95
+
70
96
  // src/ui/http.ts
71
97
  var JSON_CONTENT_TYPE_PATTERN = /\b(?:application\/json|[^;\s]+\/[^;\s]+\+json)\b/i;
72
98
  var HTML_LIKE_RESPONSE_PATTERN = /^\s*</;
@@ -217,6 +243,48 @@ async function fetchPaperclipHealth(origin) {
217
243
  }
218
244
  }
219
245
 
246
+ // src/ui/plugin-installation.ts
247
+ var SETTINGS_INDEX_HREF = "/instance/settings/plugins";
248
+ var GITHUB_SYNC_PLUGIN_KEY = "paperclip-github-plugin";
249
+ var GITHUB_SYNC_PLUGIN_DISPLAY_NAME = "GitHub Sync";
250
+ function getStringValue(record, key) {
251
+ if (!record) {
252
+ return null;
253
+ }
254
+ const value = record[key];
255
+ return typeof value === "string" && value.trim() ? value.trim() : null;
256
+ }
257
+ function resolveGitHubSyncPluginRecord(records) {
258
+ if (!Array.isArray(records)) {
259
+ return null;
260
+ }
261
+ for (const entry of records) {
262
+ if (!entry || typeof entry !== "object") {
263
+ continue;
264
+ }
265
+ const record = entry;
266
+ const manifest = record.manifest && typeof record.manifest === "object" ? record.manifest : null;
267
+ const key = getStringValue(record, "pluginKey") ?? getStringValue(record, "key") ?? getStringValue(record, "packageName") ?? getStringValue(record, "name") ?? getStringValue(manifest, "id");
268
+ const displayName = getStringValue(record, "displayName") ?? getStringValue(manifest, "displayName");
269
+ const id = getStringValue(record, "id") ?? getStringValue(record, "pluginId");
270
+ if (id && (key === GITHUB_SYNC_PLUGIN_KEY || displayName === GITHUB_SYNC_PLUGIN_DISPLAY_NAME)) {
271
+ return record;
272
+ }
273
+ }
274
+ return null;
275
+ }
276
+ function resolveInstalledGitHubSyncPluginId(records, preferredPluginId) {
277
+ if (typeof preferredPluginId === "string" && preferredPluginId.trim()) {
278
+ return preferredPluginId.trim();
279
+ }
280
+ const record = resolveGitHubSyncPluginRecord(records);
281
+ return getStringValue(record, "id") ?? getStringValue(record, "pluginId");
282
+ }
283
+ function resolvePluginSettingsHref(records) {
284
+ const pluginId = resolveInstalledGitHubSyncPluginId(records);
285
+ return pluginId ? `${SETTINGS_INDEX_HREF}/${pluginId}` : SETTINGS_INDEX_HREF;
286
+ }
287
+
220
288
  // src/ui/plugin-config.ts
221
289
  function normalizeOptionalString2(value) {
222
290
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
@@ -2681,24 +2749,39 @@ function getPaperclipApiBaseUrl() {
2681
2749
  return window.location.origin;
2682
2750
  }
2683
2751
  var syncedPaperclipApiBaseUrlsByPluginId = /* @__PURE__ */ new Map();
2752
+ var installedGitHubSyncPluginIdPromise = null;
2753
+ async function resolveCurrentPluginId(pluginId) {
2754
+ if (pluginId) {
2755
+ return pluginId;
2756
+ }
2757
+ if (!installedGitHubSyncPluginIdPromise) {
2758
+ installedGitHubSyncPluginIdPromise = fetchJson("/api/plugins").then((records) => resolveInstalledGitHubSyncPluginId(records)).catch(() => null);
2759
+ }
2760
+ const resolvedPluginId = await installedGitHubSyncPluginIdPromise;
2761
+ if (!resolvedPluginId) {
2762
+ installedGitHubSyncPluginIdPromise = null;
2763
+ }
2764
+ return resolvedPluginId;
2765
+ }
2684
2766
  async function syncTrustedPaperclipApiBaseUrl(pluginId) {
2685
2767
  const paperclipApiBaseUrl = getPaperclipApiBaseUrl();
2686
2768
  if (!paperclipApiBaseUrl) {
2687
2769
  return void 0;
2688
2770
  }
2689
- if (!pluginId) {
2771
+ const resolvedPluginId = await resolveCurrentPluginId(pluginId);
2772
+ if (!resolvedPluginId) {
2690
2773
  throw new Error(
2691
2774
  "Unable to sync the trusted Paperclip API origin because the plugin ID is missing. Reload the plugin and try again before saving or syncing."
2692
2775
  );
2693
2776
  }
2694
- const lastSyncedPaperclipApiBaseUrl = syncedPaperclipApiBaseUrlsByPluginId.get(pluginId);
2777
+ const lastSyncedPaperclipApiBaseUrl = syncedPaperclipApiBaseUrlsByPluginId.get(resolvedPluginId);
2695
2778
  if (lastSyncedPaperclipApiBaseUrl === paperclipApiBaseUrl) {
2696
2779
  return paperclipApiBaseUrl;
2697
2780
  }
2698
- await patchPluginConfig(pluginId, {
2781
+ await patchPluginConfig(resolvedPluginId, {
2699
2782
  paperclipApiBaseUrl
2700
2783
  });
2701
- syncedPaperclipApiBaseUrlsByPluginId.set(pluginId, paperclipApiBaseUrl);
2784
+ syncedPaperclipApiBaseUrlsByPluginId.set(resolvedPluginId, paperclipApiBaseUrl);
2702
2785
  return paperclipApiBaseUrl;
2703
2786
  }
2704
2787
  function formatDate(value, fallback = "Never") {
@@ -2804,6 +2887,11 @@ async function listCompanyProjects(companyId) {
2804
2887
  return id && name ? { id, name } : null;
2805
2888
  }).filter((entry) => entry !== null);
2806
2889
  }
2890
+ async function listCompanyAssigneeOptions(companyId) {
2891
+ return normalizeCompanyAssigneeOptionsResponse(
2892
+ await fetchJson(`/api/companies/${companyId}/agents`)
2893
+ );
2894
+ }
2807
2895
  async function listProjectWorkspaces(projectId) {
2808
2896
  const response = await fetchJson(`/api/projects/${projectId}/workspaces`);
2809
2897
  if (!Array.isArray(response)) {
@@ -3018,9 +3106,9 @@ function getToneClass(tone) {
3018
3106
  return "ghsync__badge--neutral";
3019
3107
  }
3020
3108
  }
3021
- var SETTINGS_INDEX_HREF = "/instance/settings/plugins";
3109
+ var SETTINGS_INDEX_HREF2 = "/instance/settings/plugins";
3022
3110
  var GITHUB_SYNC_SETTINGS_UPDATED_EVENT = "paperclip-github-plugin:settings-updated";
3023
- function getStringValue(record, key) {
3111
+ function getStringValue2(record, key) {
3024
3112
  const value = record[key];
3025
3113
  return typeof value === "string" && value.trim() ? value.trim() : null;
3026
3114
  }
@@ -3038,12 +3126,12 @@ function humanizeCompanyPrefix(value) {
3038
3126
  return trimmed.replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (character) => character.toUpperCase());
3039
3127
  }
3040
3128
  function getCompanyLabelFromRecord(record) {
3041
- const explicitLabel = getStringValue(record, "displayName") ?? getStringValue(record, "name") ?? getStringValue(record, "title");
3129
+ const explicitLabel = getStringValue2(record, "displayName") ?? getStringValue2(record, "name") ?? getStringValue2(record, "title");
3042
3130
  if (explicitLabel && !isUuidLike(explicitLabel)) {
3043
3131
  return explicitLabel;
3044
3132
  }
3045
3133
  return humanizeCompanyPrefix(
3046
- getStringValue(record, "companyPrefix") ?? getStringValue(record, "prefix") ?? getStringValue(record, "slug")
3134
+ getStringValue2(record, "companyPrefix") ?? getStringValue2(record, "prefix") ?? getStringValue2(record, "slug")
3047
3135
  );
3048
3136
  }
3049
3137
  async function resolveCompanyScopeLabel(companyId, companyPrefix) {
@@ -3069,8 +3157,8 @@ async function resolveCompanyScopeLabel(companyId, companyPrefix) {
3069
3157
  return false;
3070
3158
  }
3071
3159
  const record = entry;
3072
- const entryId = getStringValue(record, "id");
3073
- const entryPrefix = getStringValue(record, "companyPrefix") ?? getStringValue(record, "prefix") ?? getStringValue(record, "slug");
3160
+ const entryId = getStringValue2(record, "id");
3161
+ const entryPrefix = getStringValue2(record, "companyPrefix") ?? getStringValue2(record, "prefix") ?? getStringValue2(record, "slug");
3074
3162
  return entryId === companyId || Boolean(companyPrefix && entryPrefix === companyPrefix);
3075
3163
  });
3076
3164
  if (matchingCompany && typeof matchingCompany === "object") {
@@ -3154,25 +3242,6 @@ function useResolvedIssueId(params) {
3154
3242
  loading: false
3155
3243
  };
3156
3244
  }
3157
- function resolvePluginSettingsHref(records) {
3158
- if (!Array.isArray(records)) {
3159
- return SETTINGS_INDEX_HREF;
3160
- }
3161
- for (const entry of records) {
3162
- if (!entry || typeof entry !== "object") {
3163
- continue;
3164
- }
3165
- const record = entry;
3166
- const manifest = record.manifest && typeof record.manifest === "object" ? record.manifest : null;
3167
- const id = getStringValue(record, "id") ?? getStringValue(record, "pluginId");
3168
- const key = getStringValue(record, "pluginKey") ?? getStringValue(record, "key") ?? getStringValue(record, "packageName") ?? getStringValue(record, "name") ?? (manifest ? getStringValue(manifest, "id") : null);
3169
- const displayName = getStringValue(record, "displayName") ?? (manifest ? getStringValue(manifest, "displayName") : null);
3170
- if (id && (key === "paperclip-github-plugin" || displayName === "GitHub Sync")) {
3171
- return `${SETTINGS_INDEX_HREF}/${id}`;
3172
- }
3173
- }
3174
- return SETTINGS_INDEX_HREF;
3175
- }
3176
3245
  function formatSyncProgressRepository(repositoryUrl) {
3177
3246
  if (!repositoryUrl?.trim()) {
3178
3247
  return null;
@@ -3580,10 +3649,12 @@ function GitHubSyncSettingsPage() {
3580
3649
  const [existingProjectCandidates, setExistingProjectCandidates] = useState([]);
3581
3650
  const [existingProjectCandidatesLoading, setExistingProjectCandidatesLoading] = useState(false);
3582
3651
  const [existingProjectCandidatesError, setExistingProjectCandidatesError] = useState(null);
3652
+ const [browserAvailableAssignees, setBrowserAvailableAssignees] = useState([]);
3583
3653
  const themeMode = useResolvedThemeMode();
3584
3654
  const boardAccessRequirement = usePaperclipBoardAccessRequirement();
3585
3655
  const armSyncCompletionToast = useSyncCompletionToast(form.syncState, toast);
3586
3656
  const boardAccessConfigSyncAttemptRef = useRef(null);
3657
+ const assigneeFallbackAttemptRef = useRef(null);
3587
3658
  const currentSettings = settings.data ?? cachedSettings;
3588
3659
  const showInitialLoadingState = settings.loading && !settings.data && !cachedSettings;
3589
3660
  useEffect(() => {
@@ -3659,10 +3730,47 @@ function GitHubSyncSettingsPage() {
3659
3730
  cancelled = true;
3660
3731
  };
3661
3732
  }, [hostContext.companyId, settings.data?.updatedAt, tokenStatusOverride]);
3733
+ useEffect(() => {
3734
+ const companyId = hostContext.companyId;
3735
+ const workerAvailableAssignees = currentSettings?.availableAssignees ?? [];
3736
+ const snapshotKey = `${companyId ?? "none"}:${currentSettings?.updatedAt ?? "none"}`;
3737
+ if (!companyId) {
3738
+ assigneeFallbackAttemptRef.current = null;
3739
+ setBrowserAvailableAssignees([]);
3740
+ return;
3741
+ }
3742
+ if (workerAvailableAssignees.length > 0) {
3743
+ assigneeFallbackAttemptRef.current = snapshotKey;
3744
+ setBrowserAvailableAssignees([]);
3745
+ return;
3746
+ }
3747
+ if (assigneeFallbackAttemptRef.current === snapshotKey) {
3748
+ return;
3749
+ }
3750
+ assigneeFallbackAttemptRef.current = snapshotKey;
3751
+ let cancelled = false;
3752
+ void (async () => {
3753
+ try {
3754
+ const assignees = await listCompanyAssigneeOptions(companyId);
3755
+ if (cancelled) {
3756
+ return;
3757
+ }
3758
+ setBrowserAvailableAssignees(assignees);
3759
+ } catch {
3760
+ if (cancelled) {
3761
+ return;
3762
+ }
3763
+ setBrowserAvailableAssignees([]);
3764
+ }
3765
+ })();
3766
+ return () => {
3767
+ cancelled = true;
3768
+ };
3769
+ }, [currentSettings?.availableAssignees?.length, currentSettings?.updatedAt, hostContext.companyId]);
3662
3770
  useEffect(() => {
3663
3771
  const companyId = hostContext.companyId;
3664
3772
  const secretRef = settings.data?.paperclipBoardAccessNeedsConfigSync ? settings.data.paperclipBoardAccessConfigSyncRef : void 0;
3665
- if (!companyId || !pluginIdFromLocation || !secretRef) {
3773
+ if (!companyId || !secretRef) {
3666
3774
  return;
3667
3775
  }
3668
3776
  const attemptKey = `${companyId}:${secretRef}`;
@@ -3673,7 +3781,11 @@ function GitHubSyncSettingsPage() {
3673
3781
  let cancelled = false;
3674
3782
  void (async () => {
3675
3783
  try {
3676
- await patchPluginConfig(pluginIdFromLocation, {
3784
+ const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
3785
+ if (!pluginId) {
3786
+ throw new Error("Plugin id is required to finish syncing Paperclip board access into plugin config.");
3787
+ }
3788
+ await patchPluginConfig(pluginId, {
3677
3789
  paperclipBoardApiTokenRefs: {
3678
3790
  [companyId]: secretRef
3679
3791
  }
@@ -3760,7 +3872,7 @@ function GitHubSyncSettingsPage() {
3760
3872
  const boardAccessSectionDescription = "";
3761
3873
  const repositoriesUnlocked = tokenStatus === "valid";
3762
3874
  const availableAssignees = getAvailableAssigneeOptions(
3763
- currentSettings?.availableAssignees ?? form.availableAssignees,
3875
+ (currentSettings?.availableAssignees?.length ? currentSettings.availableAssignees : null) ?? (form.availableAssignees?.length ? form.availableAssignees : null) ?? browserAvailableAssignees,
3764
3876
  form.advancedSettings.defaultAssigneeAgentId
3765
3877
  );
3766
3878
  const savedMappingsSource = currentSettings ? currentSettings.mappings ?? [] : form.mappings;
@@ -3946,13 +4058,14 @@ function GitHubSyncSettingsPage() {
3946
4058
  if (!companyId) {
3947
4059
  throw new Error("Company context is required to save the GitHub token.");
3948
4060
  }
3949
- if (!pluginIdFromLocation) {
4061
+ const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
4062
+ if (!pluginId) {
3950
4063
  throw new Error("Plugin id is required to save the GitHub token.");
3951
4064
  }
3952
4065
  const trimmedToken = tokenDraft.trim();
3953
4066
  const secretName = `github_sync_${companyId.replace(/[^a-z0-9]+/gi, "_").toLowerCase()}`;
3954
4067
  const secret = await resolveOrCreateCompanySecret(companyId, secretName, trimmedToken);
3955
- await patchPluginConfig(pluginIdFromLocation, {
4068
+ await patchPluginConfig(pluginId, {
3956
4069
  githubTokenRef: secret.id
3957
4070
  });
3958
4071
  await saveRegistration({
@@ -3997,7 +4110,8 @@ function GitHubSyncSettingsPage() {
3997
4110
  if (!companyId) {
3998
4111
  throw new Error("Company context is required to connect Paperclip board access.");
3999
4112
  }
4000
- if (!pluginIdFromLocation) {
4113
+ const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
4114
+ if (!pluginId) {
4001
4115
  throw new Error("Plugin id is required to connect Paperclip board access.");
4002
4116
  }
4003
4117
  if (typeof window !== "undefined") {
@@ -4020,7 +4134,7 @@ function GitHubSyncSettingsPage() {
4020
4134
  const identity = await fetchBoardAccessIdentity(boardApiToken);
4021
4135
  const secretName = `paperclip_board_api_${companyId.replace(/[^a-z0-9]+/gi, "_").toLowerCase()}`;
4022
4136
  const secret = await resolveOrCreateCompanySecret(companyId, secretName, boardApiToken);
4023
- await patchPluginConfig(pluginIdFromLocation, {
4137
+ await patchPluginConfig(pluginId, {
4024
4138
  paperclipBoardApiTokenRefs: {
4025
4139
  [companyId]: secret.id
4026
4140
  }
@@ -4729,7 +4843,7 @@ function GitHubSyncDashboardWidget() {
4729
4843
  const runSyncNow = usePluginAction("sync.runNow");
4730
4844
  const [runningSync, setRunningSync] = useState(false);
4731
4845
  const [manualSyncRequestError, setManualSyncRequestError] = useState(null);
4732
- const [settingsHref, setSettingsHref] = useState(SETTINGS_INDEX_HREF);
4846
+ const [settingsHref, setSettingsHref] = useState(SETTINGS_INDEX_HREF2);
4733
4847
  const [cachedSettings, setCachedSettings] = useState(null);
4734
4848
  const themeMode = useResolvedThemeMode();
4735
4849
  const boardAccessRequirement = usePaperclipBoardAccessRequirement();
@@ -4792,7 +4906,7 @@ function GitHubSyncDashboardWidget() {
4792
4906
  }
4793
4907
  } catch {
4794
4908
  if (!cancelled) {
4795
- setSettingsHref(SETTINGS_INDEX_HREF);
4909
+ setSettingsHref(SETTINGS_INDEX_HREF2);
4796
4910
  }
4797
4911
  }
4798
4912
  }