paperclip-github-plugin 0.4.0 → 0.4.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/worker.js CHANGED
@@ -630,11 +630,13 @@ var COMMENT_ANNOTATION_ENTITY_TYPE = "paperclip-github-plugin.comment-annotation
630
630
  var AI_AUTHORED_COMMENT_FOOTER_PREFIX = "Created by a Paperclip AI agent using ";
631
631
  var HIDDEN_GITHUB_IMPORT_MARKER_PREFIX = "<!-- paperclip-github-plugin-imported-from: ";
632
632
  var HIDDEN_GITHUB_IMPORT_MARKER_SUFFIX = " -->";
633
+ var EMPTY_GITHUB_ISSUE_DESCRIPTION_PLACEHOLDER = "_No description provided on GitHub._";
633
634
  function normalizeCompanyId(value) {
634
635
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
635
636
  }
636
637
  var activeSyncPromise = null;
637
638
  var activeRunningSyncState = null;
639
+ var activeRunningSyncCompanyId;
638
640
  var activePaperclipApiAuthTokensByCompanyId = null;
639
641
  var activeExternalConfigWarningKey = null;
640
642
  var activeProjectPullRequestPageCache = /* @__PURE__ */ new Map();
@@ -710,6 +712,7 @@ var FAILED_STATUS_CONTEXT_STATES = /* @__PURE__ */ new Set(["ERROR", "FAILURE"])
710
712
  var PENDING_STATUS_CONTEXT_STATES = /* @__PURE__ */ new Set(["EXPECTED", "PENDING"]);
711
713
  var GITHUB_REPOSITORY_MAINTAINER_WARMUP_CONCURRENCY = 4;
712
714
  var GITHUB_REPOSITORY_MAINTAINER_ROLE_NAMES = /* @__PURE__ */ new Set(["admin", "maintain"]);
715
+ var GITHUB_REPOSITORY_TRUSTED_AUTHOR_ASSOCIATIONS = /* @__PURE__ */ new Set(["collaborator", "member", "owner"]);
713
716
  var GITHUB_ISSUE_STATUS_SNAPSHOT_QUERY = `
714
717
  query GitHubIssueStatusSnapshot($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
715
718
  repository(owner: $owner, name: $repo) {
@@ -1613,6 +1616,20 @@ function normalizeGitHubUserLogin(value) {
1613
1616
  function normalizeGitHubTokenRef(value) {
1614
1617
  return normalizeSecretRef(value);
1615
1618
  }
1619
+ function normalizeGitHubTokenRefs(value) {
1620
+ if (!value || typeof value !== "object") {
1621
+ return void 0;
1622
+ }
1623
+ const entries = Object.entries(value).map(([companyId, secretRef]) => {
1624
+ const normalizedCompanyId = normalizeCompanyId(companyId);
1625
+ const normalizedSecretRef = normalizeGitHubTokenRef(secretRef);
1626
+ return normalizedCompanyId && normalizedSecretRef ? [normalizedCompanyId, normalizedSecretRef] : null;
1627
+ }).filter((entry) => entry !== null);
1628
+ if (entries.length === 0) {
1629
+ return void 0;
1630
+ }
1631
+ return Object.fromEntries(entries);
1632
+ }
1616
1633
  function formatUtcTimestamp(value) {
1617
1634
  const parsed = new Date(value);
1618
1635
  if (Number.isNaN(parsed.getTime())) {
@@ -2237,7 +2254,7 @@ async function hydrateRecoveredPaperclipIssueGitHubLink(ctx, issueId, fallbackLi
2237
2254
  }
2238
2255
  let octokit;
2239
2256
  try {
2240
- octokit = await createGitHubToolOctokit(ctx);
2257
+ octokit = await createGitHubToolOctokit(ctx, fallbackLink.companyId);
2241
2258
  } catch {
2242
2259
  return null;
2243
2260
  }
@@ -2410,10 +2427,10 @@ function extractGitHubLinksFromCommentBody(body) {
2410
2427
  return [...links.values()];
2411
2428
  }
2412
2429
  async function buildToolbarSyncState(ctx, input) {
2413
- const settings = await getActiveOrCurrentSyncState(ctx);
2414
- const config = await getResolvedConfig(ctx);
2415
- const githubTokenConfigured = hasConfiguredGithubToken(settings, config);
2416
2430
  const companyId = typeof input.companyId === "string" && input.companyId.trim() ? input.companyId.trim() : void 0;
2431
+ const settings = await getActiveOrCurrentSyncState(ctx, companyId);
2432
+ const config = await getResolvedConfig(ctx);
2433
+ const githubTokenConfigured = hasConfiguredGithubToken(settings, config, companyId);
2417
2434
  const entityId = typeof input.entityId === "string" && input.entityId.trim() ? input.entityId.trim() : void 0;
2418
2435
  const entityType = typeof input.entityType === "string" && input.entityType.trim() ? input.entityType.trim() : void 0;
2419
2436
  const savedMappingCount = companyId ? getSyncableMappingsForTarget(settings.mappings, {
@@ -2454,7 +2471,7 @@ async function buildToolbarSyncState(ctx, input) {
2454
2471
  }) : [];
2455
2472
  return {
2456
2473
  kind: "issue",
2457
- visible: Boolean(link),
2474
+ visible: false,
2458
2475
  canRun: githubTokenConfigured && mappings.length > 0,
2459
2476
  label: link?.githubIssueNumber ? `Sync #${link.githubIssueNumber}` : "Sync issue",
2460
2477
  message: link ? `Sync ${link.repositoryUrl.replace(/^https:\/\/github\.com\//, "")} issue #${link.githubIssueNumber}.` : "This Paperclip issue is not linked to GitHub yet.",
@@ -2625,14 +2642,26 @@ function sanitizeSettingsForCurrentSetup(settings, setup) {
2625
2642
  }
2626
2643
  function getPublicSettings(settings) {
2627
2644
  const {
2645
+ githubTokenRefs: _githubTokenRefs,
2646
+ githubTokenLoginByCompanyId: _githubTokenLoginByCompanyId,
2628
2647
  githubTokenRef: _githubTokenRef,
2629
2648
  paperclipBoardApiTokenRefs: _paperclipBoardApiTokenRefs,
2630
2649
  paperclipBoardAccessIdentityByCompanyId: _paperclipBoardAccessIdentityByCompanyId,
2631
2650
  companyAdvancedSettingsByCompanyId: _companyAdvancedSettingsByCompanyId,
2651
+ syncStateByCompanyId: _syncStateByCompanyId,
2652
+ scheduleFrequencyMinutesByCompanyId: _scheduleFrequencyMinutesByCompanyId,
2653
+ paperclipApiBaseUrlByCompanyId: _paperclipApiBaseUrlByCompanyId,
2632
2654
  ...publicSettings
2633
2655
  } = settings;
2634
2656
  return publicSettings;
2635
2657
  }
2658
+ function getGitHubTokenLogin(settings, companyId) {
2659
+ const normalizedCompanyId = normalizeCompanyId(companyId);
2660
+ if (!normalizedCompanyId) {
2661
+ return void 0;
2662
+ }
2663
+ return normalizeOptionalString2(settings.githubTokenLoginByCompanyId?.[normalizedCompanyId]) ?? normalizeOptionalString2(settings.githubTokenLogin);
2664
+ }
2636
2665
  function getPaperclipBoardAccessIdentity(settings, companyId) {
2637
2666
  const normalizedCompanyId = normalizeCompanyId(companyId);
2638
2667
  if (!normalizedCompanyId) {
@@ -2642,11 +2671,13 @@ function getPaperclipBoardAccessIdentity(settings, companyId) {
2642
2671
  }
2643
2672
  function getPublicSettingsForScope(settings, companyId) {
2644
2673
  const publicSettings = getPublicSettings(settings);
2674
+ const githubTokenLogin = getGitHubTokenLogin(settings, companyId);
2645
2675
  const paperclipBoardAccessIdentity = getPaperclipBoardAccessIdentity(settings, companyId);
2646
2676
  return {
2647
2677
  ...publicSettings,
2648
2678
  mappings: filterMappingsByCompany(publicSettings.mappings, companyId),
2649
2679
  advancedSettings: getCompanyAdvancedSettings(settings, companyId),
2680
+ ...githubTokenLogin ? { githubTokenLogin } : {},
2650
2681
  ...paperclipBoardAccessIdentity ? { paperclipBoardAccessIdentity } : {}
2651
2682
  };
2652
2683
  }
@@ -2830,14 +2861,11 @@ function createSetupConfigurationErrorSyncState(issue, trigger) {
2830
2861
  });
2831
2862
  }
2832
2863
  }
2833
- async function saveSettingsSyncState(ctx, settings, syncState) {
2834
- const next = {
2835
- ...settings,
2836
- syncState
2837
- };
2864
+ async function saveSettingsSyncState(ctx, settings, syncState, companyId) {
2865
+ const next = upsertScopedSyncState(normalizeSettings(settings), syncState, companyId);
2838
2866
  await ctx.state.set(SETTINGS_SCOPE, next);
2839
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
2840
- return next;
2867
+ await ctx.state.set(SYNC_STATE_SCOPE, syncState);
2868
+ return materializeScopedSettings(next, null, companyId);
2841
2869
  }
2842
2870
  async function setSyncCancellationRequest(ctx, request) {
2843
2871
  if (request) {
@@ -2862,8 +2890,8 @@ function buildCancelledSyncMessage(target, progress) {
2862
2890
  const completionSummary = completedIssueCount !== void 0 && totalIssueCount !== void 0 ? ` Completed ${Math.min(completedIssueCount, totalIssueCount)} of ${totalIssueCount} issues before stopping.` : "";
2863
2891
  return `${scopeLabel} was cancelled before it finished.${completionSummary}`;
2864
2892
  }
2865
- async function createUnexpectedSyncErrorResult(ctx, trigger, error) {
2866
- const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
2893
+ async function createUnexpectedSyncErrorResult(ctx, trigger, error, companyId) {
2894
+ const settings = materializeScopedSettings(normalizeSettings(await ctx.state.get(SETTINGS_SCOPE)), null, companyId);
2867
2895
  const errorDetails = buildSyncErrorDetails(error, {
2868
2896
  phase: "configuration"
2869
2897
  });
@@ -2888,7 +2916,8 @@ async function createUnexpectedSyncErrorResult(ctx, trigger, error) {
2888
2916
  errorDetails
2889
2917
  })
2890
2918
  )
2891
- })
2919
+ }),
2920
+ companyId
2892
2921
  );
2893
2922
  }
2894
2923
  async function waitForSyncResultWithinGracePeriod(promise, timeoutMs) {
@@ -2906,15 +2935,13 @@ async function waitForSyncResultWithinGracePeriod(promise, timeoutMs) {
2906
2935
  }
2907
2936
  }
2908
2937
  }
2909
- async function getActiveOrCurrentSyncState(ctx) {
2910
- if (activeRunningSyncState?.syncState.status === "running") {
2911
- return activeRunningSyncState;
2938
+ async function getActiveOrCurrentSyncState(ctx, companyId) {
2939
+ const normalizedCompanyId = normalizeCompanyId(companyId);
2940
+ if (activeRunningSyncState?.syncState.status === "running" && activeRunningSyncCompanyId === normalizedCompanyId) {
2941
+ return materializeScopedSettings(activeRunningSyncState, null, normalizedCompanyId);
2912
2942
  }
2913
2943
  const current = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
2914
- if (current.syncState.status === "running") {
2915
- return current;
2916
- }
2917
- return current;
2944
+ return materializeScopedSettings(current, null, normalizedCompanyId);
2918
2945
  }
2919
2946
  function updateSyncFailureContext(current, next) {
2920
2947
  if ("phase" in next) {
@@ -2953,11 +2980,13 @@ function normalizeConfig(value) {
2953
2980
  return {};
2954
2981
  }
2955
2982
  const record = value;
2983
+ const githubTokenRefs = normalizeGitHubTokenRefs(record.githubTokenRefs);
2956
2984
  const githubTokenRef = normalizeGitHubTokenRef(record.githubTokenRef);
2957
2985
  const githubToken = normalizeGitHubToken(record.githubToken);
2958
2986
  const paperclipBoardApiTokenRefs = normalizePaperclipBoardApiTokenRefs(record.paperclipBoardApiTokenRefs);
2959
2987
  const paperclipApiBaseUrl = normalizePaperclipApiBaseUrl(record.paperclipApiBaseUrl);
2960
2988
  return {
2989
+ ...githubTokenRefs ? { githubTokenRefs } : {},
2961
2990
  ...githubTokenRef ? { githubTokenRef } : {},
2962
2991
  ...githubToken ? { githubToken } : {},
2963
2992
  ...paperclipBoardApiTokenRefs ? { paperclipBoardApiTokenRefs } : {},
@@ -3049,6 +3078,20 @@ function normalizePaperclipBoardApiTokenRefs(value) {
3049
3078
  }
3050
3079
  return Object.fromEntries(entries);
3051
3080
  }
3081
+ function normalizeGitHubTokenLoginByCompanyId(value) {
3082
+ if (!value || typeof value !== "object") {
3083
+ return void 0;
3084
+ }
3085
+ const entries = Object.entries(value).map(([companyId, login]) => {
3086
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3087
+ const normalizedLogin = normalizeOptionalString2(login);
3088
+ return normalizedCompanyId && normalizedLogin ? [normalizedCompanyId, normalizedLogin] : null;
3089
+ }).filter((entry) => entry !== null);
3090
+ if (entries.length === 0) {
3091
+ return void 0;
3092
+ }
3093
+ return Object.fromEntries(entries);
3094
+ }
3052
3095
  function normalizePaperclipBoardAccessIdentityByCompanyId(value) {
3053
3096
  if (!value || typeof value !== "object") {
3054
3097
  return void 0;
@@ -3082,14 +3125,14 @@ function normalizeSyncState(value) {
3082
3125
  const recentFailures = normalizeSyncFailureLogEntries(record.recentFailures);
3083
3126
  return {
3084
3127
  status: status === "running" || status === "success" || status === "error" || status === "cancelled" ? status : "idle",
3085
- message: typeof record.message === "string" ? record.message : void 0,
3086
- checkedAt: typeof record.checkedAt === "string" ? record.checkedAt : void 0,
3087
- syncedIssuesCount: typeof record.syncedIssuesCount === "number" ? record.syncedIssuesCount : void 0,
3088
- createdIssuesCount: typeof record.createdIssuesCount === "number" ? record.createdIssuesCount : void 0,
3089
- skippedIssuesCount: typeof record.skippedIssuesCount === "number" ? record.skippedIssuesCount : void 0,
3090
- erroredIssuesCount: typeof record.erroredIssuesCount === "number" ? record.erroredIssuesCount : void 0,
3091
- lastRunTrigger: lastRunTrigger === "manual" || lastRunTrigger === "schedule" || lastRunTrigger === "retry" ? lastRunTrigger : void 0,
3092
- cancelRequestedAt: typeof record.cancelRequestedAt === "string" ? record.cancelRequestedAt : void 0,
3128
+ ...typeof record.message === "string" ? { message: record.message } : {},
3129
+ ...typeof record.checkedAt === "string" ? { checkedAt: record.checkedAt } : {},
3130
+ ...typeof record.syncedIssuesCount === "number" ? { syncedIssuesCount: record.syncedIssuesCount } : {},
3131
+ ...typeof record.createdIssuesCount === "number" ? { createdIssuesCount: record.createdIssuesCount } : {},
3132
+ ...typeof record.skippedIssuesCount === "number" ? { skippedIssuesCount: record.skippedIssuesCount } : {},
3133
+ ...typeof record.erroredIssuesCount === "number" ? { erroredIssuesCount: record.erroredIssuesCount } : {},
3134
+ ...lastRunTrigger === "manual" || lastRunTrigger === "schedule" || lastRunTrigger === "retry" ? { lastRunTrigger } : {},
3135
+ ...typeof record.cancelRequestedAt === "string" ? { cancelRequestedAt: record.cancelRequestedAt } : {},
3093
3136
  ...progress ? { progress } : {},
3094
3137
  ...errorDetails ? { errorDetails } : {},
3095
3138
  ...recentFailures ? { recentFailures } : {}
@@ -3199,6 +3242,26 @@ function normalizeScheduleFrequencyMinutes(value) {
3199
3242
  }
3200
3243
  return Math.floor(numericValue);
3201
3244
  }
3245
+ function normalizeSyncStateByCompanyId(value) {
3246
+ if (!value || typeof value !== "object") {
3247
+ return void 0;
3248
+ }
3249
+ const entries = Object.entries(value).map(([companyId, syncState]) => {
3250
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3251
+ return normalizedCompanyId ? [normalizedCompanyId, normalizeSyncState(syncState)] : null;
3252
+ }).filter((entry) => entry !== null);
3253
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
3254
+ }
3255
+ function normalizeScheduleFrequencyMinutesByCompanyId(value) {
3256
+ if (!value || typeof value !== "object") {
3257
+ return void 0;
3258
+ }
3259
+ const entries = Object.entries(value).map(([companyId, scheduleFrequencyMinutes]) => {
3260
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3261
+ return normalizedCompanyId ? [normalizedCompanyId, normalizeScheduleFrequencyMinutes(scheduleFrequencyMinutes)] : null;
3262
+ }).filter((entry) => entry !== null);
3263
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
3264
+ }
3202
3265
  function normalizePaperclipApiBaseUrl(value) {
3203
3266
  if (typeof value !== "string") {
3204
3267
  return void 0;
@@ -3213,6 +3276,17 @@ function normalizePaperclipApiBaseUrl(value) {
3213
3276
  return void 0;
3214
3277
  }
3215
3278
  }
3279
+ function normalizePaperclipApiBaseUrlByCompanyId(value) {
3280
+ if (!value || typeof value !== "object") {
3281
+ return void 0;
3282
+ }
3283
+ const entries = Object.entries(value).map(([companyId, paperclipApiBaseUrl]) => {
3284
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3285
+ const normalizedPaperclipApiBaseUrl = normalizePaperclipApiBaseUrl(paperclipApiBaseUrl);
3286
+ return normalizedCompanyId && normalizedPaperclipApiBaseUrl ? [normalizedCompanyId, normalizedPaperclipApiBaseUrl] : null;
3287
+ }).filter((entry) => entry !== null);
3288
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
3289
+ }
3216
3290
  function getRuntimePaperclipApiBaseUrl() {
3217
3291
  if (typeof process === "undefined" || !process?.env) {
3218
3292
  return void 0;
@@ -3232,19 +3306,26 @@ function resolvePaperclipApiBaseUrl(...values) {
3232
3306
  }
3233
3307
  return void 0;
3234
3308
  }
3235
- function getConfiguredPaperclipApiBaseUrl(settings, config) {
3236
- return resolvePaperclipApiBaseUrl(config?.paperclipApiBaseUrl, settings?.paperclipApiBaseUrl);
3309
+ function getConfiguredPaperclipApiBaseUrl(settings, config, companyId) {
3310
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3311
+ return normalizedCompanyId ? resolvePaperclipApiBaseUrl(
3312
+ config?.paperclipApiBaseUrl,
3313
+ settings?.paperclipApiBaseUrlByCompanyId?.[normalizedCompanyId],
3314
+ settings?.paperclipApiBaseUrl
3315
+ ) : resolvePaperclipApiBaseUrl(config?.paperclipApiBaseUrl, settings?.paperclipApiBaseUrl);
3237
3316
  }
3238
- function resolveTrustedPaperclipApiBaseUrlInput(value, settings, config) {
3317
+ function resolveTrustedPaperclipApiBaseUrlInput(value, settings, config, companyId) {
3239
3318
  const runtimePaperclipApiBaseUrl = getRuntimePaperclipApiBaseUrl();
3240
3319
  if (runtimePaperclipApiBaseUrl) {
3241
3320
  return runtimePaperclipApiBaseUrl;
3242
3321
  }
3243
3322
  const requestedPaperclipApiBaseUrl = normalizePaperclipApiBaseUrl(value);
3244
3323
  const configuredPaperclipApiBaseUrl = normalizePaperclipApiBaseUrl(config?.paperclipApiBaseUrl);
3324
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3325
+ const savedCompanyPaperclipApiBaseUrl = normalizedCompanyId ? normalizePaperclipApiBaseUrl(settings?.paperclipApiBaseUrlByCompanyId?.[normalizedCompanyId]) : void 0;
3245
3326
  const savedPaperclipApiBaseUrl = normalizePaperclipApiBaseUrl(settings?.paperclipApiBaseUrl);
3246
3327
  if (!requestedPaperclipApiBaseUrl) {
3247
- return configuredPaperclipApiBaseUrl ?? savedPaperclipApiBaseUrl;
3328
+ return configuredPaperclipApiBaseUrl ?? savedCompanyPaperclipApiBaseUrl ?? savedPaperclipApiBaseUrl;
3248
3329
  }
3249
3330
  if (configuredPaperclipApiBaseUrl) {
3250
3331
  if (requestedPaperclipApiBaseUrl !== configuredPaperclipApiBaseUrl) {
@@ -3254,6 +3335,9 @@ function resolveTrustedPaperclipApiBaseUrlInput(value, settings, config) {
3254
3335
  }
3255
3336
  return configuredPaperclipApiBaseUrl;
3256
3337
  }
3338
+ if (savedCompanyPaperclipApiBaseUrl && requestedPaperclipApiBaseUrl === savedCompanyPaperclipApiBaseUrl) {
3339
+ return savedCompanyPaperclipApiBaseUrl;
3340
+ }
3257
3341
  if (savedPaperclipApiBaseUrl && requestedPaperclipApiBaseUrl === savedPaperclipApiBaseUrl) {
3258
3342
  return savedPaperclipApiBaseUrl;
3259
3343
  }
@@ -3266,7 +3350,14 @@ function normalizeSettings(value) {
3266
3350
  return DEFAULT_SETTINGS;
3267
3351
  }
3268
3352
  const record = value;
3353
+ const syncStateByCompanyId = normalizeSyncStateByCompanyId(record.syncStateByCompanyId);
3354
+ const scheduleFrequencyMinutesByCompanyId = normalizeScheduleFrequencyMinutesByCompanyId(
3355
+ record.scheduleFrequencyMinutesByCompanyId
3356
+ );
3269
3357
  const paperclipApiBaseUrl = resolvePaperclipApiBaseUrl(record.paperclipApiBaseUrl);
3358
+ const paperclipApiBaseUrlByCompanyId = normalizePaperclipApiBaseUrlByCompanyId(record.paperclipApiBaseUrlByCompanyId);
3359
+ const githubTokenRefs = normalizeGitHubTokenRefs(record.githubTokenRefs);
3360
+ const githubTokenLoginByCompanyId = normalizeGitHubTokenLoginByCompanyId(record.githubTokenLoginByCompanyId);
3270
3361
  const githubTokenRef = normalizeGitHubTokenRef(record.githubTokenRef);
3271
3362
  const githubTokenLogin = normalizeOptionalString2(record.githubTokenLogin);
3272
3363
  const paperclipBoardApiTokenRefs = normalizePaperclipBoardApiTokenRefs(record.paperclipBoardApiTokenRefs);
@@ -3277,8 +3368,13 @@ function normalizeSettings(value) {
3277
3368
  return {
3278
3369
  mappings: normalizeMappings(record.mappings),
3279
3370
  syncState: normalizeSyncState(record.syncState),
3371
+ ...syncStateByCompanyId ? { syncStateByCompanyId } : {},
3280
3372
  scheduleFrequencyMinutes: normalizeScheduleFrequencyMinutes(record.scheduleFrequencyMinutes),
3373
+ ...scheduleFrequencyMinutesByCompanyId ? { scheduleFrequencyMinutesByCompanyId } : {},
3281
3374
  ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {},
3375
+ ...paperclipApiBaseUrlByCompanyId ? { paperclipApiBaseUrlByCompanyId } : {},
3376
+ ...githubTokenRefs ? { githubTokenRefs } : {},
3377
+ ...githubTokenLoginByCompanyId ? { githubTokenLoginByCompanyId } : {},
3282
3378
  ...githubTokenRef ? { githubTokenRef } : {},
3283
3379
  ...githubTokenLogin ? { githubTokenLogin } : {},
3284
3380
  ...paperclipBoardApiTokenRefs ? { paperclipBoardApiTokenRefs } : {},
@@ -3287,6 +3383,103 @@ function normalizeSettings(value) {
3287
3383
  updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : void 0
3288
3384
  };
3289
3385
  }
3386
+ function getScopedSyncState(settings, companyId) {
3387
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3388
+ if (!normalizedCompanyId) {
3389
+ return normalizeSyncState(settings.syncState);
3390
+ }
3391
+ const scopedSyncState = settings.syncStateByCompanyId?.[normalizedCompanyId];
3392
+ return scopedSyncState ? normalizeSyncState(scopedSyncState) : normalizeSyncState(settings.syncState);
3393
+ }
3394
+ function getScopedScheduleFrequencyMinutes(settings, companyId) {
3395
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3396
+ if (!normalizedCompanyId) {
3397
+ return normalizeScheduleFrequencyMinutes(settings.scheduleFrequencyMinutes);
3398
+ }
3399
+ const scopedScheduleFrequencyMinutes = settings.scheduleFrequencyMinutesByCompanyId?.[normalizedCompanyId];
3400
+ return scopedScheduleFrequencyMinutes !== void 0 ? normalizeScheduleFrequencyMinutes(scopedScheduleFrequencyMinutes) : normalizeScheduleFrequencyMinutes(settings.scheduleFrequencyMinutes);
3401
+ }
3402
+ function materializeScopedSettings(settings, config, companyId) {
3403
+ const syncState = getScopedSyncState(settings, companyId);
3404
+ const scheduleFrequencyMinutes = getScopedScheduleFrequencyMinutes(settings, companyId);
3405
+ const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(settings, config, companyId);
3406
+ return {
3407
+ ...settings,
3408
+ syncState,
3409
+ scheduleFrequencyMinutes,
3410
+ ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {}
3411
+ };
3412
+ }
3413
+ function upsertScopedSyncState(settings, syncState, companyId) {
3414
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3415
+ if (!normalizedCompanyId) {
3416
+ return {
3417
+ ...settings,
3418
+ syncState
3419
+ };
3420
+ }
3421
+ return {
3422
+ ...settings,
3423
+ syncState,
3424
+ syncStateByCompanyId: {
3425
+ ...settings.syncStateByCompanyId ?? {},
3426
+ [normalizedCompanyId]: syncState
3427
+ }
3428
+ };
3429
+ }
3430
+ function upsertScopedScheduleFrequencyMinutes(settings, scheduleFrequencyMinutes, companyId) {
3431
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3432
+ if (!normalizedCompanyId) {
3433
+ return {
3434
+ ...settings,
3435
+ scheduleFrequencyMinutes
3436
+ };
3437
+ }
3438
+ return {
3439
+ ...settings,
3440
+ scheduleFrequencyMinutes,
3441
+ scheduleFrequencyMinutesByCompanyId: {
3442
+ ...settings.scheduleFrequencyMinutesByCompanyId ?? {},
3443
+ [normalizedCompanyId]: scheduleFrequencyMinutes
3444
+ }
3445
+ };
3446
+ }
3447
+ function upsertScopedPaperclipApiBaseUrl(settings, paperclipApiBaseUrl, companyId) {
3448
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3449
+ if (!normalizedCompanyId) {
3450
+ return {
3451
+ ...settings,
3452
+ ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {}
3453
+ };
3454
+ }
3455
+ const nextPaperclipApiBaseUrlByCompanyId = {
3456
+ ...settings.paperclipApiBaseUrlByCompanyId ?? {}
3457
+ };
3458
+ if (paperclipApiBaseUrl) {
3459
+ nextPaperclipApiBaseUrlByCompanyId[normalizedCompanyId] = paperclipApiBaseUrl;
3460
+ } else {
3461
+ delete nextPaperclipApiBaseUrlByCompanyId[normalizedCompanyId];
3462
+ }
3463
+ return {
3464
+ ...settings,
3465
+ ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {},
3466
+ ...Object.keys(nextPaperclipApiBaseUrlByCompanyId).length > 0 ? { paperclipApiBaseUrlByCompanyId: nextPaperclipApiBaseUrlByCompanyId } : {}
3467
+ };
3468
+ }
3469
+ function getScopedSyncTarget(companyId) {
3470
+ return {
3471
+ kind: "company",
3472
+ companyId,
3473
+ displayLabel: "this company"
3474
+ };
3475
+ }
3476
+ function getSyncableMappingsForScope(mappings, companyId) {
3477
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3478
+ return normalizedCompanyId ? getSyncableMappingsForTarget(mappings, getScopedSyncTarget(normalizedCompanyId)) : getSyncableMappings(mappings);
3479
+ }
3480
+ function hasAnyScopedValue(valueByCompanyId) {
3481
+ return Boolean(valueByCompanyId && Object.keys(valueByCompanyId).length > 0);
3482
+ }
3290
3483
  function normalizeImportRegistry(value) {
3291
3484
  if (!Array.isArray(value)) {
3292
3485
  return [];
@@ -3446,6 +3639,7 @@ function normalizeGitHubIssueRecord(issue) {
3446
3639
  body: typeof issue.body === "string" ? stripNullBytes(issue.body) : null,
3447
3640
  htmlUrl: issue.html_url,
3448
3641
  ...normalizeGitHubUsername(issue.user?.login) ? { authorLogin: normalizeGitHubUsername(issue.user?.login) } : {},
3642
+ ...normalizeGitHubLowercaseString(issue.author_association) ? { authorAssociation: normalizeGitHubLowercaseString(issue.author_association) } : {},
3449
3643
  labels: normalizeGitHubIssueLabels(issue.labels),
3450
3644
  state: issue.state === "closed" ? "closed" : "open",
3451
3645
  stateReason: normalizeGitHubIssueStateReason(issue.state_reason),
@@ -4226,6 +4420,22 @@ async function getGitHubIssueStatusSnapshot(octokit, repository, issueNumber, gi
4226
4420
  function buildGitHubRepositoryActorCacheKey(repository, login) {
4227
4421
  return `${repository.owner.toLowerCase()}/${repository.repo.toLowerCase()}:${login}`;
4228
4422
  }
4423
+ function getGitHubRepositoryTrustedAuthorStatusFromAssociation(authorAssociation) {
4424
+ const normalizedAssociation = normalizeGitHubLowercaseString(authorAssociation);
4425
+ if (!normalizedAssociation) {
4426
+ return void 0;
4427
+ }
4428
+ return GITHUB_REPOSITORY_TRUSTED_AUTHOR_ASSOCIATIONS.has(normalizedAssociation);
4429
+ }
4430
+ function cacheGitHubRepositoryTrustedAuthorStatusFromAssociation(repository, login, authorAssociation, cache) {
4431
+ const normalizedLogin = normalizeGitHubUserLogin(login);
4432
+ const trustedAuthorStatus = getGitHubRepositoryTrustedAuthorStatusFromAssociation(authorAssociation);
4433
+ if (!normalizedLogin || trustedAuthorStatus === void 0) {
4434
+ return trustedAuthorStatus;
4435
+ }
4436
+ cache.set(buildGitHubRepositoryActorCacheKey(repository, normalizedLogin), trustedAuthorStatus);
4437
+ return trustedAuthorStatus;
4438
+ }
4229
4439
  async function isGitHubUserRepositoryMaintainer(octokit, repository, login, cache) {
4230
4440
  const normalizedLogin = normalizeGitHubUserLogin(login);
4231
4441
  if (!normalizedLogin) {
@@ -4291,6 +4501,7 @@ async function listNewGitHubIssueCommentsSinceCount(octokit, repository, issueNu
4291
4501
  body: typeof comment.body === "string" ? stripNullBytes(comment.body) : "",
4292
4502
  url: comment.html_url ?? void 0,
4293
4503
  authorLogin: normalizeGitHubUserLogin(comment.user?.login),
4504
+ ...normalizeGitHubLowercaseString(comment.author_association) ? { authorAssociation: normalizeGitHubLowercaseString(comment.author_association) } : {},
4294
4505
  createdAt: comment.created_at ?? void 0,
4295
4506
  updatedAt: comment.updated_at ?? void 0
4296
4507
  });
@@ -4332,6 +4543,18 @@ async function hasTrustedNewGitHubIssueComment(params) {
4332
4543
  if (originalPosterLogin && authorLogin === originalPosterLogin) {
4333
4544
  return true;
4334
4545
  }
4546
+ const trustedAuthorStatus = cacheGitHubRepositoryTrustedAuthorStatusFromAssociation(
4547
+ params.repository,
4548
+ authorLogin,
4549
+ comment.authorAssociation,
4550
+ params.maintainerCache
4551
+ );
4552
+ if (trustedAuthorStatus === true) {
4553
+ return true;
4554
+ }
4555
+ if (trustedAuthorStatus === false) {
4556
+ continue;
4557
+ }
4335
4558
  unseenAuthors.add(authorLogin);
4336
4559
  }
4337
4560
  for (const authorLogin of unseenAuthors) {
@@ -4351,6 +4574,15 @@ async function isMaintainerAuthoredGitHubIssue(params) {
4351
4574
  if (!authorLogin) {
4352
4575
  return false;
4353
4576
  }
4577
+ const trustedAuthorStatus = cacheGitHubRepositoryTrustedAuthorStatusFromAssociation(
4578
+ params.repository,
4579
+ authorLogin,
4580
+ params.githubIssue.authorAssociation,
4581
+ params.maintainerCache
4582
+ );
4583
+ if (trustedAuthorStatus !== void 0) {
4584
+ return trustedAuthorStatus;
4585
+ }
4354
4586
  return isGitHubUserRepositoryMaintainer(
4355
4587
  params.octokit,
4356
4588
  params.repository,
@@ -4360,7 +4592,19 @@ async function isMaintainerAuthoredGitHubIssue(params) {
4360
4592
  }
4361
4593
  async function warmGitHubRepositoryMaintainerCache(params) {
4362
4594
  const uniqueAuthorLogins = [...new Set(
4363
- params.githubIssues.map((issue) => normalizeGitHubUserLogin(issue.authorLogin)).filter((authorLogin) => Boolean(authorLogin))
4595
+ params.githubIssues.flatMap((issue) => {
4596
+ const authorLogin = normalizeGitHubUserLogin(issue.authorLogin);
4597
+ if (!authorLogin) {
4598
+ return [];
4599
+ }
4600
+ const trustedAuthorStatus = cacheGitHubRepositoryTrustedAuthorStatusFromAssociation(
4601
+ params.repository,
4602
+ authorLogin,
4603
+ issue.authorAssociation,
4604
+ params.maintainerCache
4605
+ );
4606
+ return trustedAuthorStatus === void 0 ? [authorLogin] : [];
4607
+ })
4364
4608
  )].filter((authorLogin) => !params.maintainerCache.has(buildGitHubRepositoryActorCacheKey(params.repository, authorLogin)));
4365
4609
  if (uniqueAuthorLogins.length === 0) {
4366
4610
  return;
@@ -4575,7 +4819,9 @@ function buildPaperclipIssueDescription(issue, linkedPullRequestNumbers = []) {
4575
4819
  return normalizedBody ?? "";
4576
4820
  }
4577
4821
  if (!normalizedBody) {
4578
- return hiddenImportMarker;
4822
+ return `${EMPTY_GITHUB_ISSUE_DESCRIPTION_PLACEHOLDER}
4823
+
4824
+ ${hiddenImportMarker}`;
4579
4825
  }
4580
4826
  return `${normalizedBody}
4581
4827
 
@@ -6173,20 +6419,34 @@ async function getResolvedConfig(ctx) {
6173
6419
  ...normalizeConfig(savedConfig)
6174
6420
  };
6175
6421
  }
6176
- function getConfiguredGithubTokenSource(settings, config) {
6177
- const secretRef = normalizeGitHubTokenRef(config.githubTokenRef) ?? normalizeGitHubTokenRef(settings?.githubTokenRef);
6422
+ function getConfiguredGithubTokenSource(settings, config, companyId) {
6423
+ const normalizedCompanyId = normalizeCompanyId(companyId);
6424
+ const hasScopedGitHubTokenRefs = hasAnyScopedValue(settings?.githubTokenRefs) || hasAnyScopedValue(config.githubTokenRefs);
6425
+ const secretRef = normalizedCompanyId ? normalizeSecretRef(config.githubTokenRefs?.[normalizedCompanyId]) ?? normalizeSecretRef(settings?.githubTokenRefs?.[normalizedCompanyId]) ?? (!hasScopedGitHubTokenRefs ? normalizeGitHubTokenRef(config.githubTokenRef) ?? normalizeGitHubTokenRef(settings?.githubTokenRef) : void 0) : normalizeGitHubTokenRef(config.githubTokenRef) ?? normalizeGitHubTokenRef(settings?.githubTokenRef) ?? (() => {
6426
+ const configuredRefs = [
6427
+ ...Object.values(config.githubTokenRefs ?? {}),
6428
+ ...Object.values(settings?.githubTokenRefs ?? {})
6429
+ ].map((value) => normalizeGitHubTokenRef(value)).filter((value) => Boolean(value));
6430
+ const uniqueRefs = [...new Set(configuredRefs)];
6431
+ return uniqueRefs.length === 1 ? uniqueRefs[0] : void 0;
6432
+ })();
6178
6433
  if (secretRef) {
6179
6434
  return { secretRef };
6180
6435
  }
6181
- const token = normalizeGitHubToken(config.githubToken);
6436
+ const token = !normalizedCompanyId || !hasScopedGitHubTokenRefs ? normalizeGitHubToken(config.githubToken) : void 0;
6182
6437
  return token ? { token } : {};
6183
6438
  }
6184
- function getConfiguredGithubTokenRef(settings, config) {
6185
- return getConfiguredGithubTokenSource(settings, config).secretRef;
6186
- }
6187
- function hasConfiguredGithubToken(settings, config) {
6188
- const configuredTokenSource = getConfiguredGithubTokenSource(settings, config);
6189
- return Boolean(configuredTokenSource.secretRef ?? configuredTokenSource.token);
6439
+ function hasConfiguredGithubToken(settings, config, companyId) {
6440
+ const configuredTokenSource = getConfiguredGithubTokenSource(settings, config, companyId);
6441
+ if (configuredTokenSource.secretRef ?? configuredTokenSource.token) {
6442
+ return true;
6443
+ }
6444
+ if (normalizeCompanyId(companyId)) {
6445
+ return false;
6446
+ }
6447
+ return Boolean(
6448
+ settings?.githubTokenRefs && Object.keys(settings.githubTokenRefs).length > 0 || config.githubTokenRefs && Object.keys(config.githubTokenRefs).length > 0
6449
+ );
6190
6450
  }
6191
6451
  function getSavedPaperclipBoardApiTokenRef(settings, companyId) {
6192
6452
  if (!companyId) {
@@ -6269,7 +6529,7 @@ async function resolvePaperclipApiAuthTokens(ctx, settings, config, mappings) {
6269
6529
  async function resolveGithubToken(ctx, options = {}) {
6270
6530
  const settings = options.settings ?? normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
6271
6531
  const config = options.config ?? await getResolvedConfig(ctx);
6272
- const configuredTokenSource = getConfiguredGithubTokenSource(settings, config);
6532
+ const configuredTokenSource = getConfiguredGithubTokenSource(settings, config, options.companyId);
6273
6533
  if (configuredTokenSource.secretRef) {
6274
6534
  return ctx.secrets.resolve(configuredTokenSource.secretRef);
6275
6535
  }
@@ -6347,8 +6607,8 @@ async function executeGitHubTool(fn) {
6347
6607
  return buildToolErrorResult(error);
6348
6608
  }
6349
6609
  }
6350
- async function createGitHubToolOctokit(ctx) {
6351
- const token = (await resolveGithubToken(ctx)).trim();
6610
+ async function createGitHubToolOctokit(ctx, companyId) {
6611
+ const token = (await resolveGithubToken(ctx, { companyId })).trim();
6352
6612
  if (!token) {
6353
6613
  throw new Error(MISSING_GITHUB_TOKEN_SYNC_MESSAGE);
6354
6614
  }
@@ -6888,6 +7148,7 @@ async function listAllGitHubIssueComments(octokit, repository, issueNumber) {
6888
7148
  body: typeof comment.body === "string" ? stripNullBytes(comment.body) : "",
6889
7149
  url: comment.html_url ?? void 0,
6890
7150
  authorLogin: normalizeGitHubUserLogin(comment.user?.login),
7151
+ ...normalizeGitHubLowercaseString(comment.author_association) ? { authorAssociation: normalizeGitHubLowercaseString(comment.author_association) } : {},
6891
7152
  authorUrl: comment.user?.html_url ?? void 0,
6892
7153
  authorAvatarUrl: comment.user?.avatar_url ?? void 0,
6893
7154
  createdAt: comment.created_at ?? void 0,
@@ -7770,7 +8031,7 @@ async function getOrLoadCachedProjectPullRequestMetricsEntry(ctx, scope, octokit
7770
8031
  return inFlightMetrics;
7771
8032
  }
7772
8033
  const loadMetricsPromise = (async () => {
7773
- const resolvedOctokit = octokit ?? await createGitHubToolOctokit(ctx);
8034
+ const resolvedOctokit = octokit ?? await createGitHubToolOctokit(ctx, scope.companyId);
7774
8035
  const metrics = await listProjectPullRequestMetrics(resolvedOctokit, scope);
7775
8036
  return cacheProjectPullRequestMetricsEntry(scope, metrics);
7776
8037
  })();
@@ -7808,7 +8069,7 @@ async function getOrLoadCachedProjectPullRequestCount(ctx, scope, octokit) {
7808
8069
  return inFlightCount;
7809
8070
  }
7810
8071
  const loadCountPromise = (async () => {
7811
- const resolvedOctokit = octokit ?? await createGitHubToolOctokit(ctx);
8072
+ const resolvedOctokit = octokit ?? await createGitHubToolOctokit(ctx, scope.companyId);
7812
8073
  const totalOpenPullRequests = await listProjectPullRequestCount(resolvedOctokit, scope);
7813
8074
  return setCacheValue(
7814
8075
  activeProjectPullRequestCountCache,
@@ -7920,7 +8181,7 @@ async function getOrLoadProjectPullRequestSummaryRecordsForNumbers(ctx, scope, p
7920
8181
  }
7921
8182
  const missingPullRequestNumbers = normalizedPullRequestNumbers.filter((pullRequestNumber) => !recordsByNumber.has(pullRequestNumber));
7922
8183
  if (missingPullRequestNumbers.length > 0) {
7923
- const resolvedOctokit = octokit ?? await createGitHubToolOctokit(ctx);
8184
+ const resolvedOctokit = octokit ?? await createGitHubToolOctokit(ctx, scope.companyId);
7924
8185
  const loadedRecords = await listProjectPullRequestSummaryRecordsByNumbers(
7925
8186
  ctx,
7926
8187
  resolvedOctokit,
@@ -8030,7 +8291,7 @@ async function buildProjectPullRequestsPageData(ctx, input) {
8030
8291
  }
8031
8292
  const scope = await requireProjectPullRequestScope(ctx, input, projectMappings);
8032
8293
  const config = await getResolvedConfig(ctx);
8033
- if (!hasConfiguredGithubToken(settings, config)) {
8294
+ if (!hasConfiguredGithubToken(settings, config, scope.companyId)) {
8034
8295
  return {
8035
8296
  status: "missing_token",
8036
8297
  projectId,
@@ -8049,7 +8310,7 @@ async function buildProjectPullRequestsPageData(ctx, input) {
8049
8310
  };
8050
8311
  }
8051
8312
  try {
8052
- const octokit = await createGitHubToolOctokit(ctx);
8313
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8053
8314
  const pageCacheKey = buildProjectPullRequestPageCacheKey(scope, filter, pageIndex, cursor);
8054
8315
  const cachedPage = getFreshCacheValue(activeProjectPullRequestPageCache, pageCacheKey);
8055
8316
  if (cachedPage) {
@@ -8221,7 +8482,7 @@ async function buildProjectPullRequestMetricsData(ctx, input) {
8221
8482
  };
8222
8483
  }
8223
8484
  const config = await getResolvedConfig(ctx);
8224
- if (!hasConfiguredGithubToken(settings, config)) {
8485
+ if (!hasConfiguredGithubToken(settings, config, companyId)) {
8225
8486
  return {
8226
8487
  status: "missing_token",
8227
8488
  projectId,
@@ -8270,7 +8531,7 @@ async function buildProjectPullRequestCountData(ctx, input) {
8270
8531
  };
8271
8532
  }
8272
8533
  const config = await getResolvedConfig(ctx);
8273
- if (!hasConfiguredGithubToken(settings, config)) {
8534
+ if (!hasConfiguredGithubToken(settings, config, companyId)) {
8274
8535
  return {
8275
8536
  status: "missing_token",
8276
8537
  projectId,
@@ -8307,7 +8568,7 @@ async function buildSettingsTokenPermissionAuditData(ctx, input) {
8307
8568
  }
8308
8569
  const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
8309
8570
  const config = await getResolvedConfig(ctx);
8310
- if (!hasConfiguredGithubToken(settings, config)) {
8571
+ if (!hasConfiguredGithubToken(settings, config, requestedCompanyId)) {
8311
8572
  return {
8312
8573
  status: "missing_token",
8313
8574
  allRequiredPermissionsGranted: false,
@@ -8328,7 +8589,7 @@ async function buildSettingsTokenPermissionAuditData(ctx, input) {
8328
8589
  };
8329
8590
  }
8330
8591
  try {
8331
- const octokit = await createGitHubToolOctokit(ctx);
8592
+ const octokit = await createGitHubToolOctokit(ctx, requestedCompanyId);
8332
8593
  const repositories = await Promise.all(
8333
8594
  [
8334
8595
  ...new Map(
@@ -8397,7 +8658,7 @@ async function buildProjectPullRequestDetailData(ctx, input) {
8397
8658
  buildProjectPullRequestSummaryRecordCacheKey(scope, pullRequestNumber)
8398
8659
  );
8399
8660
  const cachedLinkedIssue = cachedSummaryRecord ? getLinkedPaperclipIssueFromProjectPullRequestRecord(cachedSummaryRecord) : void 0;
8400
- const octokit = await createGitHubToolOctokit(ctx);
8661
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8401
8662
  const response = await octokit.rest.pulls.get({
8402
8663
  owner: scope.repository.owner,
8403
8664
  repo: scope.repository.repo,
@@ -8528,7 +8789,7 @@ async function createProjectPullRequestPaperclipIssue(ctx, input) {
8528
8789
  throw new Error("This Paperclip runtime does not expose plugin issue creation yet.");
8529
8790
  }
8530
8791
  const scope = await requireProjectPullRequestScope(ctx, input);
8531
- const octokit = await createGitHubToolOctokit(ctx);
8792
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8532
8793
  const pullRequestResponse = await octokit.rest.pulls.get({
8533
8794
  owner: scope.repository.owner,
8534
8795
  repo: scope.repository.repo,
@@ -8602,7 +8863,7 @@ async function updateProjectPullRequestBranch(ctx, input) {
8602
8863
  throw new Error("pullRequestNumber is required.");
8603
8864
  }
8604
8865
  const scope = await requireProjectPullRequestScope(ctx, input);
8605
- const octokit = await createGitHubToolOctokit(ctx);
8866
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8606
8867
  const pullRequestResponse = await octokit.rest.pulls.get({
8607
8868
  owner: scope.repository.owner,
8608
8869
  repo: scope.repository.repo,
@@ -8665,7 +8926,7 @@ async function mergeProjectPullRequest(ctx, input) {
8665
8926
  throw new Error("pullRequestNumber is required.");
8666
8927
  }
8667
8928
  const scope = await requireProjectPullRequestScope(ctx, input);
8668
- const octokit = await createGitHubToolOctokit(ctx);
8929
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8669
8930
  const response = await octokit.rest.pulls.merge({
8670
8931
  owner: scope.repository.owner,
8671
8932
  repo: scope.repository.repo,
@@ -8689,7 +8950,7 @@ async function closeProjectPullRequest(ctx, input) {
8689
8950
  throw new Error("pullRequestNumber is required.");
8690
8951
  }
8691
8952
  const scope = await requireProjectPullRequestScope(ctx, input);
8692
- const octokit = await createGitHubToolOctokit(ctx);
8953
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8693
8954
  const response = await octokit.rest.pulls.update({
8694
8955
  owner: scope.repository.owner,
8695
8956
  repo: scope.repository.repo,
@@ -8718,7 +8979,7 @@ async function addProjectPullRequestComment(ctx, input) {
8718
8979
  throw new Error("Comment body cannot be empty.");
8719
8980
  }
8720
8981
  const scope = await requireProjectPullRequestScope(ctx, input);
8721
- const octokit = await createGitHubToolOctokit(ctx);
8982
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8722
8983
  const response = await createProjectPullRequestGitHubComment(octokit, scope, pullRequestNumber, body);
8723
8984
  invalidateProjectPullRequestCaches(scope);
8724
8985
  return {
@@ -8775,7 +9036,7 @@ async function requestProjectPullRequestCopilotAction(ctx, input) {
8775
9036
  throw new Error('action must be one of "fix_ci", "rebase", "address_review_feedback", or "review".');
8776
9037
  }
8777
9038
  const scope = await requireProjectPullRequestScope(ctx, input);
8778
- const octokit = await createGitHubToolOctokit(ctx);
9039
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8779
9040
  const pullRequestResponse = await octokit.rest.pulls.get({
8780
9041
  owner: scope.repository.owner,
8781
9042
  repo: scope.repository.repo,
@@ -8889,7 +9150,7 @@ async function reviewProjectPullRequest(ctx, input) {
8889
9150
  }
8890
9151
  const body = typeof input.body === "string" ? input.body.trim() : "";
8891
9152
  const scope = await requireProjectPullRequestScope(ctx, input);
8892
- const octokit = await createGitHubToolOctokit(ctx);
9153
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8893
9154
  let response;
8894
9155
  try {
8895
9156
  response = await octokit.rest.pulls.createReview({
@@ -8936,7 +9197,7 @@ async function rerunProjectPullRequestCi(ctx, input) {
8936
9197
  throw new Error("pullRequestNumber is required.");
8937
9198
  }
8938
9199
  const scope = await requireProjectPullRequestScope(ctx, input);
8939
- const octokit = await createGitHubToolOctokit(ctx);
9200
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8940
9201
  const pullRequestResponse = await octokit.rest.pulls.get({
8941
9202
  owner: scope.repository.owner,
8942
9203
  repo: scope.repository.repo,
@@ -9122,12 +9383,25 @@ function shouldRunScheduledSync(settings, scheduledAt) {
9122
9383
  }
9123
9384
  return now - lastCheckedAt >= settings.scheduleFrequencyMinutes * 6e4;
9124
9385
  }
9386
+ function listScheduledSyncTargets(settings) {
9387
+ const companyIds = [
9388
+ ...new Set(
9389
+ settings.mappings.map((mapping) => normalizeCompanyId(mapping.companyId)).filter((companyId) => Boolean(companyId))
9390
+ )
9391
+ ];
9392
+ if (companyIds.length === 0) {
9393
+ return settings.mappings.length > 0 ? [void 0] : [];
9394
+ }
9395
+ return companyIds.map((companyId) => getScopedSyncTarget(companyId));
9396
+ }
9125
9397
  async function performSync(ctx, trigger, options = {}) {
9126
- const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
9398
+ const targetCompanyId = normalizeCompanyId(options.target?.companyId);
9399
+ const baseSettings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
9127
9400
  const config = await getResolvedConfig(ctx);
9401
+ const settings = materializeScopedSettings(baseSettings, config, targetCompanyId);
9128
9402
  const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
9129
- const token = typeof options.resolvedToken === "string" ? options.resolvedToken : await resolveGithubToken(ctx);
9130
- const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(settings, config);
9403
+ const token = typeof options.resolvedToken === "string" ? options.resolvedToken : await resolveGithubToken(ctx, { companyId: targetCompanyId });
9404
+ const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(baseSettings, config, targetCompanyId);
9131
9405
  const mappings = getSyncableMappingsForTarget(settings.mappings, options.target);
9132
9406
  activePaperclipApiAuthTokensByCompanyId = null;
9133
9407
  const failureContext = {
@@ -9138,18 +9412,14 @@ async function performSync(ctx, trigger, options = {}) {
9138
9412
  ...settings,
9139
9413
  syncState: createSetupConfigurationErrorSyncState("missing_token", trigger)
9140
9414
  };
9141
- await ctx.state.set(SETTINGS_SCOPE, next);
9142
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9143
- return next;
9415
+ return saveSettingsSyncState(ctx, settings, next.syncState, targetCompanyId);
9144
9416
  }
9145
9417
  if (mappings.length === 0) {
9146
9418
  const next = {
9147
9419
  ...settings,
9148
9420
  syncState: createSetupConfigurationErrorSyncState("missing_mapping", trigger)
9149
9421
  };
9150
- await ctx.state.set(SETTINGS_SCOPE, next);
9151
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9152
- return next;
9422
+ return saveSettingsSyncState(ctx, settings, next.syncState, targetCompanyId);
9153
9423
  }
9154
9424
  const mappingsMissingBoardAccess = getMappingsMissingPaperclipBoardAccess(settings, config, mappings);
9155
9425
  if (mappingsMissingBoardAccess.length > 0 && await detectPaperclipBoardAccessRequirement(paperclipApiBaseUrl)) {
@@ -9157,9 +9427,7 @@ async function performSync(ctx, trigger, options = {}) {
9157
9427
  ...settings,
9158
9428
  syncState: createSetupConfigurationErrorSyncState("missing_board_access", trigger)
9159
9429
  };
9160
- await ctx.state.set(SETTINGS_SCOPE, next);
9161
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9162
- return next;
9430
+ return saveSettingsSyncState(ctx, settings, next.syncState, targetCompanyId);
9163
9431
  }
9164
9432
  if (!ctx.issues || typeof ctx.issues.create !== "function") {
9165
9433
  const errorDetails = {
@@ -9185,9 +9453,7 @@ async function performSync(ctx, trigger, options = {}) {
9185
9453
  )
9186
9454
  })
9187
9455
  };
9188
- await ctx.state.set(SETTINGS_SCOPE, next);
9189
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9190
- return next;
9456
+ return saveSettingsSyncState(ctx, settings, next.syncState, targetCompanyId);
9191
9457
  }
9192
9458
  activePaperclipApiAuthTokensByCompanyId = await resolvePaperclipApiAuthTokens(ctx, settings, config, mappings);
9193
9459
  const octokit = new Octokit({ auth: token });
@@ -9255,7 +9521,8 @@ async function performSync(ctx, trigger, options = {}) {
9255
9521
  erroredIssuesCount: recoverableFailures.length,
9256
9522
  progress,
9257
9523
  recentFailures
9258
- })
9524
+ }),
9525
+ targetCompanyId
9259
9526
  );
9260
9527
  activeRunningSyncState = currentSettings;
9261
9528
  lastProgressPersistedAt = now;
@@ -9592,10 +9859,9 @@ async function performSync(ctx, trigger, options = {}) {
9592
9859
  recentFailures: buildRecentSyncFailureLogEntries(recoverableFailures)
9593
9860
  })
9594
9861
  };
9595
- await ctx.state.set(SETTINGS_SCOPE, next2);
9596
- await ctx.state.set(SYNC_STATE_SCOPE, next2.syncState);
9862
+ await saveSettingsSyncState(ctx, currentSettings, next2.syncState, targetCompanyId);
9597
9863
  await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextRegistry);
9598
- return next2;
9864
+ return materializeScopedSettings(next2, config, targetCompanyId);
9599
9865
  }
9600
9866
  const next = {
9601
9867
  ...currentSettings,
@@ -9651,10 +9917,9 @@ async function performSync(ctx, trigger, options = {}) {
9651
9917
  )
9652
9918
  })
9653
9919
  };
9654
- await ctx.state.set(SETTINGS_SCOPE, next);
9655
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9920
+ await saveSettingsSyncState(ctx, currentSettings, next.syncState, targetCompanyId);
9656
9921
  await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextRegistry);
9657
- return next;
9922
+ return materializeScopedSettings(next, config, targetCompanyId);
9658
9923
  }
9659
9924
  }
9660
9925
  async function startSync(ctx, trigger, options = {}) {
@@ -9672,26 +9937,32 @@ async function startSync(ctx, trigger, options = {}) {
9672
9937
  getResolvedConfig(ctx),
9673
9938
  ctx.state.get(SETTINGS_SCOPE).then((value) => normalizeSettings(value))
9674
9939
  ]);
9940
+ const targetCompanyId = normalizeCompanyId(options.target?.companyId);
9675
9941
  const token = await resolveGithubToken(ctx, {
9942
+ companyId: targetCompanyId,
9676
9943
  config,
9677
9944
  settings: persistedSettings
9678
9945
  }).catch(() => "");
9679
- let currentSettings = sanitizeSettingsForCurrentSetup(persistedSettings, {
9946
+ let currentSettings = sanitizeSettingsForCurrentSetup(materializeScopedSettings(persistedSettings, config, targetCompanyId), {
9680
9947
  hasToken: Boolean(token.trim()),
9681
- hasMappings: getSyncableMappings(persistedSettings.mappings).length > 0
9948
+ hasMappings: getSyncableMappingsForScope(persistedSettings.mappings, targetCompanyId).length > 0
9682
9949
  });
9683
- const nextPaperclipApiBaseUrl = trigger === "manual" ? resolveTrustedPaperclipApiBaseUrlInput(options.paperclipApiBaseUrl, currentSettings, config) : getConfiguredPaperclipApiBaseUrl(currentSettings, config);
9950
+ const nextPaperclipApiBaseUrl = trigger === "manual" ? resolveTrustedPaperclipApiBaseUrlInput(options.paperclipApiBaseUrl, persistedSettings, config, targetCompanyId) : getConfiguredPaperclipApiBaseUrl(persistedSettings, config, targetCompanyId);
9684
9951
  if (nextPaperclipApiBaseUrl !== currentSettings.paperclipApiBaseUrl) {
9685
- currentSettings = {
9686
- ...currentSettings,
9687
- ...nextPaperclipApiBaseUrl ? { paperclipApiBaseUrl: nextPaperclipApiBaseUrl } : {},
9688
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
9689
- };
9690
- await ctx.state.set(SETTINGS_SCOPE, currentSettings);
9952
+ const nextPersistedSettings = upsertScopedPaperclipApiBaseUrl(
9953
+ {
9954
+ ...persistedSettings,
9955
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
9956
+ },
9957
+ nextPaperclipApiBaseUrl,
9958
+ targetCompanyId
9959
+ );
9960
+ await ctx.state.set(SETTINGS_SCOPE, nextPersistedSettings);
9691
9961
  await ctx.state.set(SYNC_STATE_SCOPE, currentSettings.syncState);
9962
+ currentSettings = materializeScopedSettings(nextPersistedSettings, config, targetCompanyId);
9692
9963
  }
9693
- if (currentSettings !== persistedSettings) {
9694
- await saveSettingsSyncState(ctx, currentSettings, currentSettings.syncState);
9964
+ if (JSON.stringify(currentSettings.syncState) !== JSON.stringify(getScopedSyncState(persistedSettings, targetCompanyId))) {
9965
+ await saveSettingsSyncState(ctx, persistedSettings, currentSettings.syncState, targetCompanyId);
9695
9966
  }
9696
9967
  if (getActiveGitHubRateLimitPause(currentSettings.syncState)) {
9697
9968
  return currentSettings;
@@ -9720,7 +9991,8 @@ async function startSync(ctx, trigger, options = {}) {
9720
9991
  ...currentSettings,
9721
9992
  syncState
9722
9993
  };
9723
- return saveSettingsSyncState(ctx, currentSettings, syncState);
9994
+ activeRunningSyncCompanyId = targetCompanyId;
9995
+ return saveSettingsSyncState(ctx, currentSettings, syncState, targetCompanyId);
9724
9996
  })();
9725
9997
  activeSyncPromise = (async () => {
9726
9998
  try {
@@ -9730,11 +10002,12 @@ async function startSync(ctx, trigger, options = {}) {
9730
10002
  target: options.target
9731
10003
  });
9732
10004
  } catch (error) {
9733
- return await createUnexpectedSyncErrorResult(ctx, trigger, error);
10005
+ return await createUnexpectedSyncErrorResult(ctx, trigger, error, targetCompanyId);
9734
10006
  } finally {
9735
10007
  await setSyncCancellationRequest(ctx, null);
9736
10008
  activePaperclipApiAuthTokensByCompanyId = null;
9737
10009
  activeRunningSyncState = null;
10010
+ activeRunningSyncCompanyId = void 0;
9738
10011
  activeSyncPromise = null;
9739
10012
  }
9740
10013
  })();
@@ -9763,7 +10036,7 @@ function registerGitHubAgentTools(ctx) {
9763
10036
  getGitHubAgentToolDeclaration("search_repository_items"),
9764
10037
  async (params, runCtx) => executeGitHubTool(async () => {
9765
10038
  const input = getToolInputRecord(params);
9766
- const octokit = await createGitHubToolOctokit(ctx);
10039
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
9767
10040
  const repository = await resolveGitHubToolRepository(ctx, runCtx, input);
9768
10041
  const rawQuery = normalizeOptionalToolString(input.query);
9769
10042
  if (!rawQuery) {
@@ -9832,7 +10105,7 @@ function registerGitHubAgentTools(ctx) {
9832
10105
  async (params, runCtx) => executeGitHubTool(async () => {
9833
10106
  const input = getToolInputRecord(params);
9834
10107
  const target = await resolveGitHubIssueToolTarget(ctx, runCtx, input);
9835
- const octokit = await createGitHubToolOctokit(ctx);
10108
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
9836
10109
  const response = await octokit.rest.issues.get({
9837
10110
  owner: target.repository.owner,
9838
10111
  repo: target.repository.repo,
@@ -9879,7 +10152,7 @@ function registerGitHubAgentTools(ctx) {
9879
10152
  async (params, runCtx) => executeGitHubTool(async () => {
9880
10153
  const input = getToolInputRecord(params);
9881
10154
  const target = await resolveGitHubIssueToolTarget(ctx, runCtx, input);
9882
- const octokit = await createGitHubToolOctokit(ctx);
10155
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
9883
10156
  const comments = await listAllGitHubIssueComments(octokit, target.repository, target.issueNumber);
9884
10157
  return buildToolSuccessResult(
9885
10158
  `Loaded ${comments.length} GitHub ${comments.length === 1 ? "comment" : "comments"} from issue #${target.issueNumber}.`,
@@ -9897,7 +10170,7 @@ function registerGitHubAgentTools(ctx) {
9897
10170
  async (params, runCtx) => executeGitHubTool(async () => {
9898
10171
  const input = getToolInputRecord(params);
9899
10172
  const target = await resolveGitHubIssueToolTarget(ctx, runCtx, input);
9900
- const octokit = await createGitHubToolOctokit(ctx);
10173
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
9901
10174
  const currentResponse = await octokit.rest.issues.get({
9902
10175
  owner: target.repository.owner,
9903
10176
  repo: target.repository.repo,
@@ -9969,7 +10242,7 @@ function registerGitHubAgentTools(ctx) {
9969
10242
  async (params, runCtx) => executeGitHubTool(async () => {
9970
10243
  const input = getToolInputRecord(params);
9971
10244
  const target = await resolveGitHubIssueToolTarget(ctx, runCtx, input);
9972
- const octokit = await createGitHubToolOctokit(ctx);
10245
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
9973
10246
  const body = appendAiAuthorshipFooter(String(input.body ?? ""), normalizeOptionalToolString(input.llmModel) ?? "");
9974
10247
  const response = await octokit.rest.issues.createComment({
9975
10248
  owner: target.repository.owner,
@@ -10008,7 +10281,7 @@ function registerGitHubAgentTools(ctx) {
10008
10281
  if (!head || !base || !title) {
10009
10282
  throw new Error("head, base, and title are required.");
10010
10283
  }
10011
- const octokit = await createGitHubToolOctokit(ctx);
10284
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10012
10285
  const response = await octokit.rest.pulls.create({
10013
10286
  owner: repository.owner,
10014
10287
  repo: repository.repo,
@@ -10045,7 +10318,7 @@ function registerGitHubAgentTools(ctx) {
10045
10318
  async (params, runCtx) => executeGitHubTool(async () => {
10046
10319
  const input = getToolInputRecord(params);
10047
10320
  const target = await resolveGitHubPullRequestToolTarget(ctx, runCtx, input);
10048
- const octokit = await createGitHubToolOctokit(ctx);
10321
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10049
10322
  const response = await octokit.rest.pulls.get({
10050
10323
  owner: target.repository.owner,
10051
10324
  repo: target.repository.repo,
@@ -10093,7 +10366,7 @@ function registerGitHubAgentTools(ctx) {
10093
10366
  async (params, runCtx) => executeGitHubTool(async () => {
10094
10367
  const input = getToolInputRecord(params);
10095
10368
  const target = await resolveGitHubPullRequestToolTarget(ctx, runCtx, input);
10096
- const octokit = await createGitHubToolOctokit(ctx);
10369
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10097
10370
  let currentResponse = await octokit.rest.pulls.get({
10098
10371
  owner: target.repository.owner,
10099
10372
  repo: target.repository.repo,
@@ -10158,7 +10431,7 @@ function registerGitHubAgentTools(ctx) {
10158
10431
  async (params, runCtx) => executeGitHubTool(async () => {
10159
10432
  const input = getToolInputRecord(params);
10160
10433
  const target = await resolveGitHubPullRequestToolTarget(ctx, runCtx, input);
10161
- const octokit = await createGitHubToolOctokit(ctx);
10434
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10162
10435
  const files = await listAllPullRequestFiles(octokit, target.repository, target.pullRequestNumber);
10163
10436
  return buildToolSuccessResult(
10164
10437
  `Loaded ${files.length} changed ${files.length === 1 ? "file" : "files"} from pull request #${target.pullRequestNumber}.`,
@@ -10176,7 +10449,7 @@ function registerGitHubAgentTools(ctx) {
10176
10449
  async (params, runCtx) => executeGitHubTool(async () => {
10177
10450
  const input = getToolInputRecord(params);
10178
10451
  const target = await resolveGitHubPullRequestToolTarget(ctx, runCtx, input);
10179
- const octokit = await createGitHubToolOctokit(ctx);
10452
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10180
10453
  const pullRequestResponse = await octokit.rest.pulls.get({
10181
10454
  owner: target.repository.owner,
10182
10455
  repo: target.repository.repo,
@@ -10274,7 +10547,7 @@ function registerGitHubAgentTools(ctx) {
10274
10547
  async (params, runCtx) => executeGitHubTool(async () => {
10275
10548
  const input = getToolInputRecord(params);
10276
10549
  const target = await resolveGitHubPullRequestToolTarget(ctx, runCtx, input);
10277
- const octokit = await createGitHubToolOctokit(ctx);
10550
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10278
10551
  const threads = await listDetailedPullRequestReviewThreads(octokit, target.repository, target.pullRequestNumber);
10279
10552
  return buildToolSuccessResult(
10280
10553
  `Loaded ${threads.length} review ${threads.length === 1 ? "thread" : "threads"} from pull request #${target.pullRequestNumber}.`,
@@ -10289,14 +10562,14 @@ function registerGitHubAgentTools(ctx) {
10289
10562
  ctx.tools.register(
10290
10563
  "reply_to_review_thread",
10291
10564
  getGitHubAgentToolDeclaration("reply_to_review_thread"),
10292
- async (params) => executeGitHubTool(async () => {
10565
+ async (params, runCtx) => executeGitHubTool(async () => {
10293
10566
  const input = getToolInputRecord(params);
10294
10567
  const threadId = normalizeOptionalToolString(input.threadId);
10295
10568
  if (!threadId) {
10296
10569
  throw new Error("threadId is required.");
10297
10570
  }
10298
10571
  const body = appendAiAuthorshipFooter(String(input.body ?? ""), normalizeOptionalToolString(input.llmModel) ?? "");
10299
- const octokit = await createGitHubToolOctokit(ctx);
10572
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10300
10573
  const response = await octokit.graphql(
10301
10574
  GITHUB_ADD_PULL_REQUEST_REVIEW_THREAD_REPLY_MUTATION,
10302
10575
  {
@@ -10325,13 +10598,13 @@ function registerGitHubAgentTools(ctx) {
10325
10598
  ctx.tools.register(
10326
10599
  "resolve_review_thread",
10327
10600
  getGitHubAgentToolDeclaration("resolve_review_thread"),
10328
- async (params) => executeGitHubTool(async () => {
10601
+ async (params, runCtx) => executeGitHubTool(async () => {
10329
10602
  const input = getToolInputRecord(params);
10330
10603
  const threadId = normalizeOptionalToolString(input.threadId);
10331
10604
  if (!threadId) {
10332
10605
  throw new Error("threadId is required.");
10333
10606
  }
10334
- const octokit = await createGitHubToolOctokit(ctx);
10607
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10335
10608
  const response = await octokit.graphql(
10336
10609
  GITHUB_RESOLVE_REVIEW_THREAD_MUTATION,
10337
10610
  {
@@ -10356,13 +10629,13 @@ function registerGitHubAgentTools(ctx) {
10356
10629
  ctx.tools.register(
10357
10630
  "unresolve_review_thread",
10358
10631
  getGitHubAgentToolDeclaration("unresolve_review_thread"),
10359
- async (params) => executeGitHubTool(async () => {
10632
+ async (params, runCtx) => executeGitHubTool(async () => {
10360
10633
  const input = getToolInputRecord(params);
10361
10634
  const threadId = normalizeOptionalToolString(input.threadId);
10362
10635
  if (!threadId) {
10363
10636
  throw new Error("threadId is required.");
10364
10637
  }
10365
- const octokit = await createGitHubToolOctokit(ctx);
10638
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10366
10639
  const response = await octokit.graphql(
10367
10640
  GITHUB_UNRESOLVE_REVIEW_THREAD_MUTATION,
10368
10641
  {
@@ -10395,7 +10668,7 @@ function registerGitHubAgentTools(ctx) {
10395
10668
  if (userReviewers.length === 0 && teamReviewers.length === 0) {
10396
10669
  throw new Error("Provide at least one user reviewer or team reviewer.");
10397
10670
  }
10398
- const octokit = await createGitHubToolOctokit(ctx);
10671
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10399
10672
  const response = await octokit.rest.pulls.requestReviewers({
10400
10673
  owner: target.repository.owner,
10401
10674
  repo: target.repository.repo,
@@ -10420,13 +10693,13 @@ function registerGitHubAgentTools(ctx) {
10420
10693
  ctx.tools.register(
10421
10694
  "list_organization_projects",
10422
10695
  getGitHubAgentToolDeclaration("list_organization_projects"),
10423
- async (params) => executeGitHubTool(async () => {
10696
+ async (params, runCtx) => executeGitHubTool(async () => {
10424
10697
  const input = getToolInputRecord(params);
10425
10698
  const organization = normalizeOptionalToolString(input.organization);
10426
10699
  if (!organization) {
10427
10700
  throw new Error("organization is required.");
10428
10701
  }
10429
- const octokit = await createGitHubToolOctokit(ctx);
10702
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10430
10703
  const projects = await listGitHubOrganizationProjects(octokit, organization, {
10431
10704
  includeClosed: input.includeClosed === true,
10432
10705
  query: normalizeOptionalToolString(input.query),
@@ -10447,7 +10720,7 @@ function registerGitHubAgentTools(ctx) {
10447
10720
  async (params, runCtx) => executeGitHubTool(async () => {
10448
10721
  const input = getToolInputRecord(params);
10449
10722
  const target = await resolveGitHubPullRequestToolTarget(ctx, runCtx, input);
10450
- const octokit = await createGitHubToolOctokit(ctx);
10723
+ const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10451
10724
  const projectTarget = await resolveGitHubProjectToolTarget(octokit, input);
10452
10725
  const pullRequest = await getGitHubPullRequestProjectItems(
10453
10726
  octokit,
@@ -10524,22 +10797,18 @@ var plugin = definePlugin({
10524
10797
  const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
10525
10798
  const normalizedSettings = normalizeSettings(saved);
10526
10799
  const config = await getResolvedConfig(ctx);
10527
- const githubTokenRef = getConfiguredGithubTokenRef(normalizedSettings, config);
10528
- const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(normalizedSettings, config);
10529
- const githubTokenConfigured = hasConfiguredGithubToken(normalizedSettings, config);
10800
+ const githubTokenConfigured = hasConfiguredGithubToken(normalizedSettings, config, requestedCompanyId);
10530
10801
  const configuredBoardTokenRef = getConfiguredPaperclipBoardApiTokenRef(config, requestedCompanyId);
10531
10802
  const savedBoardTokenRef = getSavedPaperclipBoardApiTokenRef(normalizedSettings, requestedCompanyId);
10532
- const settingsWithResolvedToken = githubTokenRef === normalizedSettings.githubTokenRef && paperclipApiBaseUrl === normalizedSettings.paperclipApiBaseUrl ? normalizedSettings : {
10533
- ...normalizedSettings,
10534
- ...githubTokenRef ? { githubTokenRef } : {},
10535
- ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {}
10536
- };
10537
- const settingsForResponse = sanitizeSettingsForCurrentSetup(settingsWithResolvedToken, {
10538
- hasToken: githubTokenConfigured,
10539
- hasMappings: getSyncableMappings(settingsWithResolvedToken.mappings).length > 0
10540
- });
10541
- if (settingsForResponse !== normalizedSettings) {
10542
- await saveSettingsSyncState(ctx, settingsForResponse, settingsForResponse.syncState);
10803
+ const settingsForResponse = sanitizeSettingsForCurrentSetup(
10804
+ materializeScopedSettings(normalizedSettings, config, requestedCompanyId),
10805
+ {
10806
+ hasToken: githubTokenConfigured,
10807
+ hasMappings: getSyncableMappingsForScope(normalizedSettings.mappings, requestedCompanyId).length > 0
10808
+ }
10809
+ );
10810
+ if (JSON.stringify(settingsForResponse.syncState) !== JSON.stringify(getScopedSyncState(normalizedSettings, requestedCompanyId))) {
10811
+ await saveSettingsSyncState(ctx, normalizedSettings, settingsForResponse.syncState, requestedCompanyId);
10543
10812
  }
10544
10813
  const scopedMappings = filterMappingsByCompany(settingsForResponse.mappings, requestedCompanyId);
10545
10814
  const availableAssignees = includeAssignees && requestedCompanyId ? await listAvailableAssignees(ctx, requestedCompanyId) : [];
@@ -10598,10 +10867,34 @@ var plugin = definePlugin({
10598
10867
  const config = await getResolvedConfig(ctx);
10599
10868
  const record = input && typeof input === "object" ? input : {};
10600
10869
  const requestedCompanyId = normalizeCompanyId(record.companyId);
10870
+ const requestedGitHubTokenLogin = "githubTokenLogin" in record ? normalizeOptionalString2(record.githubTokenLogin) : void 0;
10601
10871
  const hasMappingsPatch = "mappings" in record;
10602
10872
  const hasAdvancedSettingsPatch = "advancedSettings" in record;
10603
- const githubTokenRef = "githubTokenRef" in record ? normalizeGitHubTokenRef(record.githubTokenRef) : normalizeGitHubTokenRef(previous.githubTokenRef) ?? normalizeGitHubTokenRef(config.githubTokenRef);
10604
- const githubTokenLogin = "githubTokenLogin" in record ? normalizeOptionalString2(record.githubTokenLogin) : previous.githubTokenLogin;
10873
+ const previousScopedSettings = materializeScopedSettings(previous, config, requestedCompanyId);
10874
+ const nextGitHubTokenRefs = {
10875
+ ...previous.githubTokenRefs ?? {}
10876
+ };
10877
+ if (requestedCompanyId) {
10878
+ const companyScopedGitHubTokenRef = normalizeGitHubTokenRefs(record.githubTokenRefs)?.[requestedCompanyId] ?? ("githubTokenRef" in record ? normalizeGitHubTokenRef(record.githubTokenRef) : void 0);
10879
+ if (companyScopedGitHubTokenRef) {
10880
+ nextGitHubTokenRefs[requestedCompanyId] = companyScopedGitHubTokenRef;
10881
+ }
10882
+ }
10883
+ const githubTokenRefs = Object.keys(nextGitHubTokenRefs).length > 0 ? nextGitHubTokenRefs : void 0;
10884
+ const githubTokenRef = !requestedCompanyId && "githubTokenRef" in record ? normalizeGitHubTokenRef(record.githubTokenRef) : normalizeGitHubTokenRef(previous.githubTokenRef) ?? normalizeGitHubTokenRef(config.githubTokenRef);
10885
+ const nextGitHubTokenLoginByCompanyId = {
10886
+ ...previous.githubTokenLoginByCompanyId ?? {}
10887
+ };
10888
+ if (requestedCompanyId && "githubTokenLogin" in record) {
10889
+ const companyScopedGitHubTokenLogin = requestedGitHubTokenLogin;
10890
+ if (companyScopedGitHubTokenLogin) {
10891
+ nextGitHubTokenLoginByCompanyId[requestedCompanyId] = companyScopedGitHubTokenLogin;
10892
+ } else {
10893
+ delete nextGitHubTokenLoginByCompanyId[requestedCompanyId];
10894
+ }
10895
+ }
10896
+ const githubTokenLoginByCompanyId = Object.keys(nextGitHubTokenLoginByCompanyId).length > 0 ? nextGitHubTokenLoginByCompanyId : void 0;
10897
+ const githubTokenLogin = !requestedCompanyId && "githubTokenLogin" in record ? requestedGitHubTokenLogin : previous.githubTokenLogin;
10605
10898
  const inputMappings = hasMappingsPatch ? normalizeMappings(record.mappings) : previous.mappings;
10606
10899
  const nextCompanyAdvancedSettingsByCompanyId = {
10607
10900
  ...previous.companyAdvancedSettingsByCompanyId ?? {}
@@ -10616,17 +10909,26 @@ var plugin = definePlugin({
10616
10909
  companyId: requestedCompanyId
10617
10910
  }))
10618
10911
  ] : inputMappings;
10619
- const current = normalizeSettings({
10912
+ let current = normalizeSettings({
10620
10913
  mappings: mergedMappings,
10621
10914
  syncState: previous.syncState,
10622
- scheduleFrequencyMinutes: "scheduleFrequencyMinutes" in record ? record.scheduleFrequencyMinutes : previous.scheduleFrequencyMinutes,
10623
- paperclipApiBaseUrl: "paperclipApiBaseUrl" in record ? resolveTrustedPaperclipApiBaseUrlInput(record.paperclipApiBaseUrl, previous, config) : getConfiguredPaperclipApiBaseUrl(previous, config),
10915
+ ...previous.syncStateByCompanyId ? { syncStateByCompanyId: previous.syncStateByCompanyId } : {},
10916
+ scheduleFrequencyMinutes: previous.scheduleFrequencyMinutes,
10917
+ ...previous.scheduleFrequencyMinutesByCompanyId ? { scheduleFrequencyMinutesByCompanyId: previous.scheduleFrequencyMinutesByCompanyId } : {},
10918
+ ...previous.paperclipApiBaseUrl ? { paperclipApiBaseUrl: previous.paperclipApiBaseUrl } : {},
10919
+ ...previous.paperclipApiBaseUrlByCompanyId ? { paperclipApiBaseUrlByCompanyId: previous.paperclipApiBaseUrlByCompanyId } : {},
10920
+ ...githubTokenRefs ? { githubTokenRefs } : {},
10921
+ ...githubTokenLoginByCompanyId ? { githubTokenLoginByCompanyId } : {},
10624
10922
  ...githubTokenLogin ? { githubTokenLogin } : {},
10625
10923
  paperclipBoardApiTokenRefs: previous.paperclipBoardApiTokenRefs,
10626
10924
  paperclipBoardAccessIdentityByCompanyId: previous.paperclipBoardAccessIdentityByCompanyId,
10627
10925
  ...Object.keys(nextCompanyAdvancedSettingsByCompanyId).length > 0 ? { companyAdvancedSettingsByCompanyId: nextCompanyAdvancedSettingsByCompanyId } : {},
10628
10926
  ...githubTokenRef ? { githubTokenRef } : {}
10629
10927
  });
10928
+ const nextScheduleFrequencyMinutes = "scheduleFrequencyMinutes" in record ? normalizeScheduleFrequencyMinutes(record.scheduleFrequencyMinutes) : getScopedScheduleFrequencyMinutes(previous, requestedCompanyId);
10929
+ current = upsertScopedScheduleFrequencyMinutes(current, nextScheduleFrequencyMinutes, requestedCompanyId);
10930
+ const nextPaperclipApiBaseUrl = "paperclipApiBaseUrl" in record ? resolveTrustedPaperclipApiBaseUrlInput(record.paperclipApiBaseUrl, previous, config, requestedCompanyId) : getConfiguredPaperclipApiBaseUrl(previous, config, requestedCompanyId);
10931
+ current = upsertScopedPaperclipApiBaseUrl(current, nextPaperclipApiBaseUrl, requestedCompanyId);
10630
10932
  const nextMappings = current.mappings.map((mapping, index) => ({
10631
10933
  id: mapping.id.trim() || createMappingId(index),
10632
10934
  repositoryUrl: parseRepositoryReference(mapping.repositoryUrl)?.url ?? mapping.repositoryUrl.trim(),
@@ -10634,11 +10936,15 @@ var plugin = definePlugin({
10634
10936
  paperclipProjectId: mapping.paperclipProjectId,
10635
10937
  companyId: mapping.companyId
10636
10938
  }));
10939
+ const materializedCurrent = materializeScopedSettings(current, config, requestedCompanyId);
10637
10940
  const next = sanitizeSettingsForCurrentSetup({
10941
+ ...current,
10638
10942
  mappings: nextMappings,
10639
- syncState: previous.syncState,
10640
- scheduleFrequencyMinutes: current.scheduleFrequencyMinutes,
10641
- ...current.paperclipApiBaseUrl ? { paperclipApiBaseUrl: current.paperclipApiBaseUrl } : {},
10943
+ syncState: materializedCurrent.syncState,
10944
+ scheduleFrequencyMinutes: materializedCurrent.scheduleFrequencyMinutes,
10945
+ ...materializedCurrent.paperclipApiBaseUrl ? { paperclipApiBaseUrl: materializedCurrent.paperclipApiBaseUrl } : {},
10946
+ ...current.githubTokenRefs ? { githubTokenRefs: current.githubTokenRefs } : {},
10947
+ ...current.githubTokenLoginByCompanyId ? { githubTokenLoginByCompanyId: current.githubTokenLoginByCompanyId } : {},
10642
10948
  ...current.githubTokenLogin ? { githubTokenLogin: current.githubTokenLogin } : {},
10643
10949
  ...current.paperclipBoardApiTokenRefs ? { paperclipBoardApiTokenRefs: current.paperclipBoardApiTokenRefs } : {},
10644
10950
  ...current.paperclipBoardAccessIdentityByCompanyId ? { paperclipBoardAccessIdentityByCompanyId: current.paperclipBoardAccessIdentityByCompanyId } : {},
@@ -10646,14 +10952,16 @@ var plugin = definePlugin({
10646
10952
  ...githubTokenRef ? { githubTokenRef } : {},
10647
10953
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10648
10954
  }, {
10649
- hasToken: hasConfiguredGithubToken({ githubTokenRef }, config),
10650
- hasMappings: getSyncableMappings(nextMappings).length > 0
10955
+ hasToken: hasConfiguredGithubToken(current, config, requestedCompanyId),
10956
+ hasMappings: getSyncableMappingsForScope(nextMappings, requestedCompanyId).length > 0
10651
10957
  });
10652
10958
  await ctx.state.set(SETTINGS_SCOPE, next);
10653
10959
  await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
10654
10960
  clearGitHubRepositoryTokenCapabilityAudits();
10961
+ const scopedGitHubTokenLogin = (requestedCompanyId && "githubTokenLogin" in record ? requestedGitHubTokenLogin : void 0) ?? getGitHubTokenLogin(next, requestedCompanyId);
10655
10962
  return {
10656
10963
  ...getPublicSettingsForScope(next, requestedCompanyId),
10964
+ ...scopedGitHubTokenLogin ? { githubTokenLogin: scopedGitHubTokenLogin } : {},
10657
10965
  availableAssignees: requestedCompanyId ? await listAvailableAssignees(ctx, requestedCompanyId) : []
10658
10966
  };
10659
10967
  });
@@ -10691,15 +10999,16 @@ var plugin = definePlugin({
10691
10999
  paperclipBoardAccessIdentityByCompanyId: _previousPaperclipBoardAccessIdentityByCompanyId,
10692
11000
  ...previousWithoutBoardAccess
10693
11001
  } = previous;
10694
- const next = sanitizeSettingsForCurrentSetup({
11002
+ const nextBase = sanitizeSettingsForCurrentSetup({
10695
11003
  ...previousWithoutBoardAccess,
10696
11004
  ...Object.keys(nextPaperclipBoardApiTokenRefs).length > 0 ? { paperclipBoardApiTokenRefs: nextPaperclipBoardApiTokenRefs } : {},
10697
11005
  ...Object.keys(nextPaperclipBoardAccessIdentityByCompanyId).length > 0 ? { paperclipBoardAccessIdentityByCompanyId: nextPaperclipBoardAccessIdentityByCompanyId } : {},
10698
11006
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10699
11007
  }, {
10700
- hasToken: hasConfiguredGithubToken(previous, config),
10701
- hasMappings: getSyncableMappings(previous.mappings).length > 0
11008
+ hasToken: hasConfiguredGithubToken(previous, config, companyId),
11009
+ hasMappings: getSyncableMappingsForScope(previous.mappings, companyId).length > 0
10702
11010
  });
11011
+ const next = materializeScopedSettings(nextBase, config, companyId);
10703
11012
  await ctx.state.set(SETTINGS_SCOPE, next);
10704
11013
  await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
10705
11014
  return {
@@ -10770,7 +11079,7 @@ var plugin = definePlugin({
10770
11079
  });
10771
11080
  });
10772
11081
  ctx.actions.register("sync.cancel", async () => {
10773
- const currentSettings = await getActiveOrCurrentSyncState(ctx);
11082
+ const currentSettings = await getActiveOrCurrentSyncState(ctx, activeRunningSyncCompanyId);
10774
11083
  if (currentSettings.syncState.status !== "running") {
10775
11084
  return currentSettings;
10776
11085
  }
@@ -10790,7 +11099,8 @@ var plugin = definePlugin({
10790
11099
  progress: currentSettings.syncState.progress,
10791
11100
  message: CANCELLING_SYNC_MESSAGE,
10792
11101
  cancelRequestedAt: cancellationRequest.requestedAt
10793
- })
11102
+ }),
11103
+ activeRunningSyncCompanyId
10794
11104
  );
10795
11105
  activeRunningSyncState = next;
10796
11106
  return next;
@@ -10798,10 +11108,22 @@ var plugin = definePlugin({
10798
11108
  registerGitHubAgentTools(ctx);
10799
11109
  ctx.jobs.register("sync.github-issues", async (job) => {
10800
11110
  const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
10801
- if (job.trigger === "schedule" && !shouldRunScheduledSync(settings, job.scheduledAt)) {
11111
+ const trigger = job.trigger === "retry" ? "retry" : "schedule";
11112
+ const scheduledTargets = listScheduledSyncTargets(settings);
11113
+ if (scheduledTargets.length === 0) {
11114
+ if (job.trigger === "schedule" && !shouldRunScheduledSync(settings, job.scheduledAt)) {
11115
+ return;
11116
+ }
11117
+ await startSync(ctx, trigger);
10802
11118
  return;
10803
11119
  }
10804
- await startSync(ctx, job.trigger === "retry" ? "retry" : "schedule");
11120
+ for (const target of scheduledTargets) {
11121
+ const scopedSettings = materializeScopedSettings(settings, null, target?.companyId);
11122
+ if (job.trigger === "schedule" && !shouldRunScheduledSync(scopedSettings, job.scheduledAt)) {
11123
+ continue;
11124
+ }
11125
+ await startSync(ctx, trigger, target ? { target } : {});
11126
+ }
10805
11127
  });
10806
11128
  }
10807
11129
  });