@straiffi/archon 1.0.11 → 1.0.13

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.
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Archon</title>
8
- <script type="module" crossorigin src="/assets/index-Dgewr5ST.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-D3G9pPr9.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-BhQzJS4j.css">
10
10
  </head>
11
11
  <body>
@@ -866,14 +866,28 @@ const getBundleReviewState = (projectId, tickets) => {
866
866
  can_re_review: reviewState.can_re_review,
867
867
  };
868
868
  };
869
- const BUNDLE_PULL_REQUEST_SYNC_TTL_MS = 15 * 1000;
869
+ const BUNDLE_PULL_REQUEST_SYNC_TTL_MS = 60 * 1000;
870
870
  const BUNDLE_PULL_REQUEST_DISCOVERY_MISS_COOLDOWN_MS = 10 * 60 * 1000;
871
871
  const BUNDLE_PULL_REQUEST_DISCOVERY_ERROR_COOLDOWN_MS = 2 * 60 * 1000;
872
+ const BUNDLE_PULL_REQUEST_SYNC_ERROR_COOLDOWN_MS = 2 * 60 * 1000;
873
+ const MAX_BACKGROUND_PULL_REQUEST_SYNCS_PER_PROJECT_REFRESH = 2;
872
874
  // Avoid repeated open-triggered discovery hits when a bundle has no PR yet or GitHub is temporarily unavailable.
873
875
  const bundlePullRequestDiscoveryInFlight = new Map();
874
876
  const bundlePullRequestDiscoveryMissTimestamps = new Map();
875
877
  const bundlePullRequestDiscoveryErrorTimestamps = new Map();
878
+ const bundlePullRequestSyncInFlight = new Map();
879
+ const bundlePullRequestSyncErrorTimestamps = new Map();
876
880
  const getBundlePullRequestDiscoveryKey = (projectId, bundleId) => `${projectId}:${bundleId}`;
881
+ const startPassiveGitHubWork = (label, callback) => {
882
+ setImmediate(() => {
883
+ try {
884
+ callback();
885
+ }
886
+ catch (error) {
887
+ console.warn(`[integrations] github passive work failed ${label}`, error);
888
+ }
889
+ });
890
+ };
877
891
  const isBundlePullRequestDiscoveryCoolingDown = (timestamps, key, cooldownMs) => {
878
892
  const lastAttemptAt = timestamps.get(key);
879
893
  if (lastAttemptAt === undefined) {
@@ -985,6 +999,7 @@ const discoverOpenBundlePullRequest = async (project, bundle) => {
985
999
  };
986
1000
  const syncTrackedBundlePullRequestIfNeeded = async (project, bundle, options = {}) => {
987
1001
  const force = options.force ?? false;
1002
+ const rethrow = options.rethrow ?? false;
988
1003
  const githubConfig = getGitHubConnectionConfig(project.id);
989
1004
  const existingRecord = getBundlePullRequestRecord(bundle.id, project.id);
990
1005
  if (!githubConfig || !existingRecord) {
@@ -1000,9 +1015,49 @@ const syncTrackedBundlePullRequestIfNeeded = async (project, bundle, options = {
1000
1015
  }
1001
1016
  catch (error) {
1002
1017
  console.warn(`[integrations] github pr sync failed project=${project.id} bundle=${bundle.id} pr=${existingRecord.number}`, error);
1018
+ if (rethrow) {
1019
+ throw error;
1020
+ }
1003
1021
  return existingRecord;
1004
1022
  }
1005
1023
  };
1024
+ const shouldStartTrackedBundlePullRequestSync = (project, bundle, options = {}) => {
1025
+ const force = options.force ?? false;
1026
+ const existingRecord = getBundlePullRequestRecord(bundle.id, project.id);
1027
+ if (!getGitHubConnectionConfig(project.id) || !existingRecord) {
1028
+ return false;
1029
+ }
1030
+ if (!force && !shouldSyncBundlePullRequest(existingRecord, BUNDLE_PULL_REQUEST_SYNC_TTL_MS)) {
1031
+ return false;
1032
+ }
1033
+ const key = getBundlePullRequestDiscoveryKey(project.id, bundle.id);
1034
+ if (bundlePullRequestSyncInFlight.has(key)) {
1035
+ return false;
1036
+ }
1037
+ return force || !isBundlePullRequestDiscoveryCoolingDown(bundlePullRequestSyncErrorTimestamps, key, BUNDLE_PULL_REQUEST_SYNC_ERROR_COOLDOWN_MS);
1038
+ };
1039
+ const startTrackedBundlePullRequestSyncIfNeeded = (project, bundle, options = {}) => {
1040
+ if (!shouldStartTrackedBundlePullRequestSync(project, bundle, options)) {
1041
+ return false;
1042
+ }
1043
+ const key = getBundlePullRequestDiscoveryKey(project.id, bundle.id);
1044
+ const task = (async () => {
1045
+ try {
1046
+ await syncTrackedBundlePullRequestIfNeeded(project, bundle, { ...options, rethrow: true });
1047
+ bundlePullRequestSyncErrorTimestamps.delete(key);
1048
+ reconcileBundleTicketsAfterPullRequestSync(project.id, bundle.id);
1049
+ }
1050
+ catch (error) {
1051
+ bundlePullRequestSyncErrorTimestamps.set(key, Date.now());
1052
+ console.warn(`[integrations] github pr background sync failed project=${project.id} bundle=${bundle.id}`, error);
1053
+ }
1054
+ finally {
1055
+ bundlePullRequestSyncInFlight.delete(key);
1056
+ }
1057
+ })();
1058
+ bundlePullRequestSyncInFlight.set(key, task);
1059
+ return true;
1060
+ };
1006
1061
  const listBundleTicketIds = (projectId, bundleId) => {
1007
1062
  const rows = db.prepare('SELECT id FROM tickets WHERE project_id = ? AND worktree_bundle_id = ? ORDER BY updated_at DESC, id DESC').all(projectId, bundleId);
1008
1063
  return rows.map(row => row.id);
@@ -1060,11 +1115,15 @@ const reconcileBundleTicketsAfterPullRequestSync = (projectId, bundleId) => {
1060
1115
  }
1061
1116
  };
1062
1117
  const syncProjectBundlePullRequestsIfNeeded = async (project, bundles, options = {}) => {
1063
- const force = options.force ?? false;
1064
- await Promise.all(bundles.map(async (bundle) => {
1065
- await syncTrackedBundlePullRequestIfNeeded(project, bundle, { force });
1066
- reconcileBundleTicketsAfterPullRequestSync(project.id, bundle.id);
1067
- }));
1118
+ let startedCount = 0;
1119
+ for (const bundle of bundles) {
1120
+ if (startedCount >= MAX_BACKGROUND_PULL_REQUEST_SYNCS_PER_PROJECT_REFRESH) {
1121
+ return;
1122
+ }
1123
+ if (startTrackedBundlePullRequestSyncIfNeeded(project, bundle, options)) {
1124
+ startedCount += 1;
1125
+ }
1126
+ }
1068
1127
  };
1069
1128
  const startProjectBundlePullRequestSyncIfNeeded = (project, bundles, options = {}) => {
1070
1129
  void syncProjectBundlePullRequestsIfNeeded(project, bundles, options).catch(error => {
@@ -2053,17 +2112,23 @@ app.get('/tickets/:id', async (req, res) => {
2053
2112
  if (!matchesTicketProjectContext(ticket, req)) {
2054
2113
  return res.status(404).json({ error: 'Not found' });
2055
2114
  }
2115
+ let passivePullRequestContext = null;
2056
2116
  if (ticket.project_id && ticket.worktree_bundle_id) {
2057
2117
  const project = getProjectById(ticket.project_id);
2058
2118
  const bundle = project ? getBundle(ticket.worktree_bundle_id, project.id) : null;
2059
2119
  if (project && bundle) {
2060
- await syncTrackedBundlePullRequestIfNeeded(project, bundle);
2061
- reconcileBundleTicketsAfterPullRequestSync(project.id, bundle.id);
2062
- startBundlePullRequestDiscoveryIfNeeded(project, bundle);
2063
- ticket = getTicket(req.params.id);
2120
+ passivePullRequestContext = { project, bundle };
2064
2121
  }
2065
2122
  }
2066
- return res.json(ticket);
2123
+ res.json(ticket);
2124
+ if (passivePullRequestContext) {
2125
+ const { project, bundle } = passivePullRequestContext;
2126
+ startPassiveGitHubWork('ticket-open-pr-sync', () => {
2127
+ startTrackedBundlePullRequestSyncIfNeeded(project, bundle);
2128
+ startBundlePullRequestDiscoveryIfNeeded(project, bundle);
2129
+ });
2130
+ }
2131
+ return;
2067
2132
  });
2068
2133
  app.get('/tickets/:id/review-findings', (req, res) => {
2069
2134
  const ticket = getTicketRouteContext(req.params.id);
@@ -2139,12 +2204,13 @@ app.get('/bundles', async (req, res) => {
2139
2204
  timing.mark('ensure_project_root_bundle');
2140
2205
  const bundles = listBundles(result.project.id);
2141
2206
  timing.mark('list_bundles');
2142
- startProjectBundlePullRequestSyncIfNeeded(result.project, bundles);
2143
- timing.mark('start_pr_sync');
2144
2207
  const serializedBundles = bundles.map(bundle => serializeBundleRow(result.project.id, bundle));
2145
2208
  timing.mark('serialize_bundles');
2146
2209
  timing.end({ count: serializedBundles.length });
2147
2210
  res.json(serializedBundles);
2211
+ startPassiveGitHubWork('project-bundle-pr-sync', () => {
2212
+ startProjectBundlePullRequestSyncIfNeeded(result.project, bundles);
2213
+ });
2148
2214
  });
2149
2215
  app.get('/bundles/:id/details', async (req, res) => {
2150
2216
  const result = getRequiredProject(req);
@@ -2168,9 +2234,11 @@ app.get('/bundles/:id/conversation', (req, res) => {
2168
2234
  if (!bundle) {
2169
2235
  return res.status(404).json({ error: 'Bundle not found' });
2170
2236
  }
2171
- startBundlePullRequestDiscoveryIfNeeded(result.project, bundle);
2172
2237
  const payload = serializeBundleConversationDetails(result.project, bundle);
2173
2238
  res.json(payload);
2239
+ startPassiveGitHubWork('bundle-conversation-pr-discovery', () => {
2240
+ startBundlePullRequestDiscoveryIfNeeded(result.project, bundle);
2241
+ });
2174
2242
  });
2175
2243
  app.get('/bundles/:id/review-findings', (req, res) => {
2176
2244
  const result = getRequiredProject(req);