paperclip-github-plugin 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui/index.js CHANGED
@@ -26535,12 +26535,21 @@ function normalizeGitHubUsername(value) {
26535
26535
  const trimmed = value.trim().replace(/^@+/, "").toLowerCase();
26536
26536
  return trimmed ? trimmed : null;
26537
26537
  }
26538
+ function normalizeOptionalText(value) {
26539
+ return typeof value === "string" && value.trim() ? value.trim() : null;
26540
+ }
26538
26541
  function normalizeIgnoredIssueAuthorUsernames(value) {
26539
26542
  const rawEntries = Array.isArray(value) ? value : typeof value === "string" ? value.split(/[\s,]+/g) : [];
26540
26543
  return [...new Set(
26541
26544
  rawEntries.map((entry) => typeof entry === "string" ? normalizeGitHubUsername(entry) : null).filter((entry) => Boolean(entry))
26542
26545
  )];
26543
26546
  }
26547
+ function normalizeAgentIds(value) {
26548
+ const rawEntries = Array.isArray(value) ? value : [];
26549
+ return [...new Set(
26550
+ rawEntries.map((entry) => typeof entry === "string" && entry.trim() ? entry.trim() : null).filter((entry) => Boolean(entry))
26551
+ )].sort((left, right) => left.localeCompare(right));
26552
+ }
26544
26553
  function normalizeAdvancedSettings(value) {
26545
26554
  if (!value || typeof value !== "object") {
26546
26555
  return DEFAULT_ADVANCED_SETTINGS;
@@ -26550,7 +26559,8 @@ function normalizeAdvancedSettings(value) {
26550
26559
  return {
26551
26560
  ...defaultAssigneeAgentId ? { defaultAssigneeAgentId } : {},
26552
26561
  defaultStatus: normalizePaperclipIssueStatus(record.defaultStatus),
26553
- ignoredIssueAuthorUsernames: "ignoredIssueAuthorUsernames" in record ? normalizeIgnoredIssueAuthorUsernames(record.ignoredIssueAuthorUsernames) : DEFAULT_ADVANCED_SETTINGS.ignoredIssueAuthorUsernames
26562
+ ignoredIssueAuthorUsernames: "ignoredIssueAuthorUsernames" in record ? normalizeIgnoredIssueAuthorUsernames(record.ignoredIssueAuthorUsernames) : DEFAULT_ADVANCED_SETTINGS.ignoredIssueAuthorUsernames,
26563
+ ...normalizeAgentIds(record.githubTokenPropagationAgentIds).length > 0 ? { githubTokenPropagationAgentIds: normalizeAgentIds(record.githubTokenPropagationAgentIds) } : {}
26554
26564
  };
26555
26565
  }
26556
26566
  function getComparableMappings(mappings) {
@@ -26567,7 +26577,8 @@ function getComparableAdvancedSettings(value) {
26567
26577
  return {
26568
26578
  ...settings.defaultAssigneeAgentId ? { defaultAssigneeAgentId: settings.defaultAssigneeAgentId } : {},
26569
26579
  defaultStatus: settings.defaultStatus,
26570
- ignoredIssueAuthorUsernames: [...settings.ignoredIssueAuthorUsernames].sort((left, right) => left.localeCompare(right))
26580
+ ignoredIssueAuthorUsernames: [...settings.ignoredIssueAuthorUsernames].sort((left, right) => left.localeCompare(right)),
26581
+ ...settings.githubTokenPropagationAgentIds?.length ? { githubTokenPropagationAgentIds: [...settings.githubTokenPropagationAgentIds].sort((left, right) => left.localeCompare(right)) } : {}
26571
26582
  };
26572
26583
  }
26573
26584
  function formatAssigneeOptionLabel(option) {
@@ -26583,7 +26594,20 @@ function getAvailableAssigneeOptions(options, selectedAgentId) {
26583
26594
  }
26584
26595
  return normalizedOptions;
26585
26596
  }
26586
- function formatAdvancedSettingsSummary(advancedSettings, availableAssignees) {
26597
+ function getAvailablePropagationAgentOptions(options, selectedAgentIds) {
26598
+ const normalizedOptions = [...options ?? []];
26599
+ const selectedIds = normalizeAgentIds(selectedAgentIds);
26600
+ for (const selectedAgentId of selectedIds) {
26601
+ if (!normalizedOptions.some((option) => option.id === selectedAgentId)) {
26602
+ normalizedOptions.push({
26603
+ id: selectedAgentId,
26604
+ name: "Unavailable agent"
26605
+ });
26606
+ }
26607
+ }
26608
+ return normalizedOptions.sort((left, right) => left.name.localeCompare(right.name));
26609
+ }
26610
+ function formatAdvancedSettingsSummary(advancedSettings, availableAssignees, options) {
26587
26611
  const assigneeLabel = advancedSettings.defaultAssigneeAgentId ? formatAssigneeOptionLabel(
26588
26612
  availableAssignees.find((option) => option.id === advancedSettings.defaultAssigneeAgentId) ?? {
26589
26613
  id: advancedSettings.defaultAssigneeAgentId,
@@ -26592,8 +26616,41 @@ function formatAdvancedSettingsSummary(advancedSettings, availableAssignees) {
26592
26616
  ) : "Unassigned";
26593
26617
  const statusLabel = PAPERCLIP_STATUS_OPTIONS.find((option) => option.value === advancedSettings.defaultStatus)?.label ?? "Backlog";
26594
26618
  const ignoredAuthorsLabel = advancedSettings.ignoredIssueAuthorUsernames.length > 0 ? advancedSettings.ignoredIssueAuthorUsernames.join(", ") : "none";
26619
+ if (options?.includePropagation) {
26620
+ const propagatedAgentsLabel = advancedSettings.githubTokenPropagationAgentIds?.length ? `${advancedSettings.githubTokenPropagationAgentIds.length} selected` : "none";
26621
+ return `Assignee: ${assigneeLabel} \xB7 Status: ${statusLabel} \xB7 Ignore: ${ignoredAuthorsLabel} \xB7 Propagate: ${propagatedAgentsLabel}`;
26622
+ }
26595
26623
  return `Assignee: ${assigneeLabel} \xB7 Status: ${statusLabel} \xB7 Ignore: ${ignoredAuthorsLabel}`;
26596
26624
  }
26625
+ function resolveSavedTokenUiState(params) {
26626
+ if (params.githubTokenConfigured) {
26627
+ return {
26628
+ showSavedTokenHint: true,
26629
+ showTokenEditor: false,
26630
+ tokenStatusOverride: "valid",
26631
+ validatedLogin: normalizeOptionalText(params.githubTokenLogin)
26632
+ };
26633
+ }
26634
+ return {
26635
+ showSavedTokenHint: false,
26636
+ showTokenEditor: true,
26637
+ tokenStatusOverride: null,
26638
+ validatedLogin: null
26639
+ };
26640
+ }
26641
+ function formatAgentMultiSelectionLabel(values, options) {
26642
+ if (values.length === 0) {
26643
+ return "No agents selected";
26644
+ }
26645
+ const labels = values.map((value) => options.find((option) => option.value === value)?.label ?? "Unavailable agent").filter((label) => label.trim().length > 0);
26646
+ if (labels.length === 0) {
26647
+ return "No agents selected";
26648
+ }
26649
+ if (labels.length <= 2) {
26650
+ return labels.join(", ");
26651
+ }
26652
+ return `${labels.length} agents selected`;
26653
+ }
26597
26654
  function PickerChevronIcon() {
26598
26655
  return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "M4 6.5L8 10.5L12 6.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
26599
26656
  }
@@ -26748,6 +26805,122 @@ function SettingsAssigneePicker(props) {
26748
26805
  ] }) : null
26749
26806
  ] });
26750
26807
  }
26808
+ function SettingsAgentMultiPicker(props) {
26809
+ const { id, values, options, disabled, onChange } = props;
26810
+ const [open, setOpen] = useState2(false);
26811
+ const [query, setQuery] = useState2("");
26812
+ const rootRef = useRef(null);
26813
+ const searchInputRef = useRef(null);
26814
+ const selectedValues = normalizeAgentIds(values);
26815
+ const selectedValueSet = new Set(selectedValues);
26816
+ const normalizedQuery = query.trim().toLowerCase();
26817
+ const filteredOptions = normalizedQuery ? options.filter((option) => option.label.toLowerCase().includes(normalizedQuery)) : options;
26818
+ const selectedLabel = formatAgentMultiSelectionLabel(selectedValues, options);
26819
+ useEffect2(() => {
26820
+ if (!open) {
26821
+ setQuery("");
26822
+ return;
26823
+ }
26824
+ const handlePointerDown = (event) => {
26825
+ const target = event.target;
26826
+ if (target instanceof Node && rootRef.current?.contains(target)) {
26827
+ return;
26828
+ }
26829
+ setOpen(false);
26830
+ };
26831
+ const handleKeyDown = (event) => {
26832
+ if (event.key === "Escape") {
26833
+ setOpen(false);
26834
+ }
26835
+ };
26836
+ document.addEventListener("pointerdown", handlePointerDown);
26837
+ document.addEventListener("keydown", handleKeyDown);
26838
+ globalThis.setTimeout(() => {
26839
+ searchInputRef.current?.focus();
26840
+ searchInputRef.current?.select();
26841
+ }, 0);
26842
+ return () => {
26843
+ document.removeEventListener("pointerdown", handlePointerDown);
26844
+ document.removeEventListener("keydown", handleKeyDown);
26845
+ };
26846
+ }, [open]);
26847
+ useEffect2(() => {
26848
+ if (disabled && open) {
26849
+ setOpen(false);
26850
+ }
26851
+ }, [disabled, open]);
26852
+ return /* @__PURE__ */ jsxs2("div", { className: "ghsync__picker", ref: rootRef, children: [
26853
+ /* @__PURE__ */ jsxs2(
26854
+ "button",
26855
+ {
26856
+ id,
26857
+ type: "button",
26858
+ className: "ghsync__picker-trigger ghsync__picker-trigger--assignee",
26859
+ disabled,
26860
+ "aria-haspopup": "dialog",
26861
+ "aria-expanded": open,
26862
+ onClick: () => {
26863
+ if (disabled) {
26864
+ return;
26865
+ }
26866
+ setOpen((current) => !current);
26867
+ },
26868
+ children: [
26869
+ /* @__PURE__ */ jsxs2("span", { className: "ghsync__picker-trigger-main", children: [
26870
+ /* @__PURE__ */ jsx2("span", { className: "ghsync__picker-agent-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(AgentIcon, {}) }),
26871
+ /* @__PURE__ */ jsx2("span", { className: "ghsync__picker-trigger-label", children: selectedLabel })
26872
+ ] }),
26873
+ /* @__PURE__ */ jsx2("span", { className: "ghsync__picker-trigger-icon", children: /* @__PURE__ */ jsx2(PickerChevronIcon, {}) })
26874
+ ]
26875
+ }
26876
+ ),
26877
+ open ? /* @__PURE__ */ jsxs2("div", { className: "ghsync__picker-panel ghsync__picker-panel--assignee", role: "dialog", "aria-label": "Choose agents", children: [
26878
+ /* @__PURE__ */ jsx2("div", { className: "ghsync__picker-search", children: /* @__PURE__ */ jsx2(
26879
+ "input",
26880
+ {
26881
+ ref: searchInputRef,
26882
+ type: "text",
26883
+ className: "ghsync__picker-search-input",
26884
+ placeholder: "Search agents...",
26885
+ value: query,
26886
+ onChange: (event) => {
26887
+ setQuery(event.currentTarget.value);
26888
+ },
26889
+ onKeyDown: (event) => {
26890
+ if (event.key === "Escape") {
26891
+ event.preventDefault();
26892
+ setOpen(false);
26893
+ }
26894
+ }
26895
+ }
26896
+ ) }),
26897
+ /* @__PURE__ */ jsx2("div", { className: "ghsync__picker-list", role: "listbox", "aria-labelledby": id, "aria-multiselectable": "true", children: filteredOptions.length > 0 ? filteredOptions.map((option) => {
26898
+ const selected = selectedValueSet.has(option.value);
26899
+ return /* @__PURE__ */ jsxs2(
26900
+ "button",
26901
+ {
26902
+ type: "button",
26903
+ role: "option",
26904
+ "aria-selected": selected,
26905
+ className: `ghsync__picker-option${selected ? " ghsync__picker-option--selected" : ""}`,
26906
+ onClick: () => {
26907
+ const nextValues = selected ? selectedValues.filter((value) => value !== option.value) : normalizeAgentIds([...selectedValues, option.value]);
26908
+ onChange(nextValues);
26909
+ },
26910
+ children: [
26911
+ /* @__PURE__ */ jsxs2("span", { className: "ghsync__picker-trigger-main", children: [
26912
+ option.icon === "agent" ? /* @__PURE__ */ jsx2("span", { className: "ghsync__picker-agent-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(AgentIcon, {}) }) : null,
26913
+ /* @__PURE__ */ jsx2("span", { className: "ghsync__picker-option-label", children: option.label })
26914
+ ] }),
26915
+ /* @__PURE__ */ jsx2("span", { className: "ghsync__picker-option-check", "aria-hidden": "true", children: selected ? /* @__PURE__ */ jsx2(PickerCheckIcon, {}) : null })
26916
+ ]
26917
+ },
26918
+ option.value
26919
+ );
26920
+ }) : /* @__PURE__ */ jsx2("div", { className: "ghsync__picker-empty", children: "No agents match." }) })
26921
+ ] }) : null
26922
+ ] });
26923
+ }
26751
26924
  function SettingsStatusPicker(props) {
26752
26925
  const { id, value, options, disabled, onChange } = props;
26753
26926
  const [open, setOpen] = useState2(false);
@@ -27678,6 +27851,117 @@ async function patchPluginConfig(pluginId, patch5) {
27678
27851
  })
27679
27852
  });
27680
27853
  }
27854
+ var GITHUB_TOKEN_PROPAGATION_CONCURRENCY_LIMIT = 4;
27855
+ function normalizeAgentAdapterConfig(value) {
27856
+ return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
27857
+ }
27858
+ function normalizeAgentEnvBindings(value) {
27859
+ return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
27860
+ }
27861
+ function isMatchingSecretRefEnvBinding(value, secretId) {
27862
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
27863
+ return false;
27864
+ }
27865
+ const record = value;
27866
+ return record.type === "secret_ref" && record.secretId === secretId;
27867
+ }
27868
+ function getAgentPropagationPatch(params) {
27869
+ const adapterConfig = normalizeAgentAdapterConfig(params.adapterConfig);
27870
+ const currentEnv = normalizeAgentEnvBindings(adapterConfig.env);
27871
+ if (params.mode === "ensure") {
27872
+ const nextEnv2 = {
27873
+ ...currentEnv,
27874
+ GITHUB_TOKEN: {
27875
+ type: "secret_ref",
27876
+ secretId: params.githubTokenSecretRef
27877
+ }
27878
+ };
27879
+ if (JSON.stringify(nextEnv2) === JSON.stringify(currentEnv)) {
27880
+ return null;
27881
+ }
27882
+ return {
27883
+ ...adapterConfig,
27884
+ env: nextEnv2
27885
+ };
27886
+ }
27887
+ if (!isMatchingSecretRefEnvBinding(currentEnv.GITHUB_TOKEN, params.githubTokenSecretRef)) {
27888
+ return null;
27889
+ }
27890
+ const nextEnv = { ...currentEnv };
27891
+ delete nextEnv.GITHUB_TOKEN;
27892
+ const nextAdapterConfig = {
27893
+ ...adapterConfig
27894
+ };
27895
+ if (Object.keys(nextEnv).length > 0) {
27896
+ nextAdapterConfig.env = nextEnv;
27897
+ } else {
27898
+ delete nextAdapterConfig.env;
27899
+ }
27900
+ return nextAdapterConfig;
27901
+ }
27902
+ async function runWithConcurrencyLimit(items, concurrencyLimit, worker) {
27903
+ if (items.length === 0) {
27904
+ return;
27905
+ }
27906
+ let nextIndex = 0;
27907
+ const runnerCount = Math.min(concurrencyLimit, items.length);
27908
+ await Promise.all(
27909
+ Array.from({ length: runnerCount }, async () => {
27910
+ while (nextIndex < items.length) {
27911
+ const currentIndex = nextIndex;
27912
+ nextIndex += 1;
27913
+ await worker(items[currentIndex]);
27914
+ }
27915
+ })
27916
+ );
27917
+ }
27918
+ async function applyGitHubTokenPropagationUpdate(params) {
27919
+ const agent = await fetchJson(`/api/agents/${params.agentId}`);
27920
+ const nextAdapterConfig = getAgentPropagationPatch({
27921
+ adapterConfig: agent?.adapterConfig,
27922
+ githubTokenSecretRef: params.githubTokenSecretRef,
27923
+ mode: params.mode
27924
+ });
27925
+ if (!nextAdapterConfig) {
27926
+ return;
27927
+ }
27928
+ await fetchJson(`/api/agents/${params.agentId}`, {
27929
+ method: "PATCH",
27930
+ body: JSON.stringify({
27931
+ adapterConfig: nextAdapterConfig
27932
+ })
27933
+ });
27934
+ }
27935
+ async function syncGitHubTokenPropagationForAgents(params) {
27936
+ const selectedAgentIds = normalizeAgentIds(params.selectedAgentIds);
27937
+ const selectedAgentIdSet = new Set(selectedAgentIds);
27938
+ const previousAgentIds = normalizeAgentIds(params.previousAgentIds);
27939
+ const failures = /* @__PURE__ */ new Set();
27940
+ const operations = [
27941
+ ...selectedAgentIds.map((agentId) => ({ agentId, mode: "ensure" })),
27942
+ ...previousAgentIds.filter((agentId) => !selectedAgentIdSet.has(agentId)).map((agentId) => ({ agentId, mode: "remove" }))
27943
+ ];
27944
+ await runWithConcurrencyLimit(
27945
+ operations,
27946
+ GITHUB_TOKEN_PROPAGATION_CONCURRENCY_LIMIT,
27947
+ async (operation) => {
27948
+ try {
27949
+ await applyGitHubTokenPropagationUpdate({
27950
+ agentId: operation.agentId,
27951
+ githubTokenSecretRef: params.githubTokenSecretRef,
27952
+ mode: operation.mode
27953
+ });
27954
+ } catch {
27955
+ failures.add(operation.agentId);
27956
+ }
27957
+ }
27958
+ );
27959
+ if (failures.size > 0) {
27960
+ throw new Error(
27961
+ `GitHub token propagation could not update these agents: ${[...failures].join(", ")}.`
27962
+ );
27963
+ }
27964
+ }
27681
27965
  function normalizeCliAuthPollIntervalMs(value) {
27682
27966
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
27683
27967
  return CLI_AUTH_POLL_INTERVAL_FALLBACK_MS;
@@ -30778,6 +31062,11 @@ function GitHubSyncSettingsPage() {
30778
31062
  if (!settings.data) {
30779
31063
  return;
30780
31064
  }
31065
+ const tokenUiState = resolveSavedTokenUiState({
31066
+ githubTokenConfigured: settings.data.githubTokenConfigured,
31067
+ githubTokenLogin: settings.data.githubTokenLogin
31068
+ });
31069
+ const savedBoardAccessIdentity = typeof settings.data.paperclipBoardAccessIdentity === "string" && settings.data.paperclipBoardAccessIdentity.trim() ? settings.data.paperclipBoardAccessIdentity.trim() : null;
30781
31070
  const nextScheduleFrequencyMinutes = normalizeScheduleFrequencyMinutes(settings.data.scheduleFrequencyMinutes);
30782
31071
  setForm({
30783
31072
  mappings: settings.data.mappings ?? [],
@@ -30794,18 +31083,12 @@ function GitHubSyncSettingsPage() {
30794
31083
  setScheduleFrequencyDraft(String(nextScheduleFrequencyMinutes));
30795
31084
  setIgnoredAuthorsDraft(normalizeAdvancedSettings(settings.data.advancedSettings).ignoredIssueAuthorUsernames.join(", "));
30796
31085
  setTokenDraft("");
30797
- if (!settings.data.paperclipBoardAccessConfigured) {
30798
- setBoardAccessIdentity(null);
30799
- }
30800
- if (settings.data.githubTokenConfigured) {
30801
- setShowSavedTokenHint(true);
30802
- setShowTokenEditor(false);
30803
- setTokenStatusOverride("valid");
30804
- } else if (!showSavedTokenHint) {
30805
- setShowTokenEditor(true);
30806
- setValidatedLogin(null);
30807
- }
30808
- }, [settings.data, showSavedTokenHint]);
31086
+ setValidatedLogin(tokenUiState.validatedLogin);
31087
+ setBoardAccessIdentity(settings.data.paperclipBoardAccessConfigured ? savedBoardAccessIdentity : null);
31088
+ setShowSavedTokenHint(tokenUiState.showSavedTokenHint);
31089
+ setShowTokenEditor(tokenUiState.showTokenEditor);
31090
+ setTokenStatusOverride(tokenUiState.tokenStatusOverride);
31091
+ }, [settings.data]);
30809
31092
  useEffect2(() => {
30810
31093
  const companyId = hostContext.companyId;
30811
31094
  if (!companyId || tokenStatusOverride === "invalid") {
@@ -31028,6 +31311,10 @@ function GitHubSyncSettingsPage() {
31028
31311
  (currentSettings?.availableAssignees?.length ? currentSettings.availableAssignees : null) ?? (form.availableAssignees?.length ? form.availableAssignees : null) ?? browserAvailableAssignees,
31029
31312
  form.advancedSettings.defaultAssigneeAgentId
31030
31313
  );
31314
+ const propagationAgents = getAvailablePropagationAgentOptions(
31315
+ (currentSettings?.availableAssignees?.length ? currentSettings.availableAssignees : null) ?? (form.availableAssignees?.length ? form.availableAssignees : null) ?? browserAvailableAssignees,
31316
+ form.advancedSettings.githubTokenPropagationAgentIds
31317
+ );
31031
31318
  const savedMappingsSource = currentSettings ? currentSettings.mappings ?? [] : form.mappings;
31032
31319
  const savedMappings = getComparableMappings(savedMappingsSource);
31033
31320
  const draftMappings = getComparableMappings(form.mappings);
@@ -31069,7 +31356,7 @@ function GitHubSyncSettingsPage() {
31069
31356
  const canConnectBoardAccess = hasCompanyContext && !settingsMutationsLocked && !connectingBoardAccess && !showInitialLoadingState;
31070
31357
  const boardAccessStatusLabel = !hasCompanyContext ? "Unavailable" : boardAccessBannerLabel;
31071
31358
  const boardAccessStatusTone = !hasCompanyContext ? boardAccessRequired ? "warning" : "neutral" : boardAccessTone;
31072
- const boardAccessSummaryText = !hasCompanyContext ? boardAccessRequired ? "Select a company." : "Select a company." : connectingBoardAccess ? "Approval in progress." : boardAccessConfigured ? "Connected." : boardAccessRequired ? "Required for sync." : boardAccessRequirement.status === "loading" ? "Checking requirement." : "Optional.";
31359
+ const boardAccessSummaryText = !hasCompanyContext ? boardAccessRequired ? "Select a company." : "Select a company." : connectingBoardAccess ? "Approval in progress." : boardAccessConfigured ? boardAccessIdentity ? `Connected as ${boardAccessIdentity}.` : "Connected." : boardAccessRequired ? "Required for sync." : boardAccessRequirement.status === "loading" ? "Checking requirement." : "Optional.";
31073
31360
  const showTokenForm = tokenStatus !== "valid" || showTokenEditor;
31074
31361
  const lastUpdated = formatDate(form.updatedAt ?? currentSettings?.updatedAt, "Not saved yet");
31075
31362
  const lastSync = formatDate(displaySyncState.checkedAt, "Never");
@@ -31095,7 +31382,9 @@ function GitHubSyncSettingsPage() {
31095
31382
  const manualSyncScopePillClass = hasCompanyContext ? "ghsync__scope-pill ghsync__scope-pill--company" : "ghsync__scope-pill ghsync__scope-pill--mixed";
31096
31383
  const manualSyncScopePillLabel = hasCompanyContext ? "This company" : "All companies";
31097
31384
  const manualSyncButtonLabel = hasCompanyContext ? "Run sync for this company" : "Run sync across all companies";
31098
- const advancedSettingsSummary = formatAdvancedSettingsSummary(form.advancedSettings, availableAssignees);
31385
+ const advancedSettingsSummary = formatAdvancedSettingsSummary(form.advancedSettings, availableAssignees, {
31386
+ includePropagation: boardAccessRequired
31387
+ });
31099
31388
  const assigneeSelectOptions = [
31100
31389
  { value: "", label: "Unassigned" },
31101
31390
  ...availableAssignees.map((option) => ({
@@ -31104,6 +31393,11 @@ function GitHubSyncSettingsPage() {
31104
31393
  icon: "agent"
31105
31394
  }))
31106
31395
  ];
31396
+ const propagationAgentOptions = propagationAgents.map((option) => ({
31397
+ value: option.id,
31398
+ label: formatAssigneeOptionLabel(option),
31399
+ icon: "agent"
31400
+ }));
31107
31401
  const statusSelectOptions = PAPERCLIP_STATUS_OPTIONS.map((option) => ({
31108
31402
  value: option.value,
31109
31403
  label: option.label,
@@ -31234,6 +31528,33 @@ function GitHubSyncSettingsPage() {
31234
31528
  };
31235
31529
  });
31236
31530
  }
31531
+ async function propagateGitHubTokenToSelectedAgents(options) {
31532
+ if (!boardAccessRequired) {
31533
+ return;
31534
+ }
31535
+ const selectedAgentIds = normalizeAgentIds(options.selectedAgentIds);
31536
+ const previousAgentIds = normalizeAgentIds(options.previousAgentIds);
31537
+ if (selectedAgentIds.length === 0 && previousAgentIds.length === 0) {
31538
+ return;
31539
+ }
31540
+ let githubTokenSecretRef = typeof options.githubTokenSecretRef === "string" && options.githubTokenSecretRef.trim() ? options.githubTokenSecretRef.trim() : void 0;
31541
+ if (!githubTokenSecretRef) {
31542
+ const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
31543
+ if (!pluginId) {
31544
+ throw new Error("Plugin id is required to propagate the GitHub token to selected agents.");
31545
+ }
31546
+ const currentConfigResponse = await fetchJson(`/api/plugins/${pluginId}/config`);
31547
+ githubTokenSecretRef = normalizePluginConfig(currentConfigResponse?.configJson).githubTokenRef;
31548
+ }
31549
+ if (!githubTokenSecretRef) {
31550
+ throw new Error("GitHub token propagation requires a GitHub token saved through this settings page.");
31551
+ }
31552
+ await syncGitHubTokenPropagationForAgents({
31553
+ githubTokenSecretRef,
31554
+ selectedAgentIds,
31555
+ previousAgentIds
31556
+ });
31557
+ }
31237
31558
  async function handleSaveToken(event) {
31238
31559
  event.preventDefault();
31239
31560
  setSubmittingToken(true);
@@ -31277,8 +31598,20 @@ function GitHubSyncSettingsPage() {
31277
31598
  });
31278
31599
  await saveRegistration({
31279
31600
  companyId,
31280
- githubTokenRef: secret.id
31601
+ githubTokenRef: secret.id,
31602
+ githubTokenLogin: validation.login
31281
31603
  });
31604
+ const selectedAgentIds = normalizeAgentIds(currentSettings?.advancedSettings?.githubTokenPropagationAgentIds);
31605
+ let propagationError = null;
31606
+ try {
31607
+ await propagateGitHubTokenToSelectedAgents({
31608
+ selectedAgentIds,
31609
+ previousAgentIds: selectedAgentIds,
31610
+ githubTokenSecretRef: secret.id
31611
+ });
31612
+ } catch (error) {
31613
+ propagationError = error;
31614
+ }
31282
31615
  setForm((current) => ({
31283
31616
  ...current,
31284
31617
  githubTokenConfigured: true
@@ -31293,6 +31626,16 @@ function GitHubSyncSettingsPage() {
31293
31626
  body: "Token saved.",
31294
31627
  tone: "success"
31295
31628
  });
31629
+ if (propagationError) {
31630
+ toast({
31631
+ title: "GitHub token saved, but agent propagation needs attention",
31632
+ body: getActionErrorMessage(
31633
+ propagationError,
31634
+ "GitHub Sync could not update the selected agents with the saved token."
31635
+ ),
31636
+ tone: "error"
31637
+ });
31638
+ }
31296
31639
  notifyGitHubSyncSettingsChanged();
31297
31640
  try {
31298
31641
  await settings.refresh();
@@ -31348,7 +31691,8 @@ function GitHubSyncSettingsPage() {
31348
31691
  });
31349
31692
  await updateBoardAccess({
31350
31693
  companyId,
31351
- paperclipBoardApiTokenRef: secret.id
31694
+ paperclipBoardApiTokenRef: secret.id,
31695
+ paperclipBoardAccessIdentity: identity ?? ""
31352
31696
  });
31353
31697
  setBoardAccessIdentity(identity);
31354
31698
  setForm((current) => ({
@@ -31428,6 +31772,15 @@ function GitHubSyncSettingsPage() {
31428
31772
  scheduleFrequencyMinutes,
31429
31773
  ...trustedPaperclipApiBaseUrl ? { paperclipApiBaseUrl: trustedPaperclipApiBaseUrl } : {}
31430
31774
  });
31775
+ let propagationError = null;
31776
+ try {
31777
+ await propagateGitHubTokenToSelectedAgents({
31778
+ selectedAgentIds: normalizeAgentIds(normalizeAdvancedSettings(result.advancedSettings).githubTokenPropagationAgentIds),
31779
+ previousAgentIds: normalizeAgentIds(currentSettings?.advancedSettings?.githubTokenPropagationAgentIds)
31780
+ });
31781
+ } catch (error) {
31782
+ propagationError = error;
31783
+ }
31431
31784
  setForm((current) => ({
31432
31785
  ...current,
31433
31786
  mappings: result.mappings.length > 0 ? result.mappings : [createEmptyMapping(0)],
@@ -31444,6 +31797,16 @@ function GitHubSyncSettingsPage() {
31444
31797
  body: `Advanced defaults, mappings, and automatic sync are saved for ${currentCompanyName}.`,
31445
31798
  tone: "success"
31446
31799
  });
31800
+ if (propagationError) {
31801
+ toast({
31802
+ title: "Settings saved, but agent propagation needs attention",
31803
+ body: getActionErrorMessage(
31804
+ propagationError,
31805
+ "GitHub Sync could not apply the selected agent token propagation updates."
31806
+ ),
31807
+ tone: "error"
31808
+ });
31809
+ }
31447
31810
  notifyGitHubSyncSettingsChanged();
31448
31811
  try {
31449
31812
  await settings.refresh();
@@ -31685,7 +32048,7 @@ function GitHubSyncSettingsPage() {
31685
32048
  /* @__PURE__ */ jsx2("div", { className: "ghsync__permission-audit-list", children: /* @__PURE__ */ jsx2("div", { className: "ghsync__permission-audit-item", children: /* @__PURE__ */ jsx2("span", { children: tokenPermissionAuditData?.warnings[0] ?? "Add a mapped repository in this company so GitHub Sync can verify the token permissions it needs." }) }) })
31686
32049
  ] }) : null
31687
32050
  ] }),
31688
- /* @__PURE__ */ jsxs2("section", { className: "ghsync__section", children: [
32051
+ boardAccessRequired ? /* @__PURE__ */ jsxs2("section", { className: "ghsync__section", children: [
31689
32052
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__section-head", children: [
31690
32053
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__section-copy", children: [
31691
32054
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__section-title-row", children: [
@@ -31727,7 +32090,7 @@ function GitHubSyncSettingsPage() {
31727
32090
  ] }),
31728
32091
  /* @__PURE__ */ jsx2("span", { className: "ghsync__badge ghsync__badge--neutral", children: "Unavailable" })
31729
32092
  ] })
31730
- ] }),
32093
+ ] }) : null,
31731
32094
  /* @__PURE__ */ jsxs2("section", { className: "ghsync__section", children: [
31732
32095
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__section-head", children: [
31733
32096
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__section-copy", children: [
@@ -31954,7 +32317,29 @@ function GitHubSyncSettingsPage() {
31954
32317
  }
31955
32318
  ),
31956
32319
  /* @__PURE__ */ jsx2("p", { className: "ghsync__hint", children: "Comma or newline separated." })
31957
- ] })
32320
+ ] }),
32321
+ boardAccessRequired ? /* @__PURE__ */ jsxs2("div", { className: "ghsync__field", children: [
32322
+ /* @__PURE__ */ jsx2("label", { htmlFor: "advanced-token-propagation", children: "Propagate GitHub token to agents" }),
32323
+ /* @__PURE__ */ jsx2(
32324
+ SettingsAgentMultiPicker,
32325
+ {
32326
+ id: "advanced-token-propagation",
32327
+ values: form.advancedSettings.githubTokenPropagationAgentIds ?? [],
32328
+ options: propagationAgentOptions,
32329
+ disabled: settingsMutationsLocked || tokenStatus !== "valid",
32330
+ onChange: (nextValues) => {
32331
+ setForm((current) => ({
32332
+ ...current,
32333
+ advancedSettings: {
32334
+ ...current.advancedSettings,
32335
+ ...nextValues.length > 0 ? { githubTokenPropagationAgentIds: nextValues } : { githubTokenPropagationAgentIds: void 0 }
32336
+ }
32337
+ }));
32338
+ }
32339
+ }
32340
+ ),
32341
+ /* @__PURE__ */ jsx2("p", { className: "ghsync__hint", children: tokenStatus === "valid" ? "Selected agents receive `GITHUB_TOKEN` from the saved GitHub secret when you save settings." : "Save a valid GitHub token before choosing agents to propagate it to." })
32342
+ ] }) : null
31958
32343
  ] }) : null
31959
32344
  ] }),
31960
32345
  /* @__PURE__ */ jsxs2("section", { className: "ghsync__section", children: [
@@ -32107,13 +32492,13 @@ function GitHubSyncSettingsPage() {
32107
32492
  ] }),
32108
32493
  /* @__PURE__ */ jsx2("span", { children: !repositoriesUnlocked ? "Requires a token." : savedMappingCount > 0 ? hasCompanyContext ? `${savedMappingCount} saved.` : `${savedMappingCount} saved.` : hasCompanyContext ? "Add a repository." : "Select a company." })
32109
32494
  ] }),
32110
- /* @__PURE__ */ jsxs2("div", { className: "ghsync__check", children: [
32495
+ boardAccessRequired ? /* @__PURE__ */ jsxs2("div", { className: "ghsync__check", children: [
32111
32496
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__check-top", children: [
32112
32497
  /* @__PURE__ */ jsx2("strong", { children: "Paperclip board access" }),
32113
32498
  /* @__PURE__ */ jsx2("span", { className: `ghsync__badge ${getToneClass(boardAccessStatusTone)}`, children: boardAccessStatusLabel })
32114
32499
  ] }),
32115
32500
  /* @__PURE__ */ jsx2("span", { children: boardAccessSummaryText })
32116
- ] }),
32501
+ ] }) : null,
32117
32502
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__check", children: [
32118
32503
  /* @__PURE__ */ jsxs2("div", { className: "ghsync__check-top", children: [
32119
32504
  /* @__PURE__ */ jsx2("strong", { children: "Sync" }),
@@ -33123,6 +33508,8 @@ export {
33123
33508
  GitHubSyncProjectPullRequestsSidebarItem,
33124
33509
  GitHubSyncSettingsPage,
33125
33510
  index_default as default,
33126
- resolveOrCreateProject
33511
+ resolveOrCreateProject,
33512
+ resolveSavedTokenUiState,
33513
+ syncGitHubTokenPropagationForAgents
33127
33514
  };
33128
33515
  //# sourceMappingURL=index.js.map