paperclip-github-plugin 0.4.1 → 0.4.3

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
@@ -618,23 +618,27 @@ var CANCELLING_SYNC_MESSAGE = "Cancellation requested. GitHub sync will stop aft
618
618
  var SYNC_PROGRESS_PERSIST_INTERVAL_MS = 250;
619
619
  var MAX_SYNC_FAILURE_LOG_ENTRIES = 25;
620
620
  var GITHUB_SECONDARY_RATE_LIMIT_FALLBACK_MS = 6e4;
621
+ var IMPORTED_ISSUE_WAKEUP_CONCURRENCY = 4;
621
622
  var MISSING_GITHUB_TOKEN_SYNC_MESSAGE = "Configure a GitHub token before running sync.";
622
623
  var MISSING_GITHUB_TOKEN_SYNC_ACTION = 'Open settings and save a GitHub token secret, or create $PAPERCLIP_HOME/plugins/github-sync/config.json (or ~/.paperclip/plugins/github-sync/config.json when PAPERCLIP_HOME is unset) with a "githubToken" value, and then run sync again.';
623
624
  var MISSING_MAPPING_SYNC_MESSAGE = "Save at least one mapping with a created Paperclip project before running sync.";
624
625
  var MISSING_MAPPING_SYNC_ACTION = "Open settings, add a repository mapping, let Paperclip create the target project, and then retry sync.";
625
626
  var MISSING_BOARD_ACCESS_SYNC_MESSAGE = "Connect Paperclip board access before running sync on this authenticated deployment.";
626
627
  var MISSING_BOARD_ACCESS_SYNC_ACTION = "Open plugin settings for each mapped company that sync will touch, connect Paperclip board access, approve the flow, and then run sync again.";
628
+ var IMPORTED_ISSUE_WAKE_REASON = "GitHub Sync imported an assigned issue that is ready for work.";
627
629
  var ISSUE_LINK_ENTITY_TYPE = "paperclip-github-plugin.issue-link";
628
630
  var PULL_REQUEST_LINK_ENTITY_TYPE = "paperclip-github-plugin.pull-request-link";
629
631
  var COMMENT_ANNOTATION_ENTITY_TYPE = "paperclip-github-plugin.comment-annotation";
630
632
  var AI_AUTHORED_COMMENT_FOOTER_PREFIX = "Created by a Paperclip AI agent using ";
631
633
  var HIDDEN_GITHUB_IMPORT_MARKER_PREFIX = "<!-- paperclip-github-plugin-imported-from: ";
632
634
  var HIDDEN_GITHUB_IMPORT_MARKER_SUFFIX = " -->";
635
+ var EMPTY_GITHUB_ISSUE_DESCRIPTION_PLACEHOLDER = "_No description provided on GitHub._";
633
636
  function normalizeCompanyId(value) {
634
637
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
635
638
  }
636
639
  var activeSyncPromise = null;
637
640
  var activeRunningSyncState = null;
641
+ var activeRunningSyncCompanyId;
638
642
  var activePaperclipApiAuthTokensByCompanyId = null;
639
643
  var activeExternalConfigWarningKey = null;
640
644
  var activeProjectPullRequestPageCache = /* @__PURE__ */ new Map();
@@ -710,6 +714,7 @@ var FAILED_STATUS_CONTEXT_STATES = /* @__PURE__ */ new Set(["ERROR", "FAILURE"])
710
714
  var PENDING_STATUS_CONTEXT_STATES = /* @__PURE__ */ new Set(["EXPECTED", "PENDING"]);
711
715
  var GITHUB_REPOSITORY_MAINTAINER_WARMUP_CONCURRENCY = 4;
712
716
  var GITHUB_REPOSITORY_MAINTAINER_ROLE_NAMES = /* @__PURE__ */ new Set(["admin", "maintain"]);
717
+ var GITHUB_REPOSITORY_TRUSTED_AUTHOR_ASSOCIATIONS = /* @__PURE__ */ new Set(["collaborator", "member", "owner"]);
713
718
  var GITHUB_ISSUE_STATUS_SNAPSHOT_QUERY = `
714
719
  query GitHubIssueStatusSnapshot($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
715
720
  repository(owner: $owner, name: $repo) {
@@ -1613,6 +1618,20 @@ function normalizeGitHubUserLogin(value) {
1613
1618
  function normalizeGitHubTokenRef(value) {
1614
1619
  return normalizeSecretRef(value);
1615
1620
  }
1621
+ function normalizeGitHubTokenRefs(value) {
1622
+ if (!value || typeof value !== "object") {
1623
+ return void 0;
1624
+ }
1625
+ const entries = Object.entries(value).map(([companyId, secretRef]) => {
1626
+ const normalizedCompanyId = normalizeCompanyId(companyId);
1627
+ const normalizedSecretRef = normalizeGitHubTokenRef(secretRef);
1628
+ return normalizedCompanyId && normalizedSecretRef ? [normalizedCompanyId, normalizedSecretRef] : null;
1629
+ }).filter((entry) => entry !== null);
1630
+ if (entries.length === 0) {
1631
+ return void 0;
1632
+ }
1633
+ return Object.fromEntries(entries);
1634
+ }
1616
1635
  function formatUtcTimestamp(value) {
1617
1636
  const parsed = new Date(value);
1618
1637
  if (Number.isNaN(parsed.getTime())) {
@@ -2410,9 +2429,9 @@ function extractGitHubLinksFromCommentBody(body) {
2410
2429
  return [...links.values()];
2411
2430
  }
2412
2431
  async function buildToolbarSyncState(ctx, input) {
2413
- const settings = await getActiveOrCurrentSyncState(ctx);
2414
- const config = await getResolvedConfig(ctx);
2415
2432
  const companyId = typeof input.companyId === "string" && input.companyId.trim() ? input.companyId.trim() : void 0;
2433
+ const settings = await getActiveOrCurrentSyncState(ctx, companyId);
2434
+ const config = await getResolvedConfig(ctx);
2416
2435
  const githubTokenConfigured = hasConfiguredGithubToken(settings, config, companyId);
2417
2436
  const entityId = typeof input.entityId === "string" && input.entityId.trim() ? input.entityId.trim() : void 0;
2418
2437
  const entityType = typeof input.entityType === "string" && input.entityType.trim() ? input.entityType.trim() : void 0;
@@ -2454,7 +2473,7 @@ async function buildToolbarSyncState(ctx, input) {
2454
2473
  }) : [];
2455
2474
  return {
2456
2475
  kind: "issue",
2457
- visible: Boolean(link),
2476
+ visible: false,
2458
2477
  canRun: githubTokenConfigured && mappings.length > 0,
2459
2478
  label: link?.githubIssueNumber ? `Sync #${link.githubIssueNumber}` : "Sync issue",
2460
2479
  message: link ? `Sync ${link.repositoryUrl.replace(/^https:\/\/github\.com\//, "")} issue #${link.githubIssueNumber}.` : "This Paperclip issue is not linked to GitHub yet.",
@@ -2626,15 +2645,25 @@ function sanitizeSettingsForCurrentSetup(settings, setup) {
2626
2645
  function getPublicSettings(settings) {
2627
2646
  const {
2628
2647
  githubTokenRefs: _githubTokenRefs,
2629
- githubTokenLoginsByCompanyId: _githubTokenLoginsByCompanyId,
2648
+ githubTokenLoginByCompanyId: _githubTokenLoginByCompanyId,
2630
2649
  githubTokenRef: _githubTokenRef,
2631
2650
  paperclipBoardApiTokenRefs: _paperclipBoardApiTokenRefs,
2632
2651
  paperclipBoardAccessIdentityByCompanyId: _paperclipBoardAccessIdentityByCompanyId,
2633
2652
  companyAdvancedSettingsByCompanyId: _companyAdvancedSettingsByCompanyId,
2653
+ syncStateByCompanyId: _syncStateByCompanyId,
2654
+ scheduleFrequencyMinutesByCompanyId: _scheduleFrequencyMinutesByCompanyId,
2655
+ paperclipApiBaseUrlByCompanyId: _paperclipApiBaseUrlByCompanyId,
2634
2656
  ...publicSettings
2635
2657
  } = settings;
2636
2658
  return publicSettings;
2637
2659
  }
2660
+ function getGitHubTokenLogin(settings, companyId) {
2661
+ const normalizedCompanyId = normalizeCompanyId(companyId);
2662
+ if (!normalizedCompanyId) {
2663
+ return void 0;
2664
+ }
2665
+ return normalizeOptionalString2(settings.githubTokenLoginByCompanyId?.[normalizedCompanyId]) ?? normalizeOptionalString2(settings.githubTokenLogin);
2666
+ }
2638
2667
  function getPaperclipBoardAccessIdentity(settings, companyId) {
2639
2668
  const normalizedCompanyId = normalizeCompanyId(companyId);
2640
2669
  if (!normalizedCompanyId) {
@@ -2644,34 +2673,16 @@ function getPaperclipBoardAccessIdentity(settings, companyId) {
2644
2673
  }
2645
2674
  function getPublicSettingsForScope(settings, companyId) {
2646
2675
  const publicSettings = getPublicSettings(settings);
2676
+ const githubTokenLogin = getGitHubTokenLogin(settings, companyId);
2647
2677
  const paperclipBoardAccessIdentity = getPaperclipBoardAccessIdentity(settings, companyId);
2648
2678
  return {
2649
2679
  ...publicSettings,
2650
2680
  mappings: filterMappingsByCompany(publicSettings.mappings, companyId),
2651
2681
  advancedSettings: getCompanyAdvancedSettings(settings, companyId),
2682
+ ...githubTokenLogin ? { githubTokenLogin } : {},
2652
2683
  ...paperclipBoardAccessIdentity ? { paperclipBoardAccessIdentity } : {}
2653
2684
  };
2654
2685
  }
2655
- function getSavedGitHubTokenRef(settings, companyId) {
2656
- const normalizedCompanyId = normalizeCompanyId(companyId);
2657
- if (normalizedCompanyId) {
2658
- const scopedSecretRef = normalizeSecretRef(settings?.githubTokenRefs?.[normalizedCompanyId]);
2659
- if (scopedSecretRef) {
2660
- return scopedSecretRef;
2661
- }
2662
- }
2663
- return normalizeGitHubTokenRef(settings?.githubTokenRef);
2664
- }
2665
- function getSavedGitHubTokenLogin(settings, companyId) {
2666
- const normalizedCompanyId = normalizeCompanyId(companyId);
2667
- if (normalizedCompanyId) {
2668
- const scopedLogin = normalizeOptionalString2(settings?.githubTokenLoginsByCompanyId?.[normalizedCompanyId]);
2669
- if (scopedLogin) {
2670
- return scopedLogin;
2671
- }
2672
- }
2673
- return normalizeOptionalString2(settings?.githubTokenLogin);
2674
- }
2675
2686
  async function listAvailableAssignees(ctx, companyId) {
2676
2687
  try {
2677
2688
  const agents = await ctx.agents.list({
@@ -2852,14 +2863,11 @@ function createSetupConfigurationErrorSyncState(issue, trigger) {
2852
2863
  });
2853
2864
  }
2854
2865
  }
2855
- async function saveSettingsSyncState(ctx, settings, syncState) {
2856
- const next = {
2857
- ...settings,
2858
- syncState
2859
- };
2866
+ async function saveSettingsSyncState(ctx, settings, syncState, companyId) {
2867
+ const next = upsertScopedSyncState(normalizeSettings(settings), syncState, companyId);
2860
2868
  await ctx.state.set(SETTINGS_SCOPE, next);
2861
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
2862
- return next;
2869
+ await ctx.state.set(SYNC_STATE_SCOPE, syncState);
2870
+ return materializeScopedSettings(next, null, companyId);
2863
2871
  }
2864
2872
  async function setSyncCancellationRequest(ctx, request) {
2865
2873
  if (request) {
@@ -2884,8 +2892,8 @@ function buildCancelledSyncMessage(target, progress) {
2884
2892
  const completionSummary = completedIssueCount !== void 0 && totalIssueCount !== void 0 ? ` Completed ${Math.min(completedIssueCount, totalIssueCount)} of ${totalIssueCount} issues before stopping.` : "";
2885
2893
  return `${scopeLabel} was cancelled before it finished.${completionSummary}`;
2886
2894
  }
2887
- async function createUnexpectedSyncErrorResult(ctx, trigger, error) {
2888
- const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
2895
+ async function createUnexpectedSyncErrorResult(ctx, trigger, error, companyId) {
2896
+ const settings = materializeScopedSettings(normalizeSettings(await ctx.state.get(SETTINGS_SCOPE)), null, companyId);
2889
2897
  const errorDetails = buildSyncErrorDetails(error, {
2890
2898
  phase: "configuration"
2891
2899
  });
@@ -2910,7 +2918,8 @@ async function createUnexpectedSyncErrorResult(ctx, trigger, error) {
2910
2918
  errorDetails
2911
2919
  })
2912
2920
  )
2913
- })
2921
+ }),
2922
+ companyId
2914
2923
  );
2915
2924
  }
2916
2925
  async function waitForSyncResultWithinGracePeriod(promise, timeoutMs) {
@@ -2928,15 +2937,13 @@ async function waitForSyncResultWithinGracePeriod(promise, timeoutMs) {
2928
2937
  }
2929
2938
  }
2930
2939
  }
2931
- async function getActiveOrCurrentSyncState(ctx) {
2932
- if (activeRunningSyncState?.syncState.status === "running") {
2933
- return activeRunningSyncState;
2940
+ async function getActiveOrCurrentSyncState(ctx, companyId) {
2941
+ const normalizedCompanyId = normalizeCompanyId(companyId);
2942
+ if (activeRunningSyncState?.syncState.status === "running" && activeRunningSyncCompanyId === normalizedCompanyId) {
2943
+ return materializeScopedSettings(activeRunningSyncState, null, normalizedCompanyId);
2934
2944
  }
2935
2945
  const current = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
2936
- if (current.syncState.status === "running") {
2937
- return current;
2938
- }
2939
- return current;
2946
+ return materializeScopedSettings(current, null, normalizedCompanyId);
2940
2947
  }
2941
2948
  function updateSyncFailureContext(current, next) {
2942
2949
  if ("phase" in next) {
@@ -3060,12 +3067,6 @@ async function readExternalConfig(ctx) {
3060
3067
  }
3061
3068
  }
3062
3069
  function normalizePaperclipBoardApiTokenRefs(value) {
3063
- return normalizeCompanySecretRefs(value);
3064
- }
3065
- function normalizeGitHubTokenRefs(value) {
3066
- return normalizeCompanySecretRefs(value);
3067
- }
3068
- function normalizeCompanySecretRefs(value) {
3069
3070
  if (!value || typeof value !== "object") {
3070
3071
  return void 0;
3071
3072
  }
@@ -3079,7 +3080,7 @@ function normalizeCompanySecretRefs(value) {
3079
3080
  }
3080
3081
  return Object.fromEntries(entries);
3081
3082
  }
3082
- function normalizeGitHubTokenLoginsByCompanyId(value) {
3083
+ function normalizeGitHubTokenLoginByCompanyId(value) {
3083
3084
  if (!value || typeof value !== "object") {
3084
3085
  return void 0;
3085
3086
  }
@@ -3126,14 +3127,14 @@ function normalizeSyncState(value) {
3126
3127
  const recentFailures = normalizeSyncFailureLogEntries(record.recentFailures);
3127
3128
  return {
3128
3129
  status: status === "running" || status === "success" || status === "error" || status === "cancelled" ? status : "idle",
3129
- message: typeof record.message === "string" ? record.message : void 0,
3130
- checkedAt: typeof record.checkedAt === "string" ? record.checkedAt : void 0,
3131
- syncedIssuesCount: typeof record.syncedIssuesCount === "number" ? record.syncedIssuesCount : void 0,
3132
- createdIssuesCount: typeof record.createdIssuesCount === "number" ? record.createdIssuesCount : void 0,
3133
- skippedIssuesCount: typeof record.skippedIssuesCount === "number" ? record.skippedIssuesCount : void 0,
3134
- erroredIssuesCount: typeof record.erroredIssuesCount === "number" ? record.erroredIssuesCount : void 0,
3135
- lastRunTrigger: lastRunTrigger === "manual" || lastRunTrigger === "schedule" || lastRunTrigger === "retry" ? lastRunTrigger : void 0,
3136
- cancelRequestedAt: typeof record.cancelRequestedAt === "string" ? record.cancelRequestedAt : void 0,
3130
+ ...typeof record.message === "string" ? { message: record.message } : {},
3131
+ ...typeof record.checkedAt === "string" ? { checkedAt: record.checkedAt } : {},
3132
+ ...typeof record.syncedIssuesCount === "number" ? { syncedIssuesCount: record.syncedIssuesCount } : {},
3133
+ ...typeof record.createdIssuesCount === "number" ? { createdIssuesCount: record.createdIssuesCount } : {},
3134
+ ...typeof record.skippedIssuesCount === "number" ? { skippedIssuesCount: record.skippedIssuesCount } : {},
3135
+ ...typeof record.erroredIssuesCount === "number" ? { erroredIssuesCount: record.erroredIssuesCount } : {},
3136
+ ...lastRunTrigger === "manual" || lastRunTrigger === "schedule" || lastRunTrigger === "retry" ? { lastRunTrigger } : {},
3137
+ ...typeof record.cancelRequestedAt === "string" ? { cancelRequestedAt: record.cancelRequestedAt } : {},
3137
3138
  ...progress ? { progress } : {},
3138
3139
  ...errorDetails ? { errorDetails } : {},
3139
3140
  ...recentFailures ? { recentFailures } : {}
@@ -3243,6 +3244,26 @@ function normalizeScheduleFrequencyMinutes(value) {
3243
3244
  }
3244
3245
  return Math.floor(numericValue);
3245
3246
  }
3247
+ function normalizeSyncStateByCompanyId(value) {
3248
+ if (!value || typeof value !== "object") {
3249
+ return void 0;
3250
+ }
3251
+ const entries = Object.entries(value).map(([companyId, syncState]) => {
3252
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3253
+ return normalizedCompanyId ? [normalizedCompanyId, normalizeSyncState(syncState)] : null;
3254
+ }).filter((entry) => entry !== null);
3255
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
3256
+ }
3257
+ function normalizeScheduleFrequencyMinutesByCompanyId(value) {
3258
+ if (!value || typeof value !== "object") {
3259
+ return void 0;
3260
+ }
3261
+ const entries = Object.entries(value).map(([companyId, scheduleFrequencyMinutes]) => {
3262
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3263
+ return normalizedCompanyId ? [normalizedCompanyId, normalizeScheduleFrequencyMinutes(scheduleFrequencyMinutes)] : null;
3264
+ }).filter((entry) => entry !== null);
3265
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
3266
+ }
3246
3267
  function normalizePaperclipApiBaseUrl(value) {
3247
3268
  if (typeof value !== "string") {
3248
3269
  return void 0;
@@ -3257,6 +3278,17 @@ function normalizePaperclipApiBaseUrl(value) {
3257
3278
  return void 0;
3258
3279
  }
3259
3280
  }
3281
+ function normalizePaperclipApiBaseUrlByCompanyId(value) {
3282
+ if (!value || typeof value !== "object") {
3283
+ return void 0;
3284
+ }
3285
+ const entries = Object.entries(value).map(([companyId, paperclipApiBaseUrl]) => {
3286
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3287
+ const normalizedPaperclipApiBaseUrl = normalizePaperclipApiBaseUrl(paperclipApiBaseUrl);
3288
+ return normalizedCompanyId && normalizedPaperclipApiBaseUrl ? [normalizedCompanyId, normalizedPaperclipApiBaseUrl] : null;
3289
+ }).filter((entry) => entry !== null);
3290
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
3291
+ }
3260
3292
  function getRuntimePaperclipApiBaseUrl() {
3261
3293
  if (typeof process === "undefined" || !process?.env) {
3262
3294
  return void 0;
@@ -3276,19 +3308,26 @@ function resolvePaperclipApiBaseUrl(...values) {
3276
3308
  }
3277
3309
  return void 0;
3278
3310
  }
3279
- function getConfiguredPaperclipApiBaseUrl(settings, config) {
3280
- return resolvePaperclipApiBaseUrl(config?.paperclipApiBaseUrl, settings?.paperclipApiBaseUrl);
3311
+ function getConfiguredPaperclipApiBaseUrl(settings, config, companyId) {
3312
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3313
+ return normalizedCompanyId ? resolvePaperclipApiBaseUrl(
3314
+ config?.paperclipApiBaseUrl,
3315
+ settings?.paperclipApiBaseUrlByCompanyId?.[normalizedCompanyId],
3316
+ settings?.paperclipApiBaseUrl
3317
+ ) : resolvePaperclipApiBaseUrl(config?.paperclipApiBaseUrl, settings?.paperclipApiBaseUrl);
3281
3318
  }
3282
- function resolveTrustedPaperclipApiBaseUrlInput(value, settings, config) {
3319
+ function resolveTrustedPaperclipApiBaseUrlInput(value, settings, config, companyId) {
3283
3320
  const runtimePaperclipApiBaseUrl = getRuntimePaperclipApiBaseUrl();
3284
3321
  if (runtimePaperclipApiBaseUrl) {
3285
3322
  return runtimePaperclipApiBaseUrl;
3286
3323
  }
3287
3324
  const requestedPaperclipApiBaseUrl = normalizePaperclipApiBaseUrl(value);
3288
3325
  const configuredPaperclipApiBaseUrl = normalizePaperclipApiBaseUrl(config?.paperclipApiBaseUrl);
3326
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3327
+ const savedCompanyPaperclipApiBaseUrl = normalizedCompanyId ? normalizePaperclipApiBaseUrl(settings?.paperclipApiBaseUrlByCompanyId?.[normalizedCompanyId]) : void 0;
3289
3328
  const savedPaperclipApiBaseUrl = normalizePaperclipApiBaseUrl(settings?.paperclipApiBaseUrl);
3290
3329
  if (!requestedPaperclipApiBaseUrl) {
3291
- return configuredPaperclipApiBaseUrl ?? savedPaperclipApiBaseUrl;
3330
+ return configuredPaperclipApiBaseUrl ?? savedCompanyPaperclipApiBaseUrl ?? savedPaperclipApiBaseUrl;
3292
3331
  }
3293
3332
  if (configuredPaperclipApiBaseUrl) {
3294
3333
  if (requestedPaperclipApiBaseUrl !== configuredPaperclipApiBaseUrl) {
@@ -3298,6 +3337,9 @@ function resolveTrustedPaperclipApiBaseUrlInput(value, settings, config) {
3298
3337
  }
3299
3338
  return configuredPaperclipApiBaseUrl;
3300
3339
  }
3340
+ if (savedCompanyPaperclipApiBaseUrl && requestedPaperclipApiBaseUrl === savedCompanyPaperclipApiBaseUrl) {
3341
+ return savedCompanyPaperclipApiBaseUrl;
3342
+ }
3301
3343
  if (savedPaperclipApiBaseUrl && requestedPaperclipApiBaseUrl === savedPaperclipApiBaseUrl) {
3302
3344
  return savedPaperclipApiBaseUrl;
3303
3345
  }
@@ -3310,9 +3352,14 @@ function normalizeSettings(value) {
3310
3352
  return DEFAULT_SETTINGS;
3311
3353
  }
3312
3354
  const record = value;
3355
+ const syncStateByCompanyId = normalizeSyncStateByCompanyId(record.syncStateByCompanyId);
3356
+ const scheduleFrequencyMinutesByCompanyId = normalizeScheduleFrequencyMinutesByCompanyId(
3357
+ record.scheduleFrequencyMinutesByCompanyId
3358
+ );
3313
3359
  const paperclipApiBaseUrl = resolvePaperclipApiBaseUrl(record.paperclipApiBaseUrl);
3360
+ const paperclipApiBaseUrlByCompanyId = normalizePaperclipApiBaseUrlByCompanyId(record.paperclipApiBaseUrlByCompanyId);
3314
3361
  const githubTokenRefs = normalizeGitHubTokenRefs(record.githubTokenRefs);
3315
- const githubTokenLoginsByCompanyId = normalizeGitHubTokenLoginsByCompanyId(record.githubTokenLoginsByCompanyId);
3362
+ const githubTokenLoginByCompanyId = normalizeGitHubTokenLoginByCompanyId(record.githubTokenLoginByCompanyId);
3316
3363
  const githubTokenRef = normalizeGitHubTokenRef(record.githubTokenRef);
3317
3364
  const githubTokenLogin = normalizeOptionalString2(record.githubTokenLogin);
3318
3365
  const paperclipBoardApiTokenRefs = normalizePaperclipBoardApiTokenRefs(record.paperclipBoardApiTokenRefs);
@@ -3323,10 +3370,13 @@ function normalizeSettings(value) {
3323
3370
  return {
3324
3371
  mappings: normalizeMappings(record.mappings),
3325
3372
  syncState: normalizeSyncState(record.syncState),
3373
+ ...syncStateByCompanyId ? { syncStateByCompanyId } : {},
3326
3374
  scheduleFrequencyMinutes: normalizeScheduleFrequencyMinutes(record.scheduleFrequencyMinutes),
3375
+ ...scheduleFrequencyMinutesByCompanyId ? { scheduleFrequencyMinutesByCompanyId } : {},
3327
3376
  ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {},
3377
+ ...paperclipApiBaseUrlByCompanyId ? { paperclipApiBaseUrlByCompanyId } : {},
3328
3378
  ...githubTokenRefs ? { githubTokenRefs } : {},
3329
- ...githubTokenLoginsByCompanyId ? { githubTokenLoginsByCompanyId } : {},
3379
+ ...githubTokenLoginByCompanyId ? { githubTokenLoginByCompanyId } : {},
3330
3380
  ...githubTokenRef ? { githubTokenRef } : {},
3331
3381
  ...githubTokenLogin ? { githubTokenLogin } : {},
3332
3382
  ...paperclipBoardApiTokenRefs ? { paperclipBoardApiTokenRefs } : {},
@@ -3335,6 +3385,103 @@ function normalizeSettings(value) {
3335
3385
  updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : void 0
3336
3386
  };
3337
3387
  }
3388
+ function getScopedSyncState(settings, companyId) {
3389
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3390
+ if (!normalizedCompanyId) {
3391
+ return normalizeSyncState(settings.syncState);
3392
+ }
3393
+ const scopedSyncState = settings.syncStateByCompanyId?.[normalizedCompanyId];
3394
+ return scopedSyncState ? normalizeSyncState(scopedSyncState) : normalizeSyncState(settings.syncState);
3395
+ }
3396
+ function getScopedScheduleFrequencyMinutes(settings, companyId) {
3397
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3398
+ if (!normalizedCompanyId) {
3399
+ return normalizeScheduleFrequencyMinutes(settings.scheduleFrequencyMinutes);
3400
+ }
3401
+ const scopedScheduleFrequencyMinutes = settings.scheduleFrequencyMinutesByCompanyId?.[normalizedCompanyId];
3402
+ return scopedScheduleFrequencyMinutes !== void 0 ? normalizeScheduleFrequencyMinutes(scopedScheduleFrequencyMinutes) : normalizeScheduleFrequencyMinutes(settings.scheduleFrequencyMinutes);
3403
+ }
3404
+ function materializeScopedSettings(settings, config, companyId) {
3405
+ const syncState = getScopedSyncState(settings, companyId);
3406
+ const scheduleFrequencyMinutes = getScopedScheduleFrequencyMinutes(settings, companyId);
3407
+ const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(settings, config, companyId);
3408
+ return {
3409
+ ...settings,
3410
+ syncState,
3411
+ scheduleFrequencyMinutes,
3412
+ ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {}
3413
+ };
3414
+ }
3415
+ function upsertScopedSyncState(settings, syncState, companyId) {
3416
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3417
+ if (!normalizedCompanyId) {
3418
+ return {
3419
+ ...settings,
3420
+ syncState
3421
+ };
3422
+ }
3423
+ return {
3424
+ ...settings,
3425
+ syncState,
3426
+ syncStateByCompanyId: {
3427
+ ...settings.syncStateByCompanyId ?? {},
3428
+ [normalizedCompanyId]: syncState
3429
+ }
3430
+ };
3431
+ }
3432
+ function upsertScopedScheduleFrequencyMinutes(settings, scheduleFrequencyMinutes, companyId) {
3433
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3434
+ if (!normalizedCompanyId) {
3435
+ return {
3436
+ ...settings,
3437
+ scheduleFrequencyMinutes
3438
+ };
3439
+ }
3440
+ return {
3441
+ ...settings,
3442
+ scheduleFrequencyMinutes,
3443
+ scheduleFrequencyMinutesByCompanyId: {
3444
+ ...settings.scheduleFrequencyMinutesByCompanyId ?? {},
3445
+ [normalizedCompanyId]: scheduleFrequencyMinutes
3446
+ }
3447
+ };
3448
+ }
3449
+ function upsertScopedPaperclipApiBaseUrl(settings, paperclipApiBaseUrl, companyId) {
3450
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3451
+ if (!normalizedCompanyId) {
3452
+ return {
3453
+ ...settings,
3454
+ ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {}
3455
+ };
3456
+ }
3457
+ const nextPaperclipApiBaseUrlByCompanyId = {
3458
+ ...settings.paperclipApiBaseUrlByCompanyId ?? {}
3459
+ };
3460
+ if (paperclipApiBaseUrl) {
3461
+ nextPaperclipApiBaseUrlByCompanyId[normalizedCompanyId] = paperclipApiBaseUrl;
3462
+ } else {
3463
+ delete nextPaperclipApiBaseUrlByCompanyId[normalizedCompanyId];
3464
+ }
3465
+ return {
3466
+ ...settings,
3467
+ ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {},
3468
+ ...Object.keys(nextPaperclipApiBaseUrlByCompanyId).length > 0 ? { paperclipApiBaseUrlByCompanyId: nextPaperclipApiBaseUrlByCompanyId } : {}
3469
+ };
3470
+ }
3471
+ function getScopedSyncTarget(companyId) {
3472
+ return {
3473
+ kind: "company",
3474
+ companyId,
3475
+ displayLabel: "this company"
3476
+ };
3477
+ }
3478
+ function getSyncableMappingsForScope(mappings, companyId) {
3479
+ const normalizedCompanyId = normalizeCompanyId(companyId);
3480
+ return normalizedCompanyId ? getSyncableMappingsForTarget(mappings, getScopedSyncTarget(normalizedCompanyId)) : getSyncableMappings(mappings);
3481
+ }
3482
+ function hasAnyScopedValue(valueByCompanyId) {
3483
+ return Boolean(valueByCompanyId && Object.keys(valueByCompanyId).length > 0);
3484
+ }
3338
3485
  function normalizeImportRegistry(value) {
3339
3486
  if (!Array.isArray(value)) {
3340
3487
  return [];
@@ -3494,6 +3641,7 @@ function normalizeGitHubIssueRecord(issue) {
3494
3641
  body: typeof issue.body === "string" ? stripNullBytes(issue.body) : null,
3495
3642
  htmlUrl: issue.html_url,
3496
3643
  ...normalizeGitHubUsername(issue.user?.login) ? { authorLogin: normalizeGitHubUsername(issue.user?.login) } : {},
3644
+ ...normalizeGitHubLowercaseString(issue.author_association) ? { authorAssociation: normalizeGitHubLowercaseString(issue.author_association) } : {},
3497
3645
  labels: normalizeGitHubIssueLabels(issue.labels),
3498
3646
  state: issue.state === "closed" ? "closed" : "open",
3499
3647
  stateReason: normalizeGitHubIssueStateReason(issue.state_reason),
@@ -4274,6 +4422,22 @@ async function getGitHubIssueStatusSnapshot(octokit, repository, issueNumber, gi
4274
4422
  function buildGitHubRepositoryActorCacheKey(repository, login) {
4275
4423
  return `${repository.owner.toLowerCase()}/${repository.repo.toLowerCase()}:${login}`;
4276
4424
  }
4425
+ function getGitHubRepositoryTrustedAuthorStatusFromAssociation(authorAssociation) {
4426
+ const normalizedAssociation = normalizeGitHubLowercaseString(authorAssociation);
4427
+ if (!normalizedAssociation) {
4428
+ return void 0;
4429
+ }
4430
+ return GITHUB_REPOSITORY_TRUSTED_AUTHOR_ASSOCIATIONS.has(normalizedAssociation);
4431
+ }
4432
+ function cacheGitHubRepositoryTrustedAuthorStatusFromAssociation(repository, login, authorAssociation, cache) {
4433
+ const normalizedLogin = normalizeGitHubUserLogin(login);
4434
+ const trustedAuthorStatus = getGitHubRepositoryTrustedAuthorStatusFromAssociation(authorAssociation);
4435
+ if (!normalizedLogin || trustedAuthorStatus === void 0) {
4436
+ return trustedAuthorStatus;
4437
+ }
4438
+ cache.set(buildGitHubRepositoryActorCacheKey(repository, normalizedLogin), trustedAuthorStatus);
4439
+ return trustedAuthorStatus;
4440
+ }
4277
4441
  async function isGitHubUserRepositoryMaintainer(octokit, repository, login, cache) {
4278
4442
  const normalizedLogin = normalizeGitHubUserLogin(login);
4279
4443
  if (!normalizedLogin) {
@@ -4339,6 +4503,7 @@ async function listNewGitHubIssueCommentsSinceCount(octokit, repository, issueNu
4339
4503
  body: typeof comment.body === "string" ? stripNullBytes(comment.body) : "",
4340
4504
  url: comment.html_url ?? void 0,
4341
4505
  authorLogin: normalizeGitHubUserLogin(comment.user?.login),
4506
+ ...normalizeGitHubLowercaseString(comment.author_association) ? { authorAssociation: normalizeGitHubLowercaseString(comment.author_association) } : {},
4342
4507
  createdAt: comment.created_at ?? void 0,
4343
4508
  updatedAt: comment.updated_at ?? void 0
4344
4509
  });
@@ -4380,6 +4545,18 @@ async function hasTrustedNewGitHubIssueComment(params) {
4380
4545
  if (originalPosterLogin && authorLogin === originalPosterLogin) {
4381
4546
  return true;
4382
4547
  }
4548
+ const trustedAuthorStatus = cacheGitHubRepositoryTrustedAuthorStatusFromAssociation(
4549
+ params.repository,
4550
+ authorLogin,
4551
+ comment.authorAssociation,
4552
+ params.maintainerCache
4553
+ );
4554
+ if (trustedAuthorStatus === true) {
4555
+ return true;
4556
+ }
4557
+ if (trustedAuthorStatus === false) {
4558
+ continue;
4559
+ }
4383
4560
  unseenAuthors.add(authorLogin);
4384
4561
  }
4385
4562
  for (const authorLogin of unseenAuthors) {
@@ -4399,6 +4576,15 @@ async function isMaintainerAuthoredGitHubIssue(params) {
4399
4576
  if (!authorLogin) {
4400
4577
  return false;
4401
4578
  }
4579
+ const trustedAuthorStatus = cacheGitHubRepositoryTrustedAuthorStatusFromAssociation(
4580
+ params.repository,
4581
+ authorLogin,
4582
+ params.githubIssue.authorAssociation,
4583
+ params.maintainerCache
4584
+ );
4585
+ if (trustedAuthorStatus !== void 0) {
4586
+ return trustedAuthorStatus;
4587
+ }
4402
4588
  return isGitHubUserRepositoryMaintainer(
4403
4589
  params.octokit,
4404
4590
  params.repository,
@@ -4408,7 +4594,19 @@ async function isMaintainerAuthoredGitHubIssue(params) {
4408
4594
  }
4409
4595
  async function warmGitHubRepositoryMaintainerCache(params) {
4410
4596
  const uniqueAuthorLogins = [...new Set(
4411
- params.githubIssues.map((issue) => normalizeGitHubUserLogin(issue.authorLogin)).filter((authorLogin) => Boolean(authorLogin))
4597
+ params.githubIssues.flatMap((issue) => {
4598
+ const authorLogin = normalizeGitHubUserLogin(issue.authorLogin);
4599
+ if (!authorLogin) {
4600
+ return [];
4601
+ }
4602
+ const trustedAuthorStatus = cacheGitHubRepositoryTrustedAuthorStatusFromAssociation(
4603
+ params.repository,
4604
+ authorLogin,
4605
+ issue.authorAssociation,
4606
+ params.maintainerCache
4607
+ );
4608
+ return trustedAuthorStatus === void 0 ? [authorLogin] : [];
4609
+ })
4412
4610
  )].filter((authorLogin) => !params.maintainerCache.has(buildGitHubRepositoryActorCacheKey(params.repository, authorLogin)));
4413
4611
  if (uniqueAuthorLogins.length === 0) {
4414
4612
  return;
@@ -4623,7 +4821,9 @@ function buildPaperclipIssueDescription(issue, linkedPullRequestNumbers = []) {
4623
4821
  return normalizedBody ?? "";
4624
4822
  }
4625
4823
  if (!normalizedBody) {
4626
- return hiddenImportMarker;
4824
+ return `${EMPTY_GITHUB_ISSUE_DESCRIPTION_PLACEHOLDER}
4825
+
4826
+ ${hiddenImportMarker}`;
4627
4827
  }
4628
4828
  return `${normalizedBody}
4629
4829
 
@@ -5027,6 +5227,9 @@ function getPaperclipIssueEndpoint(baseUrl, issueId) {
5027
5227
  function getPaperclipHealthEndpoint(baseUrl) {
5028
5228
  return new URL("/api/health", baseUrl).toString();
5029
5229
  }
5230
+ function getPaperclipAgentWakeupEndpoint(baseUrl, agentId) {
5231
+ return new URL(`/api/agents/${agentId}/wakeup`, baseUrl).toString();
5232
+ }
5030
5233
  function getActivePaperclipApiAuthToken(companyId) {
5031
5234
  if (!companyId) {
5032
5235
  return void 0;
@@ -5071,6 +5274,46 @@ async function detectPaperclipBoardAccessRequirement(paperclipApiBaseUrl) {
5071
5274
  return false;
5072
5275
  }
5073
5276
  }
5277
+ async function wakePaperclipAssigneeForImportedIssue(ctx, params) {
5278
+ if (!params.assigneeAgentId || !params.paperclipApiBaseUrl) {
5279
+ return;
5280
+ }
5281
+ try {
5282
+ const response = await fetchPaperclipApi(
5283
+ getPaperclipAgentWakeupEndpoint(params.paperclipApiBaseUrl, params.assigneeAgentId),
5284
+ {
5285
+ method: "POST",
5286
+ headers: {
5287
+ accept: "application/json",
5288
+ "content-type": "application/json"
5289
+ },
5290
+ body: JSON.stringify({
5291
+ source: "assignment",
5292
+ triggerDetail: "system",
5293
+ reason: IMPORTED_ISSUE_WAKE_REASON,
5294
+ payload: {
5295
+ issueId: params.paperclipIssueId,
5296
+ mutation: "import"
5297
+ }
5298
+ })
5299
+ },
5300
+ {
5301
+ companyId: params.companyId
5302
+ }
5303
+ );
5304
+ if (!response.ok) {
5305
+ throw new Error(`Wakeup request failed with status ${response.status}.`);
5306
+ }
5307
+ } catch (error) {
5308
+ ctx.logger.warn("GitHub sync could not wake the assignee for an imported Paperclip issue.", {
5309
+ issueId: params.paperclipIssueId,
5310
+ agentId: params.assigneeAgentId,
5311
+ companyId: params.companyId,
5312
+ paperclipApiBaseUrl: params.paperclipApiBaseUrl,
5313
+ error: error instanceof Error ? error.message : String(error)
5314
+ });
5315
+ }
5316
+ }
5074
5317
  function parsePaperclipIssueDescription(value) {
5075
5318
  if (!value || typeof value !== "object") {
5076
5319
  return void 0;
@@ -6028,6 +6271,7 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
6028
6271
  let updatedDescriptionsCount = 0;
6029
6272
  let completedIssueCount = 0;
6030
6273
  const totalIssueCount = importedIssues.length;
6274
+ const queuedImportedIssueWakeups = [];
6031
6275
  for (const importedIssue of importedIssues) {
6032
6276
  if (assertNotCancelled) {
6033
6277
  await assertNotCancelled();
@@ -6159,9 +6403,16 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
6159
6403
  defaultImportedStatus: advancedSettings.defaultStatus,
6160
6404
  maintainerAuthoredImportedIssue
6161
6405
  });
6406
+ const shouldWakeImportedAssignee = wasImportedThisRun && nextStatus === "todo" && Boolean(paperclipIssue.assigneeAgentId);
6162
6407
  importedIssue.githubIssueNumber = githubIssue.number;
6163
6408
  importedIssue.lastSeenCommentCount = snapshot.commentCount;
6164
6409
  if (paperclipIssue.status === nextStatus) {
6410
+ if (shouldWakeImportedAssignee) {
6411
+ queuedImportedIssueWakeups.push({
6412
+ assigneeAgentId: paperclipIssue.assigneeAgentId,
6413
+ paperclipIssueId: importedIssue.paperclipIssueId
6414
+ });
6415
+ }
6165
6416
  continue;
6166
6417
  }
6167
6418
  const transitionComment = buildPaperclipIssueStatusTransitionComment({
@@ -6187,6 +6438,12 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
6187
6438
  paperclipApiBaseUrl
6188
6439
  });
6189
6440
  updatedStatusesCount += 1;
6441
+ if (shouldWakeImportedAssignee) {
6442
+ queuedImportedIssueWakeups.push({
6443
+ assigneeAgentId: paperclipIssue.assigneeAgentId,
6444
+ paperclipIssueId: importedIssue.paperclipIssueId
6445
+ });
6446
+ }
6190
6447
  } catch (error) {
6191
6448
  if (isGitHubRateLimitError(error)) {
6192
6449
  throw error;
@@ -6205,6 +6462,16 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
6205
6462
  }
6206
6463
  }
6207
6464
  }
6465
+ await mapWithConcurrency(
6466
+ queuedImportedIssueWakeups,
6467
+ IMPORTED_ISSUE_WAKEUP_CONCURRENCY,
6468
+ async (queuedWakeup) => wakePaperclipAssigneeForImportedIssue(ctx, {
6469
+ assigneeAgentId: queuedWakeup.assigneeAgentId,
6470
+ paperclipIssueId: queuedWakeup.paperclipIssueId,
6471
+ companyId: mapping.companyId,
6472
+ paperclipApiBaseUrl
6473
+ })
6474
+ );
6208
6475
  return {
6209
6476
  updatedStatusesCount,
6210
6477
  updatedLabelsCount,
@@ -6223,19 +6490,32 @@ async function getResolvedConfig(ctx) {
6223
6490
  }
6224
6491
  function getConfiguredGithubTokenSource(settings, config, companyId) {
6225
6492
  const normalizedCompanyId = normalizeCompanyId(companyId);
6226
- const secretRef = (normalizedCompanyId ? normalizeSecretRef(config.githubTokenRefs?.[normalizedCompanyId]) ?? getSavedGitHubTokenRef(settings, normalizedCompanyId) : void 0) ?? normalizeGitHubTokenRef(config.githubTokenRef) ?? getSavedGitHubTokenRef(settings);
6493
+ const hasScopedGitHubTokenRefs = hasAnyScopedValue(settings?.githubTokenRefs) || hasAnyScopedValue(config.githubTokenRefs);
6494
+ 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) ?? (() => {
6495
+ const configuredRefs = [
6496
+ ...Object.values(config.githubTokenRefs ?? {}),
6497
+ ...Object.values(settings?.githubTokenRefs ?? {})
6498
+ ].map((value) => normalizeGitHubTokenRef(value)).filter((value) => Boolean(value));
6499
+ const uniqueRefs = [...new Set(configuredRefs)];
6500
+ return uniqueRefs.length === 1 ? uniqueRefs[0] : void 0;
6501
+ })();
6227
6502
  if (secretRef) {
6228
6503
  return { secretRef };
6229
6504
  }
6230
- const token = normalizeGitHubToken(config.githubToken);
6505
+ const token = !normalizedCompanyId || !hasScopedGitHubTokenRefs ? normalizeGitHubToken(config.githubToken) : void 0;
6231
6506
  return token ? { token } : {};
6232
6507
  }
6233
- function getConfiguredGithubTokenRef(settings, config, companyId) {
6234
- return getConfiguredGithubTokenSource(settings, config, companyId).secretRef;
6235
- }
6236
6508
  function hasConfiguredGithubToken(settings, config, companyId) {
6237
6509
  const configuredTokenSource = getConfiguredGithubTokenSource(settings, config, companyId);
6238
- return Boolean(configuredTokenSource.secretRef ?? configuredTokenSource.token);
6510
+ if (configuredTokenSource.secretRef ?? configuredTokenSource.token) {
6511
+ return true;
6512
+ }
6513
+ if (normalizeCompanyId(companyId)) {
6514
+ return false;
6515
+ }
6516
+ return Boolean(
6517
+ settings?.githubTokenRefs && Object.keys(settings.githubTokenRefs).length > 0 || config.githubTokenRefs && Object.keys(config.githubTokenRefs).length > 0
6518
+ );
6239
6519
  }
6240
6520
  function getSavedPaperclipBoardApiTokenRef(settings, companyId) {
6241
6521
  if (!companyId) {
@@ -6937,6 +7217,7 @@ async function listAllGitHubIssueComments(octokit, repository, issueNumber) {
6937
7217
  body: typeof comment.body === "string" ? stripNullBytes(comment.body) : "",
6938
7218
  url: comment.html_url ?? void 0,
6939
7219
  authorLogin: normalizeGitHubUserLogin(comment.user?.login),
7220
+ ...normalizeGitHubLowercaseString(comment.author_association) ? { authorAssociation: normalizeGitHubLowercaseString(comment.author_association) } : {},
6940
7221
  authorUrl: comment.user?.html_url ?? void 0,
6941
7222
  authorAvatarUrl: comment.user?.avatar_url ?? void 0,
6942
7223
  createdAt: comment.created_at ?? void 0,
@@ -8079,7 +8360,7 @@ async function buildProjectPullRequestsPageData(ctx, input) {
8079
8360
  }
8080
8361
  const scope = await requireProjectPullRequestScope(ctx, input, projectMappings);
8081
8362
  const config = await getResolvedConfig(ctx);
8082
- if (!hasConfiguredGithubToken(settings, config, companyId)) {
8363
+ if (!hasConfiguredGithubToken(settings, config, scope.companyId)) {
8083
8364
  return {
8084
8365
  status: "missing_token",
8085
8366
  projectId,
@@ -8098,7 +8379,7 @@ async function buildProjectPullRequestsPageData(ctx, input) {
8098
8379
  };
8099
8380
  }
8100
8381
  try {
8101
- const octokit = await createGitHubToolOctokit(ctx, companyId);
8382
+ const octokit = await createGitHubToolOctokit(ctx, scope.companyId);
8102
8383
  const pageCacheKey = buildProjectPullRequestPageCacheKey(scope, filter, pageIndex, cursor);
8103
8384
  const cachedPage = getFreshCacheValue(activeProjectPullRequestPageCache, pageCacheKey);
8104
8385
  if (cachedPage) {
@@ -9171,12 +9452,25 @@ function shouldRunScheduledSync(settings, scheduledAt) {
9171
9452
  }
9172
9453
  return now - lastCheckedAt >= settings.scheduleFrequencyMinutes * 6e4;
9173
9454
  }
9455
+ function listScheduledSyncTargets(settings) {
9456
+ const companyIds = [
9457
+ ...new Set(
9458
+ settings.mappings.map((mapping) => normalizeCompanyId(mapping.companyId)).filter((companyId) => Boolean(companyId))
9459
+ )
9460
+ ];
9461
+ if (companyIds.length === 0) {
9462
+ return settings.mappings.length > 0 ? [void 0] : [];
9463
+ }
9464
+ return companyIds.map((companyId) => getScopedSyncTarget(companyId));
9465
+ }
9174
9466
  async function performSync(ctx, trigger, options = {}) {
9175
- const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
9467
+ const targetCompanyId = normalizeCompanyId(options.target?.companyId);
9468
+ const baseSettings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
9176
9469
  const config = await getResolvedConfig(ctx);
9470
+ const settings = materializeScopedSettings(baseSettings, config, targetCompanyId);
9177
9471
  const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
9178
- const token = typeof options.resolvedToken === "string" ? options.resolvedToken : await resolveGithubToken(ctx);
9179
- const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(settings, config);
9472
+ const token = typeof options.resolvedToken === "string" ? options.resolvedToken : await resolveGithubToken(ctx, { companyId: targetCompanyId });
9473
+ const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(baseSettings, config, targetCompanyId);
9180
9474
  const mappings = getSyncableMappingsForTarget(settings.mappings, options.target);
9181
9475
  activePaperclipApiAuthTokensByCompanyId = null;
9182
9476
  const failureContext = {
@@ -9187,18 +9481,14 @@ async function performSync(ctx, trigger, options = {}) {
9187
9481
  ...settings,
9188
9482
  syncState: createSetupConfigurationErrorSyncState("missing_token", trigger)
9189
9483
  };
9190
- await ctx.state.set(SETTINGS_SCOPE, next);
9191
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9192
- return next;
9484
+ return saveSettingsSyncState(ctx, settings, next.syncState, targetCompanyId);
9193
9485
  }
9194
9486
  if (mappings.length === 0) {
9195
9487
  const next = {
9196
9488
  ...settings,
9197
9489
  syncState: createSetupConfigurationErrorSyncState("missing_mapping", trigger)
9198
9490
  };
9199
- await ctx.state.set(SETTINGS_SCOPE, next);
9200
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9201
- return next;
9491
+ return saveSettingsSyncState(ctx, settings, next.syncState, targetCompanyId);
9202
9492
  }
9203
9493
  const mappingsMissingBoardAccess = getMappingsMissingPaperclipBoardAccess(settings, config, mappings);
9204
9494
  if (mappingsMissingBoardAccess.length > 0 && await detectPaperclipBoardAccessRequirement(paperclipApiBaseUrl)) {
@@ -9206,9 +9496,7 @@ async function performSync(ctx, trigger, options = {}) {
9206
9496
  ...settings,
9207
9497
  syncState: createSetupConfigurationErrorSyncState("missing_board_access", trigger)
9208
9498
  };
9209
- await ctx.state.set(SETTINGS_SCOPE, next);
9210
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9211
- return next;
9499
+ return saveSettingsSyncState(ctx, settings, next.syncState, targetCompanyId);
9212
9500
  }
9213
9501
  if (!ctx.issues || typeof ctx.issues.create !== "function") {
9214
9502
  const errorDetails = {
@@ -9234,9 +9522,7 @@ async function performSync(ctx, trigger, options = {}) {
9234
9522
  )
9235
9523
  })
9236
9524
  };
9237
- await ctx.state.set(SETTINGS_SCOPE, next);
9238
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9239
- return next;
9525
+ return saveSettingsSyncState(ctx, settings, next.syncState, targetCompanyId);
9240
9526
  }
9241
9527
  activePaperclipApiAuthTokensByCompanyId = await resolvePaperclipApiAuthTokens(ctx, settings, config, mappings);
9242
9528
  const octokit = new Octokit({ auth: token });
@@ -9304,7 +9590,8 @@ async function performSync(ctx, trigger, options = {}) {
9304
9590
  erroredIssuesCount: recoverableFailures.length,
9305
9591
  progress,
9306
9592
  recentFailures
9307
- })
9593
+ }),
9594
+ targetCompanyId
9308
9595
  );
9309
9596
  activeRunningSyncState = currentSettings;
9310
9597
  lastProgressPersistedAt = now;
@@ -9641,10 +9928,9 @@ async function performSync(ctx, trigger, options = {}) {
9641
9928
  recentFailures: buildRecentSyncFailureLogEntries(recoverableFailures)
9642
9929
  })
9643
9930
  };
9644
- await ctx.state.set(SETTINGS_SCOPE, next2);
9645
- await ctx.state.set(SYNC_STATE_SCOPE, next2.syncState);
9931
+ await saveSettingsSyncState(ctx, currentSettings, next2.syncState, targetCompanyId);
9646
9932
  await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextRegistry);
9647
- return next2;
9933
+ return materializeScopedSettings(next2, config, targetCompanyId);
9648
9934
  }
9649
9935
  const next = {
9650
9936
  ...currentSettings,
@@ -9700,10 +9986,9 @@ async function performSync(ctx, trigger, options = {}) {
9700
9986
  )
9701
9987
  })
9702
9988
  };
9703
- await ctx.state.set(SETTINGS_SCOPE, next);
9704
- await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
9989
+ await saveSettingsSyncState(ctx, currentSettings, next.syncState, targetCompanyId);
9705
9990
  await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextRegistry);
9706
- return next;
9991
+ return materializeScopedSettings(next, config, targetCompanyId);
9707
9992
  }
9708
9993
  }
9709
9994
  async function startSync(ctx, trigger, options = {}) {
@@ -9721,28 +10006,32 @@ async function startSync(ctx, trigger, options = {}) {
9721
10006
  getResolvedConfig(ctx),
9722
10007
  ctx.state.get(SETTINGS_SCOPE).then((value) => normalizeSettings(value))
9723
10008
  ]);
9724
- const targetCompanyId = options.target?.companyId;
10009
+ const targetCompanyId = normalizeCompanyId(options.target?.companyId);
9725
10010
  const token = await resolveGithubToken(ctx, {
10011
+ companyId: targetCompanyId,
9726
10012
  config,
9727
- settings: persistedSettings,
9728
- companyId: targetCompanyId
10013
+ settings: persistedSettings
9729
10014
  }).catch(() => "");
9730
- let currentSettings = sanitizeSettingsForCurrentSetup(persistedSettings, {
10015
+ let currentSettings = sanitizeSettingsForCurrentSetup(materializeScopedSettings(persistedSettings, config, targetCompanyId), {
9731
10016
  hasToken: Boolean(token.trim()),
9732
- hasMappings: getSyncableMappings(persistedSettings.mappings).length > 0
10017
+ hasMappings: getSyncableMappingsForScope(persistedSettings.mappings, targetCompanyId).length > 0
9733
10018
  });
9734
- const nextPaperclipApiBaseUrl = trigger === "manual" ? resolveTrustedPaperclipApiBaseUrlInput(options.paperclipApiBaseUrl, currentSettings, config) : getConfiguredPaperclipApiBaseUrl(currentSettings, config);
10019
+ const nextPaperclipApiBaseUrl = trigger === "manual" ? resolveTrustedPaperclipApiBaseUrlInput(options.paperclipApiBaseUrl, persistedSettings, config, targetCompanyId) : getConfiguredPaperclipApiBaseUrl(persistedSettings, config, targetCompanyId);
9735
10020
  if (nextPaperclipApiBaseUrl !== currentSettings.paperclipApiBaseUrl) {
9736
- currentSettings = {
9737
- ...currentSettings,
9738
- ...nextPaperclipApiBaseUrl ? { paperclipApiBaseUrl: nextPaperclipApiBaseUrl } : {},
9739
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
9740
- };
9741
- await ctx.state.set(SETTINGS_SCOPE, currentSettings);
10021
+ const nextPersistedSettings = upsertScopedPaperclipApiBaseUrl(
10022
+ {
10023
+ ...persistedSettings,
10024
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10025
+ },
10026
+ nextPaperclipApiBaseUrl,
10027
+ targetCompanyId
10028
+ );
10029
+ await ctx.state.set(SETTINGS_SCOPE, nextPersistedSettings);
9742
10030
  await ctx.state.set(SYNC_STATE_SCOPE, currentSettings.syncState);
10031
+ currentSettings = materializeScopedSettings(nextPersistedSettings, config, targetCompanyId);
9743
10032
  }
9744
- if (currentSettings !== persistedSettings) {
9745
- await saveSettingsSyncState(ctx, currentSettings, currentSettings.syncState);
10033
+ if (JSON.stringify(currentSettings.syncState) !== JSON.stringify(getScopedSyncState(persistedSettings, targetCompanyId))) {
10034
+ await saveSettingsSyncState(ctx, persistedSettings, currentSettings.syncState, targetCompanyId);
9746
10035
  }
9747
10036
  if (getActiveGitHubRateLimitPause(currentSettings.syncState)) {
9748
10037
  return currentSettings;
@@ -9771,7 +10060,8 @@ async function startSync(ctx, trigger, options = {}) {
9771
10060
  ...currentSettings,
9772
10061
  syncState
9773
10062
  };
9774
- return saveSettingsSyncState(ctx, currentSettings, syncState);
10063
+ activeRunningSyncCompanyId = targetCompanyId;
10064
+ return saveSettingsSyncState(ctx, currentSettings, syncState, targetCompanyId);
9775
10065
  })();
9776
10066
  activeSyncPromise = (async () => {
9777
10067
  try {
@@ -9781,11 +10071,12 @@ async function startSync(ctx, trigger, options = {}) {
9781
10071
  target: options.target
9782
10072
  });
9783
10073
  } catch (error) {
9784
- return await createUnexpectedSyncErrorResult(ctx, trigger, error);
10074
+ return await createUnexpectedSyncErrorResult(ctx, trigger, error, targetCompanyId);
9785
10075
  } finally {
9786
10076
  await setSyncCancellationRequest(ctx, null);
9787
10077
  activePaperclipApiAuthTokensByCompanyId = null;
9788
10078
  activeRunningSyncState = null;
10079
+ activeRunningSyncCompanyId = void 0;
9789
10080
  activeSyncPromise = null;
9790
10081
  }
9791
10082
  })();
@@ -10575,38 +10866,18 @@ var plugin = definePlugin({
10575
10866
  const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
10576
10867
  const normalizedSettings = normalizeSettings(saved);
10577
10868
  const config = await getResolvedConfig(ctx);
10578
- const configuredGitHubTokenRef = requestedCompanyId ? normalizeSecretRef(config.githubTokenRefs?.[requestedCompanyId]) : normalizeGitHubTokenRef(config.githubTokenRef);
10579
- const savedGitHubTokenRef = getSavedGitHubTokenRef(normalizedSettings, requestedCompanyId);
10580
- const githubTokenRef = getConfiguredGithubTokenRef(normalizedSettings, config, requestedCompanyId);
10581
- const githubTokenLogin = getSavedGitHubTokenLogin(normalizedSettings, requestedCompanyId);
10582
- const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(normalizedSettings, config);
10583
10869
  const githubTokenConfigured = hasConfiguredGithubToken(normalizedSettings, config, requestedCompanyId);
10584
10870
  const configuredBoardTokenRef = getConfiguredPaperclipBoardApiTokenRef(config, requestedCompanyId);
10585
10871
  const savedBoardTokenRef = getSavedPaperclipBoardApiTokenRef(normalizedSettings, requestedCompanyId);
10586
- const settingsWithResolvedToken = githubTokenRef === getSavedGitHubTokenRef(normalizedSettings, requestedCompanyId) && githubTokenLogin === getSavedGitHubTokenLogin(normalizedSettings, requestedCompanyId) && paperclipApiBaseUrl === normalizedSettings.paperclipApiBaseUrl ? normalizedSettings : {
10587
- ...normalizedSettings,
10588
- ...requestedCompanyId && githubTokenRef ? {
10589
- githubTokenRefs: {
10590
- ...normalizedSettings.githubTokenRefs ?? {},
10591
- [requestedCompanyId]: githubTokenRef
10592
- }
10593
- } : {},
10594
- ...requestedCompanyId && githubTokenLogin ? {
10595
- githubTokenLoginsByCompanyId: {
10596
- ...normalizedSettings.githubTokenLoginsByCompanyId ?? {},
10597
- [requestedCompanyId]: githubTokenLogin
10598
- }
10599
- } : {},
10600
- ...!requestedCompanyId && githubTokenRef ? { githubTokenRef } : {},
10601
- ...!requestedCompanyId && githubTokenLogin ? { githubTokenLogin } : {},
10602
- ...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {}
10603
- };
10604
- const settingsForResponse = sanitizeSettingsForCurrentSetup(settingsWithResolvedToken, {
10605
- hasToken: githubTokenConfigured,
10606
- hasMappings: getSyncableMappings(settingsWithResolvedToken.mappings).length > 0
10607
- });
10608
- if (settingsForResponse !== normalizedSettings) {
10609
- await saveSettingsSyncState(ctx, settingsForResponse, settingsForResponse.syncState);
10872
+ const settingsForResponse = sanitizeSettingsForCurrentSetup(
10873
+ materializeScopedSettings(normalizedSettings, config, requestedCompanyId),
10874
+ {
10875
+ hasToken: githubTokenConfigured,
10876
+ hasMappings: getSyncableMappingsForScope(normalizedSettings.mappings, requestedCompanyId).length > 0
10877
+ }
10878
+ );
10879
+ if (JSON.stringify(settingsForResponse.syncState) !== JSON.stringify(getScopedSyncState(normalizedSettings, requestedCompanyId))) {
10880
+ await saveSettingsSyncState(ctx, normalizedSettings, settingsForResponse.syncState, requestedCompanyId);
10610
10881
  }
10611
10882
  const scopedMappings = filterMappingsByCompany(settingsForResponse.mappings, requestedCompanyId);
10612
10883
  const availableAssignees = includeAssignees && requestedCompanyId ? await listAvailableAssignees(ctx, requestedCompanyId) : [];
@@ -10615,9 +10886,6 @@ var plugin = definePlugin({
10615
10886
  ...includeAssignees ? { availableAssignees } : {},
10616
10887
  totalSyncedIssuesCount: countImportedIssuesForMappings(importRegistry, scopedMappings),
10617
10888
  githubTokenConfigured,
10618
- ...githubTokenLogin ? { githubTokenLogin } : {},
10619
- ...savedGitHubTokenRef ? { githubTokenConfigSyncRef: savedGitHubTokenRef } : {},
10620
- githubTokenNeedsConfigSync: Boolean(requestedCompanyId && savedGitHubTokenRef && !configuredGitHubTokenRef),
10621
10889
  paperclipBoardAccessConfigured: requestedCompanyId ? hasConfiguredPaperclipBoardAccess(settingsForResponse, config, requestedCompanyId) : hasConfiguredPaperclipBoardAccessForMappings(settingsForResponse, config, scopedMappings),
10622
10890
  ...savedBoardTokenRef ? { paperclipBoardAccessConfigSyncRef: savedBoardTokenRef } : {},
10623
10891
  paperclipBoardAccessNeedsConfigSync: Boolean(savedBoardTokenRef && !configuredBoardTokenRef)
@@ -10668,34 +10936,38 @@ var plugin = definePlugin({
10668
10936
  const config = await getResolvedConfig(ctx);
10669
10937
  const record = input && typeof input === "object" ? input : {};
10670
10938
  const requestedCompanyId = normalizeCompanyId(record.companyId);
10939
+ const requestedGitHubTokenLogin = "githubTokenLogin" in record ? normalizeOptionalString2(record.githubTokenLogin) : void 0;
10671
10940
  const hasMappingsPatch = "mappings" in record;
10672
10941
  const hasAdvancedSettingsPatch = "advancedSettings" in record;
10673
- const githubTokenRef = "githubTokenRef" in record ? normalizeGitHubTokenRef(record.githubTokenRef) : void 0;
10674
- const githubTokenLogin = "githubTokenLogin" in record ? normalizeOptionalString2(record.githubTokenLogin) : void 0;
10675
- const inputMappings = hasMappingsPatch ? normalizeMappings(record.mappings) : previous.mappings;
10942
+ const previousScopedSettings = materializeScopedSettings(previous, config, requestedCompanyId);
10676
10943
  const nextGitHubTokenRefs = {
10677
10944
  ...previous.githubTokenRefs ?? {}
10678
10945
  };
10679
- const nextGitHubTokenLoginsByCompanyId = {
10680
- ...previous.githubTokenLoginsByCompanyId ?? {}
10681
- };
10682
- const nextCompanyAdvancedSettingsByCompanyId = {
10683
- ...previous.companyAdvancedSettingsByCompanyId ?? {}
10684
- };
10685
- if (requestedCompanyId && "githubTokenRef" in record) {
10686
- if (githubTokenRef) {
10687
- nextGitHubTokenRefs[requestedCompanyId] = githubTokenRef;
10688
- } else {
10689
- delete nextGitHubTokenRefs[requestedCompanyId];
10946
+ if (requestedCompanyId) {
10947
+ const companyScopedGitHubTokenRef = normalizeGitHubTokenRefs(record.githubTokenRefs)?.[requestedCompanyId] ?? ("githubTokenRef" in record ? normalizeGitHubTokenRef(record.githubTokenRef) : void 0);
10948
+ if (companyScopedGitHubTokenRef) {
10949
+ nextGitHubTokenRefs[requestedCompanyId] = companyScopedGitHubTokenRef;
10690
10950
  }
10691
10951
  }
10952
+ const githubTokenRefs = Object.keys(nextGitHubTokenRefs).length > 0 ? nextGitHubTokenRefs : void 0;
10953
+ const githubTokenRef = !requestedCompanyId && "githubTokenRef" in record ? normalizeGitHubTokenRef(record.githubTokenRef) : normalizeGitHubTokenRef(previous.githubTokenRef) ?? normalizeGitHubTokenRef(config.githubTokenRef);
10954
+ const nextGitHubTokenLoginByCompanyId = {
10955
+ ...previous.githubTokenLoginByCompanyId ?? {}
10956
+ };
10692
10957
  if (requestedCompanyId && "githubTokenLogin" in record) {
10693
- if (githubTokenLogin) {
10694
- nextGitHubTokenLoginsByCompanyId[requestedCompanyId] = githubTokenLogin;
10958
+ const companyScopedGitHubTokenLogin = requestedGitHubTokenLogin;
10959
+ if (companyScopedGitHubTokenLogin) {
10960
+ nextGitHubTokenLoginByCompanyId[requestedCompanyId] = companyScopedGitHubTokenLogin;
10695
10961
  } else {
10696
- delete nextGitHubTokenLoginsByCompanyId[requestedCompanyId];
10962
+ delete nextGitHubTokenLoginByCompanyId[requestedCompanyId];
10697
10963
  }
10698
10964
  }
10965
+ const githubTokenLoginByCompanyId = Object.keys(nextGitHubTokenLoginByCompanyId).length > 0 ? nextGitHubTokenLoginByCompanyId : void 0;
10966
+ const githubTokenLogin = !requestedCompanyId && "githubTokenLogin" in record ? requestedGitHubTokenLogin : previous.githubTokenLogin;
10967
+ const inputMappings = hasMappingsPatch ? normalizeMappings(record.mappings) : previous.mappings;
10968
+ const nextCompanyAdvancedSettingsByCompanyId = {
10969
+ ...previous.companyAdvancedSettingsByCompanyId ?? {}
10970
+ };
10699
10971
  if (requestedCompanyId && hasAdvancedSettingsPatch) {
10700
10972
  nextCompanyAdvancedSettingsByCompanyId[requestedCompanyId] = normalizeAdvancedSettings(record.advancedSettings);
10701
10973
  }
@@ -10706,19 +10978,26 @@ var plugin = definePlugin({
10706
10978
  companyId: requestedCompanyId
10707
10979
  }))
10708
10980
  ] : inputMappings;
10709
- const current = normalizeSettings({
10981
+ let current = normalizeSettings({
10710
10982
  mappings: mergedMappings,
10711
10983
  syncState: previous.syncState,
10712
- scheduleFrequencyMinutes: "scheduleFrequencyMinutes" in record ? record.scheduleFrequencyMinutes : previous.scheduleFrequencyMinutes,
10713
- paperclipApiBaseUrl: "paperclipApiBaseUrl" in record ? resolveTrustedPaperclipApiBaseUrlInput(record.paperclipApiBaseUrl, previous, config) : getConfiguredPaperclipApiBaseUrl(previous, config),
10714
- ...Object.keys(nextGitHubTokenRefs).length > 0 ? { githubTokenRefs: nextGitHubTokenRefs } : {},
10715
- ...Object.keys(nextGitHubTokenLoginsByCompanyId).length > 0 ? { githubTokenLoginsByCompanyId: nextGitHubTokenLoginsByCompanyId } : {},
10716
- ...!requestedCompanyId ? githubTokenLogin ? { githubTokenLogin } : previous.githubTokenLogin ? { githubTokenLogin: previous.githubTokenLogin } : {} : {},
10984
+ ...previous.syncStateByCompanyId ? { syncStateByCompanyId: previous.syncStateByCompanyId } : {},
10985
+ scheduleFrequencyMinutes: previous.scheduleFrequencyMinutes,
10986
+ ...previous.scheduleFrequencyMinutesByCompanyId ? { scheduleFrequencyMinutesByCompanyId: previous.scheduleFrequencyMinutesByCompanyId } : {},
10987
+ ...previous.paperclipApiBaseUrl ? { paperclipApiBaseUrl: previous.paperclipApiBaseUrl } : {},
10988
+ ...previous.paperclipApiBaseUrlByCompanyId ? { paperclipApiBaseUrlByCompanyId: previous.paperclipApiBaseUrlByCompanyId } : {},
10989
+ ...githubTokenRefs ? { githubTokenRefs } : {},
10990
+ ...githubTokenLoginByCompanyId ? { githubTokenLoginByCompanyId } : {},
10991
+ ...githubTokenLogin ? { githubTokenLogin } : {},
10717
10992
  paperclipBoardApiTokenRefs: previous.paperclipBoardApiTokenRefs,
10718
10993
  paperclipBoardAccessIdentityByCompanyId: previous.paperclipBoardAccessIdentityByCompanyId,
10719
10994
  ...Object.keys(nextCompanyAdvancedSettingsByCompanyId).length > 0 ? { companyAdvancedSettingsByCompanyId: nextCompanyAdvancedSettingsByCompanyId } : {},
10720
- ...!requestedCompanyId ? githubTokenRef ? { githubTokenRef } : previous.githubTokenRef ? { githubTokenRef: previous.githubTokenRef } : {} : {}
10995
+ ...githubTokenRef ? { githubTokenRef } : {}
10721
10996
  });
10997
+ const nextScheduleFrequencyMinutes = "scheduleFrequencyMinutes" in record ? normalizeScheduleFrequencyMinutes(record.scheduleFrequencyMinutes) : getScopedScheduleFrequencyMinutes(previous, requestedCompanyId);
10998
+ current = upsertScopedScheduleFrequencyMinutes(current, nextScheduleFrequencyMinutes, requestedCompanyId);
10999
+ const nextPaperclipApiBaseUrl = "paperclipApiBaseUrl" in record ? resolveTrustedPaperclipApiBaseUrlInput(record.paperclipApiBaseUrl, previous, config, requestedCompanyId) : getConfiguredPaperclipApiBaseUrl(previous, config, requestedCompanyId);
11000
+ current = upsertScopedPaperclipApiBaseUrl(current, nextPaperclipApiBaseUrl, requestedCompanyId);
10722
11001
  const nextMappings = current.mappings.map((mapping, index) => ({
10723
11002
  id: mapping.id.trim() || createMappingId(index),
10724
11003
  repositoryUrl: parseRepositoryReference(mapping.repositoryUrl)?.url ?? mapping.repositoryUrl.trim(),
@@ -10726,27 +11005,29 @@ var plugin = definePlugin({
10726
11005
  paperclipProjectId: mapping.paperclipProjectId,
10727
11006
  companyId: mapping.companyId
10728
11007
  }));
11008
+ const materializedCurrent = materializeScopedSettings(current, config, requestedCompanyId);
10729
11009
  const next = sanitizeSettingsForCurrentSetup({
11010
+ ...current,
10730
11011
  mappings: nextMappings,
10731
- syncState: previous.syncState,
10732
- scheduleFrequencyMinutes: current.scheduleFrequencyMinutes,
10733
- ...current.paperclipApiBaseUrl ? { paperclipApiBaseUrl: current.paperclipApiBaseUrl } : {},
11012
+ syncState: materializedCurrent.syncState,
11013
+ scheduleFrequencyMinutes: materializedCurrent.scheduleFrequencyMinutes,
11014
+ ...materializedCurrent.paperclipApiBaseUrl ? { paperclipApiBaseUrl: materializedCurrent.paperclipApiBaseUrl } : {},
10734
11015
  ...current.githubTokenRefs ? { githubTokenRefs: current.githubTokenRefs } : {},
10735
- ...current.githubTokenLoginsByCompanyId ? { githubTokenLoginsByCompanyId: current.githubTokenLoginsByCompanyId } : {},
11016
+ ...current.githubTokenLoginByCompanyId ? { githubTokenLoginByCompanyId: current.githubTokenLoginByCompanyId } : {},
10736
11017
  ...current.githubTokenLogin ? { githubTokenLogin: current.githubTokenLogin } : {},
10737
11018
  ...current.paperclipBoardApiTokenRefs ? { paperclipBoardApiTokenRefs: current.paperclipBoardApiTokenRefs } : {},
10738
11019
  ...current.paperclipBoardAccessIdentityByCompanyId ? { paperclipBoardAccessIdentityByCompanyId: current.paperclipBoardAccessIdentityByCompanyId } : {},
10739
11020
  ...current.companyAdvancedSettingsByCompanyId ? { companyAdvancedSettingsByCompanyId: current.companyAdvancedSettingsByCompanyId } : {},
10740
- ...current.githubTokenRef ? { githubTokenRef: current.githubTokenRef } : {},
11021
+ ...githubTokenRef ? { githubTokenRef } : {},
10741
11022
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10742
11023
  }, {
10743
11024
  hasToken: hasConfiguredGithubToken(current, config, requestedCompanyId),
10744
- hasMappings: getSyncableMappings(nextMappings).length > 0
11025
+ hasMappings: getSyncableMappingsForScope(nextMappings, requestedCompanyId).length > 0
10745
11026
  });
10746
11027
  await ctx.state.set(SETTINGS_SCOPE, next);
10747
11028
  await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
10748
11029
  clearGitHubRepositoryTokenCapabilityAudits();
10749
- const scopedGitHubTokenLogin = getSavedGitHubTokenLogin(next, requestedCompanyId);
11030
+ const scopedGitHubTokenLogin = (requestedCompanyId && "githubTokenLogin" in record ? requestedGitHubTokenLogin : void 0) ?? getGitHubTokenLogin(next, requestedCompanyId);
10750
11031
  return {
10751
11032
  ...getPublicSettingsForScope(next, requestedCompanyId),
10752
11033
  ...scopedGitHubTokenLogin ? { githubTokenLogin: scopedGitHubTokenLogin } : {},
@@ -10787,15 +11068,16 @@ var plugin = definePlugin({
10787
11068
  paperclipBoardAccessIdentityByCompanyId: _previousPaperclipBoardAccessIdentityByCompanyId,
10788
11069
  ...previousWithoutBoardAccess
10789
11070
  } = previous;
10790
- const next = sanitizeSettingsForCurrentSetup({
11071
+ const nextBase = sanitizeSettingsForCurrentSetup({
10791
11072
  ...previousWithoutBoardAccess,
10792
11073
  ...Object.keys(nextPaperclipBoardApiTokenRefs).length > 0 ? { paperclipBoardApiTokenRefs: nextPaperclipBoardApiTokenRefs } : {},
10793
11074
  ...Object.keys(nextPaperclipBoardAccessIdentityByCompanyId).length > 0 ? { paperclipBoardAccessIdentityByCompanyId: nextPaperclipBoardAccessIdentityByCompanyId } : {},
10794
11075
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
10795
11076
  }, {
10796
11077
  hasToken: hasConfiguredGithubToken(previous, config, companyId),
10797
- hasMappings: getSyncableMappings(previous.mappings).length > 0
11078
+ hasMappings: getSyncableMappingsForScope(previous.mappings, companyId).length > 0
10798
11079
  });
11080
+ const next = materializeScopedSettings(nextBase, config, companyId);
10799
11081
  await ctx.state.set(SETTINGS_SCOPE, next);
10800
11082
  await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
10801
11083
  return {
@@ -10866,7 +11148,7 @@ var plugin = definePlugin({
10866
11148
  });
10867
11149
  });
10868
11150
  ctx.actions.register("sync.cancel", async () => {
10869
- const currentSettings = await getActiveOrCurrentSyncState(ctx);
11151
+ const currentSettings = await getActiveOrCurrentSyncState(ctx, activeRunningSyncCompanyId);
10870
11152
  if (currentSettings.syncState.status !== "running") {
10871
11153
  return currentSettings;
10872
11154
  }
@@ -10886,7 +11168,8 @@ var plugin = definePlugin({
10886
11168
  progress: currentSettings.syncState.progress,
10887
11169
  message: CANCELLING_SYNC_MESSAGE,
10888
11170
  cancelRequestedAt: cancellationRequest.requestedAt
10889
- })
11171
+ }),
11172
+ activeRunningSyncCompanyId
10890
11173
  );
10891
11174
  activeRunningSyncState = next;
10892
11175
  return next;
@@ -10894,10 +11177,22 @@ var plugin = definePlugin({
10894
11177
  registerGitHubAgentTools(ctx);
10895
11178
  ctx.jobs.register("sync.github-issues", async (job) => {
10896
11179
  const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
10897
- if (job.trigger === "schedule" && !shouldRunScheduledSync(settings, job.scheduledAt)) {
11180
+ const trigger = job.trigger === "retry" ? "retry" : "schedule";
11181
+ const scheduledTargets = listScheduledSyncTargets(settings);
11182
+ if (scheduledTargets.length === 0) {
11183
+ if (job.trigger === "schedule" && !shouldRunScheduledSync(settings, job.scheduledAt)) {
11184
+ return;
11185
+ }
11186
+ await startSync(ctx, trigger);
10898
11187
  return;
10899
11188
  }
10900
- await startSync(ctx, job.trigger === "retry" ? "retry" : "schedule");
11189
+ for (const target of scheduledTargets) {
11190
+ const scopedSettings = materializeScopedSettings(settings, null, target?.companyId);
11191
+ if (job.trigger === "schedule" && !shouldRunScheduledSync(scopedSettings, job.scheduledAt)) {
11192
+ continue;
11193
+ }
11194
+ await startSync(ctx, trigger, target ? { target } : {});
11195
+ }
10901
11196
  });
10902
11197
  }
10903
11198
  });