paperclip-github-plugin 0.9.0 → 0.9.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/README.md CHANGED
@@ -196,6 +196,7 @@ The plugin is designed to avoid persisting raw credentials in plugin state.
196
196
  - The settings UI also keeps lightweight non-secret identity labels for those saved connections, so later visits can still show who each company GitHub token and board access are connected as.
197
197
  - On authenticated deployments, any selected propagation agents receive `GITHUB_TOKEN` as an agent env secret-ref binding that points at the same saved GitHub token secret instead of a copied raw token.
198
198
  - The worker resolves those secret references at runtime instead of storing raw tokens in plugin state.
199
+ - When the current Paperclip host rejects plugin secret refs, GitHub Sync keeps company-scoped worker-local compatibility copies for GitHub tokens and Paperclip board-access tokens in `${PAPERCLIP_HOME:-~/.paperclip}/plugins/github-sync/config.json`. Reconnect board access once after upgrading if sync still cannot authenticate Paperclip label or issue REST calls.
199
200
  - On authenticated Paperclip deployments, sync is blocked until the relevant company has connected Paperclip board access.
200
201
  - KPI API route requests must include `Authorization: Bearer <PAPERCLIP_API_KEY>` from an agent run; the Paperclip host authenticates the token and supplies the agent company before the worker records any metric event.
201
202
 
@@ -205,7 +206,13 @@ If Paperclip-managed secrets are not available, the worker can read a local fall
205
206
 
206
207
  ```json
207
208
  {
208
- "githubToken": "ghp_your_token_here"
209
+ "githubToken": "ghp_your_token_here",
210
+ "githubTokensByCompanyId": {
211
+ "company-uuid": "ghp_company_specific_token_here"
212
+ },
213
+ "paperclipBoardApiTokensByCompanyId": {
214
+ "company-uuid": "paperclip_board_api_token_here"
215
+ }
209
216
  }
210
217
  ```
211
218
 
@@ -213,7 +220,9 @@ Notes:
213
220
 
214
221
  - This file is read by the worker only.
215
222
  - The raw token is never persisted back into plugin state or plugin config.
216
- - A GitHub token secret saved through the settings UI takes precedence over the local file.
223
+ - A GitHub token secret saved through the settings UI is the primary source. If the current Paperclip host rejects plugin secret-ref resolution while company-scoped plugin config is unavailable, GitHub Sync stores the validated token in `githubTokensByCompanyId` as a worker-local compatibility fallback.
224
+ - A Paperclip board access secret saved through the settings UI is also the primary source. If the host cannot resolve it for plugin workers, reconnecting board access stores the approved board token in `paperclipBoardApiTokensByCompanyId` as a worker-local compatibility fallback for direct Paperclip REST calls.
225
+ - On authenticated deployments, selected agents receive `GITHUB_TOKEN` as a latest-version secret-ref env binding, and the settings UI patches agent adapter config with `replaceAdapterConfig: true` so newer Paperclip hosts persist the merged env map.
217
226
 
218
227
  ### Worker-facing Paperclip API URL
219
228
 
package/dist/manifest.js CHANGED
@@ -642,7 +642,7 @@ var PULL_REQUEST_ASSET_API_ROUTE_URL_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_I
642
642
  var require2 = createRequire(import.meta.url);
643
643
  var packageJson = require2("../package.json");
644
644
  var SCHEDULE_TICK_CRON = "* * * * *";
645
- var MANIFEST_VERSION = "0.9.0"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
645
+ var MANIFEST_VERSION = "0.9.2"?.trim() || typeof packageJson.version === "string" && packageJson.version.trim() || process.env.npm_package_version?.trim() || "0.0.0-dev";
646
646
  var manifest = {
647
647
  id: GITHUB_SYNC_PLUGIN_ID,
648
648
  apiVersion: 1,
package/dist/ui/index.js CHANGED
@@ -23165,6 +23165,9 @@ function resolveToolbarButtonState(params) {
23165
23165
  syncStartPending
23166
23166
  };
23167
23167
  }
23168
+ function clearGitHubTokenConfigSyncAttemptOnFailure(currentAttemptKey, failedAttemptKey) {
23169
+ return currentAttemptKey === failedAttemptKey ? null : currentAttemptKey;
23170
+ }
23168
23171
  function getSyncToastTitle(syncState) {
23169
23172
  if (getActiveRateLimitPause(syncState)) {
23170
23173
  return "GitHub sync is paused";
@@ -28394,6 +28397,25 @@ async function resolveOrCreateCompanySecret(companyId, name2, value) {
28394
28397
  })
28395
28398
  });
28396
28399
  }
28400
+ function isPluginSecretReferencesDisabledError(error) {
28401
+ return getActionErrorMessage(error, "").toLowerCase().includes("plugin secret references are disabled");
28402
+ }
28403
+ function stripPluginSecretRefConfig(config) {
28404
+ const {
28405
+ githubTokenRefs: _githubTokenRefs,
28406
+ paperclipBoardApiTokenRefs: _paperclipBoardApiTokenRefs,
28407
+ ...safeConfig
28408
+ } = config;
28409
+ return safeConfig;
28410
+ }
28411
+ async function writePluginConfig(pluginId, config) {
28412
+ await fetchJson(`/api/plugins/${pluginId}/config`, {
28413
+ method: "POST",
28414
+ body: JSON.stringify({
28415
+ configJson: config
28416
+ })
28417
+ });
28418
+ }
28397
28419
  async function patchPluginConfig(pluginId, patch5) {
28398
28420
  const currentConfigResponse = await fetchJson(`/api/plugins/${pluginId}/config`);
28399
28421
  const currentConfig = normalizePluginConfig(currentConfigResponse?.configJson);
@@ -28401,12 +28423,18 @@ async function patchPluginConfig(pluginId, patch5) {
28401
28423
  if (JSON.stringify(nextConfig) === JSON.stringify(currentConfig)) {
28402
28424
  return;
28403
28425
  }
28404
- await fetchJson(`/api/plugins/${pluginId}/config`, {
28405
- method: "POST",
28406
- body: JSON.stringify({
28407
- configJson: nextConfig
28408
- })
28409
- });
28426
+ try {
28427
+ await writePluginConfig(pluginId, nextConfig);
28428
+ } catch (error) {
28429
+ if (!isPluginSecretReferencesDisabledError(error)) {
28430
+ throw error;
28431
+ }
28432
+ const safeConfig = stripPluginSecretRefConfig(nextConfig);
28433
+ if (JSON.stringify(safeConfig) === JSON.stringify(nextConfig)) {
28434
+ throw error;
28435
+ }
28436
+ await writePluginConfig(pluginId, safeConfig);
28437
+ }
28410
28438
  }
28411
28439
  var GITHUB_TOKEN_PROPAGATION_CONCURRENCY_LIMIT = 4;
28412
28440
  function normalizeAgentAdapterConfig(value) {
@@ -28430,7 +28458,8 @@ function getAgentPropagationPatch(params) {
28430
28458
  ...currentEnv,
28431
28459
  GITHUB_TOKEN: {
28432
28460
  type: "secret_ref",
28433
- secretId: params.githubTokenSecretRef
28461
+ secretId: params.githubTokenSecretRef,
28462
+ version: "latest"
28434
28463
  }
28435
28464
  };
28436
28465
  if (JSON.stringify(nextEnv2) === JSON.stringify(currentEnv)) {
@@ -28485,7 +28514,8 @@ async function applyGitHubTokenPropagationUpdate(params) {
28485
28514
  await fetchJson(`/api/agents/${params.agentId}`, {
28486
28515
  method: "PATCH",
28487
28516
  body: JSON.stringify({
28488
- adapterConfig: nextAdapterConfig
28517
+ adapterConfig: nextAdapterConfig,
28518
+ replaceAdapterConfig: true
28489
28519
  })
28490
28520
  });
28491
28521
  }
@@ -31968,6 +31998,7 @@ function GitHubSyncSettingsPage() {
31968
31998
  const saveRegistration = usePluginAction("settings.saveRegistration");
31969
31999
  const updateBoardAccess = usePluginAction("settings.updateBoardAccess");
31970
32000
  const validateToken = usePluginAction("settings.validateToken");
32001
+ const ensureGitHubTokenAvailable = usePluginAction("settings.ensureGitHubTokenAvailable");
31971
32002
  const runSyncNow = usePluginAction("sync.runNow");
31972
32003
  const cancelSync = usePluginAction("sync.cancel");
31973
32004
  const [form, setForm] = useState2(EMPTY_SETTINGS);
@@ -31995,6 +32026,7 @@ function GitHubSyncSettingsPage() {
31995
32026
  const themeMode = useResolvedThemeMode();
31996
32027
  const boardAccessRequirement = usePaperclipBoardAccessRequirement();
31997
32028
  const armSyncCompletionToast = useSyncCompletionToast(form.syncState, toast);
32029
+ const githubTokenConfigSyncAttemptRef = useRef(null);
31998
32030
  const boardAccessConfigSyncAttemptRef = useRef(null);
31999
32031
  const assigneeFallbackAttemptRef = useRef(null);
32000
32032
  const currentSettings = settings.data ?? cachedSettings;
@@ -32111,6 +32143,76 @@ function GitHubSyncSettingsPage() {
32111
32143
  cancelled = true;
32112
32144
  };
32113
32145
  }, [currentSettings?.availableAssignees?.length, currentSettings?.updatedAt, hostContext.companyId]);
32146
+ useEffect2(() => {
32147
+ const companyId = hostContext.companyId;
32148
+ const secretRef = settings.data?.githubTokenNeedsConfigSync ? settings.data.githubTokenConfigSyncRef : void 0;
32149
+ if (!companyId || !secretRef) {
32150
+ return;
32151
+ }
32152
+ const attemptKey = `${companyId}:${secretRef}`;
32153
+ if (githubTokenConfigSyncAttemptRef.current === attemptKey) {
32154
+ return;
32155
+ }
32156
+ githubTokenConfigSyncAttemptRef.current = attemptKey;
32157
+ let cancelled = false;
32158
+ void (async () => {
32159
+ try {
32160
+ const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
32161
+ if (!pluginId) {
32162
+ throw new Error("Plugin id is required to finish syncing the GitHub token into plugin config.");
32163
+ }
32164
+ await patchPluginConfig(pluginId, {
32165
+ githubTokenRefs: {
32166
+ [companyId]: secretRef
32167
+ }
32168
+ });
32169
+ const selectedAgentIds = normalizeAgentIds(settings.data?.advancedSettings?.githubTokenPropagationAgentIds);
32170
+ if (selectedAgentIds.length > 0) {
32171
+ await propagateGitHubTokenToSelectedAgents({
32172
+ selectedAgentIds,
32173
+ previousAgentIds: selectedAgentIds,
32174
+ githubTokenSecretRef: secretRef
32175
+ });
32176
+ }
32177
+ if (cancelled) {
32178
+ return;
32179
+ }
32180
+ notifyGitHubSyncSettingsChanged();
32181
+ try {
32182
+ await settings.refresh();
32183
+ } catch {
32184
+ return;
32185
+ }
32186
+ } catch (error) {
32187
+ githubTokenConfigSyncAttemptRef.current = clearGitHubTokenConfigSyncAttemptOnFailure(
32188
+ githubTokenConfigSyncAttemptRef.current,
32189
+ attemptKey
32190
+ );
32191
+ if (cancelled) {
32192
+ return;
32193
+ }
32194
+ toast({
32195
+ title: "GitHub token needs reconnection",
32196
+ body: getActionErrorMessage(
32197
+ error,
32198
+ "GitHub Sync could not finish migrating the saved GitHub token into plugin config."
32199
+ ),
32200
+ tone: "error"
32201
+ });
32202
+ }
32203
+ })();
32204
+ return () => {
32205
+ cancelled = true;
32206
+ };
32207
+ }, [
32208
+ hostContext.companyId,
32209
+ pluginIdFromLocation,
32210
+ settings.data?.advancedSettings?.githubTokenPropagationAgentIds,
32211
+ settings.data?.githubTokenNeedsConfigSync,
32212
+ settings.data?.githubTokenConfigSyncRef,
32213
+ settings.refresh,
32214
+ toast
32215
+ ]);
32114
32216
  useEffect2(() => {
32115
32217
  const companyId = hostContext.companyId;
32116
32218
  const secretRef = settings.data?.paperclipBoardAccessNeedsConfigSync ? settings.data.paperclipBoardAccessConfigSyncRef : void 0;
@@ -32581,6 +32683,16 @@ function GitHubSyncSettingsPage() {
32581
32683
  },
32582
32684
  githubTokenLogin: validation.login
32583
32685
  });
32686
+ let availabilityWarning = null;
32687
+ try {
32688
+ await ensureGitHubTokenAvailable({
32689
+ companyId,
32690
+ githubTokenRef: secret.id,
32691
+ token: trimmedToken
32692
+ });
32693
+ } catch (error) {
32694
+ availabilityWarning = error;
32695
+ }
32584
32696
  const selectedAgentIds = normalizeAgentIds(currentSettings?.advancedSettings?.githubTokenPropagationAgentIds);
32585
32697
  let propagationError = null;
32586
32698
  try {
@@ -32616,6 +32728,16 @@ function GitHubSyncSettingsPage() {
32616
32728
  tone: "error"
32617
32729
  });
32618
32730
  }
32731
+ if (availabilityWarning) {
32732
+ toast({
32733
+ title: "GitHub token saved, but worker token access needs attention",
32734
+ body: getActionErrorMessage(
32735
+ availabilityWarning,
32736
+ "GitHub Sync could not verify worker access to the saved token."
32737
+ ),
32738
+ tone: "error"
32739
+ });
32740
+ }
32619
32741
  notifyGitHubSyncSettingsChanged();
32620
32742
  try {
32621
32743
  await settings.refresh();
@@ -32672,6 +32794,7 @@ function GitHubSyncSettingsPage() {
32672
32794
  await updateBoardAccess({
32673
32795
  companyId,
32674
32796
  paperclipBoardApiTokenRef: secret.id,
32797
+ paperclipBoardApiToken: boardApiToken,
32675
32798
  paperclipBoardAccessIdentity: boardIdentity.label ?? "",
32676
32799
  paperclipBoardAccessUserId: boardIdentity.userId ?? ""
32677
32800
  });
@@ -35133,7 +35256,9 @@ export {
35133
35256
  GitHubSyncProjectPullRequestsPage,
35134
35257
  GitHubSyncProjectPullRequestsSidebarItem,
35135
35258
  GitHubSyncSettingsPage,
35259
+ clearGitHubTokenConfigSyncAttemptOnFailure,
35136
35260
  index_default as default,
35261
+ patchPluginConfig,
35137
35262
  resolveGitHubIssueDetailTabState,
35138
35263
  resolveOrCreateProject,
35139
35264
  resolvePreviewPersonLabels,