@mastra/github-signals 0.2.4 → 0.2.5-alpha.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @mastra/github-signals
2
2
 
3
+ ## 0.2.5-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed a shut-down provider continuing to poll GitHub. Stopping the provider now clears its per-thread polling timers, so a provider that is replaced or disabled stops calling GitHub and stops sending notifications. ([#20848](https://github.com/mastra-ai/mastra/pull/20848))
8
+
9
+ - Fixed deferred notification deliveries failing with "No model selected" when the woken thread had no request context. The notification dispatch workflow re-sends deferred and summarized notifications long after the originating send, so any stream options attached to the original signal are gone; waking an idle thread then had no way to resolve a model. The notification delivery policy now drives the fix: `NotificationDeliveryDecision` accepts `streamOptions`, and the dispatcher re-runs the agent's delivery policy at delivery time (via the new `agent.resolveNotificationDeliveryDecision()`) to attach freshly resolved stream options to both individual and summary deliveries. Only `streamOptions` is honored at dispatch time; the record's persisted schedule still governs when and how it is delivered. Receipt-time sends also honor the policy's `streamOptions` now: an immediate deliver or summarize-now wake attaches them too, with caller-supplied stream options taking precedence. Mastra Code wires its session-based stream options resolver through the Code Agent's `deliveryPolicy.decide`, layered on the default decision logic. This fix applies to threads whose session is live in the current process; deliveries with no resolvable session fall back to a bare wake, and the "No model selected" error now distinguishes the case where a run started without any controller session context. This re-lands the capability removed by the #18637 revert in the policy-driven shape that revert called for, and the `@mastra/github-signals` bump only widens its `getNotificationStreamOptions` callback return type to allow `undefined`. ([#21113](https://github.com/mastra-ai/mastra/pull/21113))
10
+
11
+ - Updated dependencies [[`b8ce7ec`](https://github.com/mastra-ai/mastra/commit/b8ce7ec96e39343c6c2f36d12d68a9ad816c09f7), [`a3a3624`](https://github.com/mastra-ai/mastra/commit/a3a3624f646b98e409424d8defccbd334da9e8b8), [`6246914`](https://github.com/mastra-ai/mastra/commit/62469146636911f3cbbe0880bd011c6a897a59a7), [`3f73c07`](https://github.com/mastra-ai/mastra/commit/3f73c076727e8c36b4fff7a1b40290fb68957fa8), [`7c1ebb1`](https://github.com/mastra-ai/mastra/commit/7c1ebb15690c4b3f0eabb19077cf8af573311e57), [`32980a3`](https://github.com/mastra-ai/mastra/commit/32980a3e2413d0274ac244d32c37d910edc13f00), [`4bcdfaf`](https://github.com/mastra-ai/mastra/commit/4bcdfaf0eac3199d7cb171b0a19a92c9c341eea4), [`af4636a`](https://github.com/mastra-ai/mastra/commit/af4636a74463275d71c1d13a38f7d2b738f128bf), [`a463cdf`](https://github.com/mastra-ai/mastra/commit/a463cdf1c95c3059e70f0bff27959e8558bb899d), [`0ea6b80`](https://github.com/mastra-ai/mastra/commit/0ea6b8001408ce02b56e8be0536b0fd8cbaf8ad2)]:
12
+ - @mastra/core@1.58.0-alpha.11
13
+
14
+ ## 0.2.5-alpha.0
15
+
16
+ ### Patch Changes
17
+
18
+ - Fixed GitHub polling so stopping a poll prevents in-flight work from sending notifications or writing subscription state. ([#20593](https://github.com/mastra-ai/mastra/pull/20593))
19
+
20
+ - Updated dependencies [[`cdd5c33`](https://github.com/mastra-ai/mastra/commit/cdd5c33ac6c7118a9f139e6dc0e14e6a8ae31658), [`d7cf7fa`](https://github.com/mastra-ai/mastra/commit/d7cf7fafc1ae1b50bd8462dd0e6c671a8606db93), [`0f9a448`](https://github.com/mastra-ai/mastra/commit/0f9a448502157e59f7b76f24360ad497168f5ef8), [`289f4ce`](https://github.com/mastra-ai/mastra/commit/289f4ce16e3293370440172132c52ee787cbc09f), [`4f16ff8`](https://github.com/mastra-ai/mastra/commit/4f16ff824bf2f9b0ddc93f210477c10c8a4fb1ab), [`1c67d85`](https://github.com/mastra-ai/mastra/commit/1c67d85e9da8285662f4dbbf47e0378c3fee0747), [`ba24be6`](https://github.com/mastra-ai/mastra/commit/ba24be662439c331ab23a600041f93803c89eca8), [`842b5fe`](https://github.com/mastra-ai/mastra/commit/842b5fe22b6a7fa811bd14e48eb9af523ac989f2), [`80bdf3a`](https://github.com/mastra-ai/mastra/commit/80bdf3ae16ade6ff63bde0cb16fa2df8ab7dd4dd), [`9ba1247`](https://github.com/mastra-ai/mastra/commit/9ba12470c77f1c03642d720ce67e517e878f666e), [`fd96298`](https://github.com/mastra-ai/mastra/commit/fd96298a8367622f4ebfcaa97b5b6c1fbbd14564), [`6a84954`](https://github.com/mastra-ai/mastra/commit/6a84954a2667f85b6d59da652dab1bbff007ccb0), [`52d8ef0`](https://github.com/mastra-ai/mastra/commit/52d8ef03801f1deb7ee48532fc4190dd4a33916c), [`cdd5c33`](https://github.com/mastra-ai/mastra/commit/cdd5c33ac6c7118a9f139e6dc0e14e6a8ae31658), [`efd5c81`](https://github.com/mastra-ai/mastra/commit/efd5c81cc25fde3c2ddd86fc1178deb4ec176e19), [`0976933`](https://github.com/mastra-ai/mastra/commit/0976933142333ec78451feef265b68bcb45aa5e7), [`242b945`](https://github.com/mastra-ai/mastra/commit/242b94558777bfbdeb42cbfea84afff0b6ad0633), [`fea5cae`](https://github.com/mastra-ai/mastra/commit/fea5caedc7e2cfea51784a15e015952692027abf), [`4b59f78`](https://github.com/mastra-ai/mastra/commit/4b59f786cbc9a7d1ef07a07517dbd4b96865e99d), [`7010c5d`](https://github.com/mastra-ai/mastra/commit/7010c5d15728bf9c5dfe4fb6b1bf80ce23bf143a)]:
21
+ - @mastra/core@1.58.0-alpha.3
22
+
3
23
  ## 0.2.4
4
24
 
5
25
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -740,6 +740,8 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
740
740
  #syncClient;
741
741
  #repositoryResolver;
742
742
  #polling = /* @__PURE__ */ new Map();
743
+ #pollingThreadGenerations = /* @__PURE__ */ new Map();
744
+ #pollingGeneration = 0;
743
745
  #permissionCache = /* @__PURE__ */ new Map();
744
746
  #agent;
745
747
  #agentOptions = {};
@@ -813,6 +815,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
813
815
  const key = this.#pollingKey(input);
814
816
  for (const [pollingKey, state] of this.#polling.entries()) {
815
817
  if (pollingKey === key) continue;
818
+ this.#invalidatePollingThread(pollingKey);
816
819
  clearInterval(state.timer);
817
820
  this.#polling.delete(pollingKey);
818
821
  }
@@ -836,6 +839,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
836
839
  }
837
840
  stopPollingForThread(input) {
838
841
  const key = this.#pollingKey(input);
842
+ this.#invalidatePollingThread(key);
839
843
  const state = this.#polling.get(key);
840
844
  if (!state) return;
841
845
  clearInterval(state.timer);
@@ -851,9 +855,21 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
851
855
  return this.#options.pollIntervalMs ?? 3e5;
852
856
  }
853
857
  stopAllPolling() {
858
+ this.#pollingGeneration++;
859
+ this.#pollingThreadGenerations.clear();
854
860
  for (const state of this.#polling.values()) clearInterval(state.timer);
855
861
  this.#polling.clear();
856
862
  }
863
+ /**
864
+ * Shutdown hook. Per-thread polling lives in this provider's own timer map, not in the base
865
+ * class's single timer, so the inherited `stop()` would leave those intervals running: a
866
+ * provider that has been shut down would keep polling GitHub and keep notifying through the
867
+ * agent it was still connected to.
868
+ */
869
+ stop() {
870
+ super.stop();
871
+ this.stopAllPolling();
872
+ }
857
873
  async pollThreadNow(input) {
858
874
  return this.#pollThread(input, { includeComments: true });
859
875
  }
@@ -1044,6 +1060,9 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1044
1060
  #pollingKey(input) {
1045
1061
  return `${input.resourceId}:${input.threadId}`;
1046
1062
  }
1063
+ #invalidatePollingThread(key) {
1064
+ this.#pollingThreadGenerations.set(key, (this.#pollingThreadGenerations.get(key) ?? 0) + 1);
1065
+ }
1047
1066
  #getNotificationAgent(_input) {
1048
1067
  if (this.#agent) return this.#agent;
1049
1068
  const agentId = _input?.agentId ?? this.#options.agentId;
@@ -1063,6 +1082,9 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1063
1082
  const key = this.#pollingKey(input);
1064
1083
  const state = this.#polling.get(key);
1065
1084
  if (state?.running) return 0;
1085
+ const generation = this.#pollingGeneration;
1086
+ const threadGeneration = this.#pollingThreadGenerations.get(key) ?? 0;
1087
+ const isCurrentGeneration = () => this.#pollingGeneration === generation && (this.#pollingThreadGenerations.get(key) ?? 0) === threadGeneration;
1066
1088
  if (state) state.running = true;
1067
1089
  this.#notifyPollingChanged({
1068
1090
  threadId: input.threadId,
@@ -1071,6 +1093,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1071
1093
  });
1072
1094
  try {
1073
1095
  const { threadStore, loadedThread } = await this.#loadThread(input);
1096
+ if (!isCurrentGeneration()) return 0;
1074
1097
  const githubMetadata = getGithubMetadata(loadedThread.metadata);
1075
1098
  if (githubMetadata.subscriptions.length === 0) {
1076
1099
  this.stopPollingForThread(input);
@@ -1087,6 +1110,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1087
1110
  includeComments: options.includeComments
1088
1111
  };
1089
1112
  const syncResult = await this.#syncClient.syncPullRequest(syncInput);
1113
+ if (!isCurrentGeneration()) return 0;
1090
1114
  let snapshot;
1091
1115
  let snapshotError;
1092
1116
  if (syncResult.ok) try {
@@ -1094,7 +1118,9 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1094
1118
  } catch (error) {
1095
1119
  snapshotError = error instanceof Error ? error.message : String(error);
1096
1120
  }
1097
- if (snapshot) snapshot = await this.#filterUnauthorizedLatestComment(subscription.owner, subscription.repo, snapshot);
1121
+ if (!isCurrentGeneration()) return 0;
1122
+ if (snapshot) snapshot = await this.#filterUnauthorizedLatestComment(subscription.owner, subscription.repo, snapshot, isCurrentGeneration);
1123
+ if (!isCurrentGeneration()) return 0;
1098
1124
  const nextSubscription = {
1099
1125
  ...subscription,
1100
1126
  updatedAt: now,
@@ -1117,15 +1143,17 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1117
1143
  const legacyAggregateChanged = previousContentHash && snapshot?.contentHash && previousContentHash !== snapshot.contentHash && !previousThreadContentHash && !previousHeadSha;
1118
1144
  const changed = isFirstObservation || syncResult.ok && snapshot && (legacyAggregateChanged || latestCommentChanged || previousThreadContentHash && snapshot.threadContentHash && previousThreadContentHash !== snapshot.threadContentHash && !existingBotCommentEdited || previousHeadSha && snapshot.headSha && previousHeadSha !== snapshot.headSha || subscription.lastObservedState && snapshot.state && subscription.lastObservedState !== snapshot.state || hasMeaningfulMergeableStateChange(subscription.lastObservedMergeableState, snapshot.mergeableState) || subscription.lastObservedCiState && snapshot.ciState && subscription.lastObservedCiState !== snapshot.ciState || subscription.lastObservedReviewStateHash && snapshot.reviewStateHash && subscription.lastObservedReviewStateHash !== snapshot.reviewStateHash);
1119
1145
  let shouldKeepSubscription = true;
1120
- if (changed && snapshot) {
1146
+ if (changed && snapshot && isCurrentGeneration()) {
1121
1147
  const notifications = await this.#sendActivityNotifications({
1122
1148
  polling: input,
1123
1149
  subscription,
1124
1150
  snapshot,
1125
1151
  previousGithubUpdatedAt,
1126
1152
  previousContentHash,
1127
- latestCommentChanged
1153
+ latestCommentChanged,
1154
+ isCurrentGeneration
1128
1155
  });
1156
+ if (!isCurrentGeneration()) return 0;
1129
1157
  const primaryNotification = notifications[0];
1130
1158
  if (primaryNotification) {
1131
1159
  nextSubscription.lastNotificationAt = now;
@@ -1137,6 +1165,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1137
1165
  }
1138
1166
  if (shouldKeepSubscription) subscriptions.push(nextSubscription);
1139
1167
  }
1168
+ if (!isCurrentGeneration()) return 0;
1140
1169
  await threadStore.saveThread({ thread: {
1141
1170
  ...loadedThread,
1142
1171
  id: input.threadId,
@@ -1145,6 +1174,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1145
1174
  updatedAt: /* @__PURE__ */ new Date(),
1146
1175
  metadata: setGithubMetadata(loadedThread.metadata, { subscriptions })
1147
1176
  } });
1177
+ if (!isCurrentGeneration()) return 0;
1148
1178
  this.#notifySubscriptionsChanged({
1149
1179
  threadId: input.threadId,
1150
1180
  resourceId: input.resourceId,
@@ -1152,16 +1182,16 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1152
1182
  });
1153
1183
  if (subscriptions.length === 0) this.stopPollingForThread(input);
1154
1184
  return subscriptions.length;
1155
- } catch (error) {
1156
- throw error;
1157
1185
  } finally {
1158
1186
  const latestState = this.#polling.get(key);
1159
- if (latestState) latestState.running = false;
1160
- this.#notifyPollingChanged({
1161
- threadId: input.threadId,
1162
- resourceId: input.resourceId,
1163
- running: false
1164
- });
1187
+ if (isCurrentGeneration()) {
1188
+ if (latestState) latestState.running = false;
1189
+ this.#notifyPollingChanged({
1190
+ threadId: input.threadId,
1191
+ resourceId: input.resourceId,
1192
+ running: false
1193
+ });
1194
+ }
1165
1195
  }
1166
1196
  }
1167
1197
  #createGithubNotificationInput(input) {
@@ -1253,18 +1283,19 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1253
1283
  dedupeSuffix: `baseline:${input.subscription.lastSubscribeSignalId}`
1254
1284
  });
1255
1285
  }
1256
- async #isAuthorizedAuthor(owner, repo, user, metadata = {}) {
1286
+ async #isAuthorizedAuthor(owner, repo, user, metadata = {}, isCurrentGeneration) {
1257
1287
  if (!user) return false;
1258
1288
  const normalizedUser = user.toLowerCase();
1259
1289
  if (metadata.isBot === true || metadata.authorType?.toLowerCase() === "bot" || normalizedUser.endsWith("[bot]")) {
1260
1290
  if ((this.#options.ignoredBots ?? []).some((bot) => bot.toLowerCase() === normalizedUser)) return false;
1261
1291
  return (this.#options.authorizedBots ?? DEFAULT_AUTHORIZED_BOTS).some((bot) => bot.toLowerCase() === normalizedUser);
1262
1292
  }
1263
- const permission = await this.#loadAuthorPermission(owner, repo, user);
1293
+ const permission = await this.#loadAuthorPermission(owner, repo, user, isCurrentGeneration);
1294
+ if (isCurrentGeneration && !isCurrentGeneration()) return false;
1264
1295
  const authorizedPermissions = this.#options.authorizedPermissions ?? DEFAULT_AUTHORIZED_PERMISSIONS;
1265
1296
  return !!permission && authorizedPermissions.includes(permission);
1266
1297
  }
1267
- async #filterUnauthorizedLatestComment(owner, repo, snapshot) {
1298
+ async #filterUnauthorizedLatestComment(owner, repo, snapshot, isCurrentGeneration) {
1268
1299
  const comments = snapshot.latestComments?.length ? snapshot.latestComments : [{
1269
1300
  author: snapshot.latestCommentAuthor,
1270
1301
  authorType: snapshot.latestCommentAuthorType,
@@ -1276,10 +1307,11 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1276
1307
  if (!comments.some((comment) => comment.author)) return snapshot;
1277
1308
  if (!comments.some((comment) => comment.body || comment.url || comment.updatedAt)) return snapshot;
1278
1309
  for (const comment of comments) {
1310
+ if (isCurrentGeneration && !isCurrentGeneration()) return snapshot;
1279
1311
  if (!await this.#isAuthorizedAuthor(owner, repo, comment.author, {
1280
1312
  authorType: comment.authorType,
1281
1313
  isBot: comment.isBot
1282
- })) continue;
1314
+ }, isCurrentGeneration)) continue;
1283
1315
  return {
1284
1316
  ...snapshot,
1285
1317
  latestCommentAuthor: comment.author,
@@ -1300,7 +1332,7 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1300
1332
  latestCommentUpdatedAt: void 0
1301
1333
  };
1302
1334
  }
1303
- async #loadAuthorPermission(owner, repo, user) {
1335
+ async #loadAuthorPermission(owner, repo, user, isCurrentGeneration) {
1304
1336
  const cacheKey = `${owner}/${repo}:${user.toLowerCase()}`;
1305
1337
  const cached = this.#permissionCache.get(cacheKey);
1306
1338
  if (cached && cached.expiresAt > Date.now()) return cached.permission;
@@ -1325,10 +1357,11 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1325
1357
  "none"
1326
1358
  ].includes(raw) ? raw : void 0;
1327
1359
  }
1328
- if (permission) this.#permissionCache.set(cacheKey, {
1360
+ if (permission && (!isCurrentGeneration || isCurrentGeneration())) this.#permissionCache.set(cacheKey, {
1329
1361
  permission,
1330
1362
  expiresAt: Date.now() + PERMISSION_CACHE_TTL_MS
1331
1363
  });
1364
+ if (isCurrentGeneration && !isCurrentGeneration()) return void 0;
1332
1365
  return permission;
1333
1366
  } catch {
1334
1367
  this.#permissionCache.delete(cacheKey);
@@ -1351,10 +1384,12 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1351
1384
  for (const notification of notifications.sort(compareGithubActivityNotifications)) {
1352
1385
  if (!notification) continue;
1353
1386
  if (AUTHOR_GATED_NOTIFICATION_KINDS.has(notification.kind)) {
1354
- if (!await this.#isAuthorizedAuthor(input.subscription.owner, input.subscription.repo, input.snapshot.latestCommentAuthor, {
1387
+ const authorized = await this.#isAuthorizedAuthor(input.subscription.owner, input.subscription.repo, input.snapshot.latestCommentAuthor, {
1355
1388
  authorType: input.snapshot.latestCommentAuthorType,
1356
1389
  isBot: input.snapshot.latestCommentIsBot
1357
- })) continue;
1390
+ }, input.isCurrentGeneration);
1391
+ if (!input.isCurrentGeneration()) return [];
1392
+ if (!authorized) continue;
1358
1393
  }
1359
1394
  notificationInputs.push(this.#createGithubNotificationInput({
1360
1395
  subscription: input.subscription,
@@ -1371,11 +1406,14 @@ var GithubSignals = class extends _mastra_core_signals.SignalProvider {
1371
1406
  resourceId: input.polling.resourceId,
1372
1407
  threadId: input.polling.threadId
1373
1408
  };
1409
+ if (!input.isCurrentGeneration()) return [];
1374
1410
  const streamOptions = await this.#agentOptions.getNotificationStreamOptions?.(target);
1411
+ if (!input.isCurrentGeneration()) return [];
1375
1412
  await agent.sendNotificationSignal(notificationInputs, streamOptions ? {
1376
1413
  ...target,
1377
1414
  ifIdle: { streamOptions }
1378
1415
  } : target);
1416
+ if (!input.isCurrentGeneration()) return [];
1379
1417
  }
1380
1418
  return sent;
1381
1419
  }