@mastra/factory 0.10.2-alpha.3 → 0.11.1-alpha.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/factory.d.ts.map +1 -1
  3. package/dist/factory.js +18 -2
  4. package/dist/factory.js.map +1 -1
  5. package/dist/routes/attention-providers.d.ts +101 -0
  6. package/dist/routes/attention-providers.d.ts.map +1 -0
  7. package/dist/routes/attention-providers.js +384 -0
  8. package/dist/routes/attention-providers.js.map +1 -0
  9. package/dist/routes/attention.d.ts +11 -9
  10. package/dist/routes/attention.d.ts.map +1 -1
  11. package/dist/routes/attention.js +143 -193
  12. package/dist/routes/attention.js.map +1 -1
  13. package/dist/routes/surface.d.ts +2 -0
  14. package/dist/routes/surface.d.ts.map +1 -1
  15. package/dist/routes/surface.js +3 -1
  16. package/dist/routes/surface.js.map +1 -1
  17. package/dist/routes/work-items.d.ts +3 -0
  18. package/dist/routes/work-items.d.ts.map +1 -1
  19. package/dist/routes/work-items.js +3 -1
  20. package/dist/routes/work-items.js.map +1 -1
  21. package/dist/rules/dispatcher.d.ts +3 -0
  22. package/dist/rules/dispatcher.d.ts.map +1 -1
  23. package/dist/rules/dispatcher.js +9 -1
  24. package/dist/rules/dispatcher.js.map +1 -1
  25. package/dist/rules/start-coordinator.d.ts +2 -1
  26. package/dist/rules/start-coordinator.d.ts.map +1 -1
  27. package/dist/rules/start-coordinator.js +10 -2
  28. package/dist/rules/start-coordinator.js.map +1 -1
  29. package/dist/storage/domains/audit/domain.d.ts.map +1 -1
  30. package/dist/storage/domains/audit/domain.js +2 -5
  31. package/dist/storage/domains/audit/domain.js.map +1 -1
  32. package/dist/storage/domains/channel-identity/base.d.ts +4 -0
  33. package/dist/storage/domains/channel-identity/base.d.ts.map +1 -1
  34. package/dist/storage/domains/channel-identity/base.js +15 -0
  35. package/dist/storage/domains/channel-identity/base.js.map +1 -1
  36. package/dist/storage/domains/comments/actor.d.ts +51 -0
  37. package/dist/storage/domains/comments/actor.d.ts.map +1 -0
  38. package/dist/storage/domains/comments/actor.js +38 -0
  39. package/dist/storage/domains/comments/actor.js.map +1 -0
  40. package/dist/storage/domains/comments/base.d.ts +230 -0
  41. package/dist/storage/domains/comments/base.d.ts.map +1 -0
  42. package/dist/storage/domains/comments/base.js +490 -0
  43. package/dist/storage/domains/comments/base.js.map +1 -0
  44. package/dist/storage/domains/comments/domain.d.ts +125 -0
  45. package/dist/storage/domains/comments/domain.d.ts.map +1 -0
  46. package/dist/storage/domains/comments/domain.js +270 -0
  47. package/dist/storage/domains/comments/domain.js.map +1 -0
  48. package/dist/storage/domains/comments/feed-context.d.ts +20 -0
  49. package/dist/storage/domains/comments/feed-context.d.ts.map +1 -0
  50. package/dist/storage/domains/comments/feed-context.js +74 -0
  51. package/dist/storage/domains/comments/feed-context.js.map +1 -0
  52. package/dist/storage/domains/comments/feed-sync.d.ts +38 -0
  53. package/dist/storage/domains/comments/feed-sync.d.ts.map +1 -0
  54. package/dist/storage/domains/comments/feed-sync.js +1 -0
  55. package/dist/storage/domains/comments/parse.d.ts +22 -0
  56. package/dist/storage/domains/comments/parse.d.ts.map +1 -0
  57. package/dist/storage/domains/comments/parse.js +90 -0
  58. package/dist/storage/domains/comments/parse.js.map +1 -0
  59. package/dist/storage/domains/comments/routes.d.ts +22 -0
  60. package/dist/storage/domains/comments/routes.d.ts.map +1 -0
  61. package/dist/storage/domains/comments/routes.js +248 -0
  62. package/dist/storage/domains/comments/routes.js.map +1 -0
  63. package/dist/storage/domains/comments/schema.d.ts +4 -0
  64. package/dist/storage/domains/comments/schema.d.ts.map +1 -0
  65. package/dist/storage/domains/comments/schema.js +135 -0
  66. package/dist/storage/domains/comments/schema.js.map +1 -0
  67. package/dist/storage/domains/comments/wire.d.ts +40 -0
  68. package/dist/storage/domains/comments/wire.d.ts.map +1 -0
  69. package/dist/storage/domains/comments/wire.js +37 -0
  70. package/dist/storage/domains/comments/wire.js.map +1 -0
  71. package/dist/storage/domains/work-items/base.d.ts +14 -10
  72. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  73. package/dist/storage/domains/work-items/base.js +109 -22
  74. package/dist/storage/domains/work-items/base.js.map +1 -1
  75. package/factory-skills/factory-rereview/SKILL.md +23 -14
  76. package/factory-skills/factory-review/SKILL.md +20 -14
  77. package/package.json +7 -7
@@ -0,0 +1,384 @@
1
+ import { factoryAttentionKey, factoryDecisionAttentionIdentity, factoryMentionAttentionIdentity } from "../storage/domains/work-items/base.js";
2
+ import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
3
+ //#region src/routes/attention-providers.ts
4
+ /**
5
+ * Per-kind attention providers. Each kind owns its counts, its bounded page
6
+ * scan, and its wire item shape; the route layer k-way merges provider pages
7
+ * on `occurredAt desc` with one resumable cursor per kind.
8
+ */
9
+ const SCAN_PAGE_SIZE = 50;
10
+ const MAX_RECEIPT_SCAN_PAGES = 4;
11
+ /**
12
+ * One bounded keyset scan, shared by every provider read. Walks newest-first
13
+ * from `before` until the stream runs dry or the page budget is spent; the
14
+ * final batch of a budget stop carries the position to resume from.
15
+ */
16
+ async function* scanBatches(fetchBatch, positionOf, before) {
17
+ let cursor = before;
18
+ for (let scanned = 1; scanned <= MAX_RECEIPT_SCAN_PAGES; scanned += 1) {
19
+ const { rows, hasMore } = await fetchBatch(cursor);
20
+ const last = rows.at(-1);
21
+ if (!last) return;
22
+ const position = positionOf(last);
23
+ yield {
24
+ rows,
25
+ ...hasMore && scanned === MAX_RECEIPT_SCAN_PAGES ? { continuation: position } : {}
26
+ };
27
+ if (!hasMore) return;
28
+ cursor = position;
29
+ }
30
+ }
31
+ /** Fills a page from the scan, stopping at `limit` entries or at the scan budget. */
32
+ async function collectPage(batches, limit, toEntries) {
33
+ const entries = [];
34
+ for await (const batch of batches) {
35
+ for (const entry of await toEntries(batch.rows)) {
36
+ if (entries.length === limit) return {
37
+ entries,
38
+ hasMore: true
39
+ };
40
+ entries.push(entry);
41
+ }
42
+ if (batch.continuation) return {
43
+ entries,
44
+ hasMore: true,
45
+ continuation: batch.continuation
46
+ };
47
+ }
48
+ return {
49
+ entries,
50
+ hasMore: false
51
+ };
52
+ }
53
+ /** Marks every scanned batch read, reporting where a budget stop left off. */
54
+ async function markScanRead(batches, markBatch) {
55
+ for await (const batch of batches) {
56
+ await markBatch(batch.rows);
57
+ if (batch.continuation) return {
58
+ hasMore: true,
59
+ continuation: batch.continuation
60
+ };
61
+ }
62
+ return { hasMore: false };
63
+ }
64
+ function factoryDecisionType(decision) {
65
+ return typeof decision.decision.type === "string" ? decision.decision.type.slice(0, 64) : "unknown";
66
+ }
67
+ function failureOccurredAt(decision) {
68
+ return decision.completedAt ?? decision.updatedAt;
69
+ }
70
+ function matchesView(view, receipt) {
71
+ if (view === "archived") return receipt?.state === "archived";
72
+ if (view === "unread") return receipt === void 0;
73
+ return receipt?.state !== "archived";
74
+ }
75
+ function attentionTarget(decision, item) {
76
+ if (!item) return { kind: "rules" };
77
+ const role = typeof decision.decision.role === "string" ? decision.decision.role : void 0;
78
+ const session = role ? item.sessions[role] : void 0;
79
+ if (session) return {
80
+ kind: "thread",
81
+ sessionId: session.sessionId,
82
+ threadId: session.threadId
83
+ };
84
+ return {
85
+ kind: "work-item",
86
+ workItemId: item.id,
87
+ board: workItemBoard(item)
88
+ };
89
+ }
90
+ function workItemBoard(item) {
91
+ return item.externalSource?.integrationId === "github" && item.externalSource.type === "pull-request" ? "review" : "work";
92
+ }
93
+ var AutomationFailedAttentionProvider = class {
94
+ kind = "automation-failed";
95
+ #workItems;
96
+ constructor({ workItems }) {
97
+ this.#workItems = workItems;
98
+ }
99
+ async counts(scope) {
100
+ const [failedCount, receiptCount, archivedCount] = await Promise.all([
101
+ this.#workItems.countDeferredDecisionsByStatuses({
102
+ orgId: scope.orgId,
103
+ factoryProjectId: scope.factoryProjectId,
104
+ statuses: ["failed"]
105
+ }),
106
+ this.#workItems.countAttentionReceipts({
107
+ ...receiptScope(scope),
108
+ kind: this.kind
109
+ }),
110
+ this.#workItems.countAttentionReceipts({
111
+ ...receiptScope(scope),
112
+ kind: this.kind,
113
+ state: "archived"
114
+ })
115
+ ]);
116
+ return {
117
+ open: Math.max(0, failedCount - archivedCount),
118
+ unread: Math.max(0, failedCount - receiptCount)
119
+ };
120
+ }
121
+ async latest(scope) {
122
+ const newest = (await this.#workItems.listFailedDecisionPage({
123
+ orgId: scope.orgId,
124
+ factoryProjectId: scope.factoryProjectId,
125
+ limit: 1
126
+ })).decisions[0];
127
+ if (!newest) return null;
128
+ const identity = factoryDecisionAttentionIdentity(newest.id, newest.failureOccurrence);
129
+ const receipts = await this.#workItems.listAttentionReceipts({
130
+ ...receiptScope(scope),
131
+ identities: [identity]
132
+ });
133
+ return {
134
+ key: factoryAttentionKey(scope.factoryProjectId, identity),
135
+ at: failureOccurredAt(newest),
136
+ unread: receipts.length === 0
137
+ };
138
+ }
139
+ #scan(scope, before) {
140
+ return scanBatches(async (cursor) => {
141
+ const page = await this.#workItems.listFailedDecisionPage({
142
+ orgId: scope.orgId,
143
+ factoryProjectId: scope.factoryProjectId,
144
+ before: cursor,
145
+ limit: SCAN_PAGE_SIZE
146
+ });
147
+ return {
148
+ rows: page.decisions,
149
+ hasMore: page.hasMore
150
+ };
151
+ }, (decision) => ({
152
+ occurredAt: failureOccurredAt(decision),
153
+ id: decision.id
154
+ }), before);
155
+ }
156
+ async page(scope, { view, search, before, limit }) {
157
+ return collectPage(this.#scan(scope, before), limit, async (decisions) => {
158
+ const [receiptByKey, itemById] = await Promise.all([this.#receiptsFor(scope, decisions), this.#linkedItems(scope, decisions)]);
159
+ const entries = [];
160
+ for (const decision of decisions) {
161
+ const identity = factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);
162
+ const receipt = receiptByKey.get(factoryAttentionKey(scope.factoryProjectId, identity));
163
+ if (!matchesView(view, receipt)) continue;
164
+ const item = decision.workItemId ? itemById.get(decision.workItemId) : void 0;
165
+ if (search && !matchesFailureSearch(decision, item, search)) continue;
166
+ entries.push({
167
+ occurredAt: failureOccurredAt(decision),
168
+ resumeCursor: {
169
+ occurredAt: failureOccurredAt(decision),
170
+ id: decision.id
171
+ },
172
+ item: toFailureItem(scope, decision, item, receipt)
173
+ });
174
+ }
175
+ return entries;
176
+ });
177
+ }
178
+ async #receiptsFor(scope, decisions) {
179
+ const receipts = await this.#workItems.listAttentionReceipts({
180
+ ...receiptScope(scope),
181
+ identities: decisions.map((decision) => factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence))
182
+ });
183
+ return new Map(receipts.map((receipt) => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]));
184
+ }
185
+ async #linkedItems(scope, decisions) {
186
+ const items = await this.#workItems.listByIds({
187
+ orgId: scope.orgId,
188
+ factoryProjectId: scope.factoryProjectId,
189
+ ids: decisions.flatMap((decision) => decision.workItemId ? [decision.workItemId] : [])
190
+ });
191
+ return new Map(items.map((item) => [item.id, item]));
192
+ }
193
+ async markAllRead(scope, { before, now }) {
194
+ return markScanRead(this.#scan(scope, before), async (decisions) => {
195
+ await this.#workItems.markAttentionReceiptsRead({
196
+ ...receiptScope(scope),
197
+ identities: decisions.map((decision) => factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence)),
198
+ now
199
+ });
200
+ });
201
+ }
202
+ };
203
+ var MentionAttentionProvider = class {
204
+ kind = "mention";
205
+ #workItems;
206
+ #comments;
207
+ constructor({ workItems, comments }) {
208
+ this.#workItems = workItems;
209
+ this.#comments = comments;
210
+ }
211
+ #scan(scope, before) {
212
+ return scanBatches(async (cursor) => {
213
+ const rows = await this.#comments.listMentionsForUser({
214
+ ...receiptScope(scope),
215
+ ...cursor ? { before: cursor } : {},
216
+ limit: 51
217
+ });
218
+ return {
219
+ rows: rows.slice(0, SCAN_PAGE_SIZE),
220
+ hasMore: rows.length > SCAN_PAGE_SIZE
221
+ };
222
+ }, (mention) => ({
223
+ occurredAt: mention.occurredAt,
224
+ id: mention.id
225
+ }), before);
226
+ }
227
+ /**
228
+ * Mention rows joined to what the inbox needs to show one — dropping the
229
+ * ones it cannot: a deleted comment, or a work item gone from the project.
230
+ */
231
+ async #resolve(scope, mentions) {
232
+ const [receipts, comments, items] = await Promise.all([
233
+ this.#workItems.listAttentionReceipts({
234
+ ...receiptScope(scope),
235
+ identities: mentions.map((mention) => factoryMentionAttentionIdentity(mention.commentId))
236
+ }),
237
+ this.#comments.listByIds({
238
+ orgId: scope.orgId,
239
+ ids: [...new Set(mentions.map((mention) => mention.commentId))]
240
+ }),
241
+ this.#workItems.listByIds({
242
+ orgId: scope.orgId,
243
+ factoryProjectId: scope.factoryProjectId,
244
+ ids: [...new Set(mentions.map((mention) => mention.workItemId))]
245
+ })
246
+ ]);
247
+ const receiptByKey = new Map(receipts.map((receipt) => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]));
248
+ const commentById = new Map(comments.map((comment) => [comment.id, comment]));
249
+ const itemById = new Map(items.map((item) => [item.id, item]));
250
+ const resolved = [];
251
+ for (const mention of mentions) {
252
+ const comment = commentById.get(mention.commentId);
253
+ const item = itemById.get(mention.workItemId);
254
+ if (!comment || comment.deletedAt || !item) continue;
255
+ const identity = factoryMentionAttentionIdentity(mention.commentId);
256
+ resolved.push({
257
+ mention,
258
+ comment,
259
+ item,
260
+ identity,
261
+ receipt: receiptByKey.get(factoryAttentionKey(scope.factoryProjectId, identity))
262
+ });
263
+ }
264
+ return resolved;
265
+ }
266
+ /**
267
+ * Derived from the same bounded scan as `page`, never from raw aggregates:
268
+ * the badge must count exactly what the views can show, down to stopping at
269
+ * the same scan budget. Aggregates would count orphan mention rows the list
270
+ * can never display or clear.
271
+ */
272
+ async counts(scope) {
273
+ let open = 0;
274
+ let unread = 0;
275
+ for await (const batch of this.#scan(scope)) for (const { receipt } of await this.#resolve(scope, batch.rows)) {
276
+ if (receipt?.state !== "archived") open += 1;
277
+ if (receipt === void 0) unread += 1;
278
+ }
279
+ return {
280
+ open,
281
+ unread
282
+ };
283
+ }
284
+ async latest(scope) {
285
+ const mentions = await this.#comments.listMentionsForUser({
286
+ ...receiptScope(scope),
287
+ limit: 10
288
+ });
289
+ const newest = (await this.#resolve(scope, mentions))[0];
290
+ if (!newest) return null;
291
+ return {
292
+ key: factoryAttentionKey(scope.factoryProjectId, newest.identity),
293
+ at: newest.mention.occurredAt,
294
+ unread: newest.receipt === void 0
295
+ };
296
+ }
297
+ async page(scope, { view, search, before, limit }) {
298
+ return collectPage(this.#scan(scope, before), limit, async (mentions) => {
299
+ const entries = [];
300
+ for (const resolved of await this.#resolve(scope, mentions)) {
301
+ if (!matchesView(view, resolved.receipt)) continue;
302
+ if (search && !matchesMentionSearch(resolved, search)) continue;
303
+ entries.push({
304
+ occurredAt: resolved.mention.occurredAt,
305
+ resumeCursor: {
306
+ occurredAt: resolved.mention.occurredAt,
307
+ id: resolved.mention.id
308
+ },
309
+ item: toMentionItem(scope, resolved)
310
+ });
311
+ }
312
+ return entries;
313
+ });
314
+ }
315
+ async markAllRead(scope, { before, now }) {
316
+ return markScanRead(this.#scan(scope, before), async (mentions) => {
317
+ await this.#workItems.markAttentionReceiptsRead({
318
+ ...receiptScope(scope),
319
+ identities: mentions.map((mention) => factoryMentionAttentionIdentity(mention.commentId)),
320
+ now
321
+ });
322
+ });
323
+ }
324
+ };
325
+ function matchesFailureSearch(decision, item, search) {
326
+ return item?.title.toLowerCase().includes(search) === true || decision.lastError?.toLowerCase().includes(search) === true || factoryDecisionType(decision).toLowerCase().includes(search);
327
+ }
328
+ function toFailureItem(scope, decision, item, receipt) {
329
+ const failure = factoryDispatchFailureMetadata(decision.failureCode);
330
+ const identity = factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);
331
+ return {
332
+ key: factoryAttentionKey(scope.factoryProjectId, identity),
333
+ kind: "automation-failed",
334
+ decisionId: decision.id,
335
+ occurrence: decision.failureOccurrence,
336
+ workItemId: decision.workItemId,
337
+ title: item?.title ?? failure.label,
338
+ detail: decision.lastError?.slice(0, 512) ?? failure.label,
339
+ decisionType: factoryDecisionType(decision),
340
+ failureCode: decision.failureCode,
341
+ canRetry: failure.canRetry,
342
+ occurredAt: failureOccurredAt(decision).toISOString(),
343
+ read: receipt !== void 0,
344
+ archived: receipt?.state === "archived",
345
+ target: attentionTarget(decision, item)
346
+ };
347
+ }
348
+ function matchesMentionSearch({ item, comment }, search) {
349
+ return item.title.toLowerCase().includes(search) || comment.body.toLowerCase().includes(search) || comment.author.displayName?.toLowerCase().includes(search) === true;
350
+ }
351
+ function toMentionItem(scope, { mention, comment, item, identity, receipt }) {
352
+ const authorName = comment.author.displayName;
353
+ return {
354
+ key: factoryAttentionKey(scope.factoryProjectId, identity),
355
+ kind: "mention",
356
+ commentId: mention.commentId,
357
+ occurrence: 0,
358
+ workItemId: mention.workItemId,
359
+ title: item.title,
360
+ detail: comment.body.slice(0, 512),
361
+ authorId: comment.author.id,
362
+ ...authorName ? { authorName } : {},
363
+ occurredAt: mention.occurredAt.toISOString(),
364
+ read: receipt !== void 0,
365
+ archived: receipt?.state === "archived",
366
+ target: {
367
+ kind: "work-item",
368
+ workItemId: mention.workItemId,
369
+ board: workItemBoard(item),
370
+ commentId: mention.commentId
371
+ }
372
+ };
373
+ }
374
+ function receiptScope(scope) {
375
+ return {
376
+ orgId: scope.orgId,
377
+ factoryProjectId: scope.factoryProjectId,
378
+ userId: scope.userId
379
+ };
380
+ }
381
+ //#endregion
382
+ export { AutomationFailedAttentionProvider, MentionAttentionProvider, factoryDecisionType };
383
+
384
+ //# sourceMappingURL=attention-providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-providers.js","names":["#workItems","#scan","#receiptsFor","#linkedItems","#comments","#resolve"],"sources":["../../src/routes/attention-providers.ts"],"sourcesContent":["/**\n * Per-kind attention providers. Each kind owns its counts, its bounded page\n * scan, and its wire item shape; the route layer k-way merges provider pages\n * on `occurredAt desc` with one resumable cursor per kind.\n */\n\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport type {\n WorkItemCommentRow,\n WorkItemCommentsStorage,\n WorkItemMentionRow,\n} from '../storage/domains/comments/base.js';\nimport type {\n FactoryAttentionIdentity,\n FactoryAttentionKind,\n FactoryAttentionReceiptRecord,\n FactoryDeferredDecisionRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport {\n factoryAttentionKey,\n factoryDecisionAttentionIdentity,\n factoryMentionAttentionIdentity,\n} from '../storage/domains/work-items/base.js';\n\nexport type FactoryAttentionView = 'open' | 'unread' | 'archived';\n\nexport interface AttentionScope {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n}\n\nexport interface AttentionStreamPosition {\n occurredAt: Date;\n id: string;\n}\n\nexport interface AttentionEntry {\n occurredAt: Date;\n /** Position resuming the provider's stream right after this entry. */\n resumeCursor: AttentionStreamPosition;\n item: Record<string, unknown>;\n}\n\nexport interface AttentionPageArgs {\n view: FactoryAttentionView;\n search?: string;\n before?: AttentionStreamPosition;\n limit: number;\n}\n\nexport interface AttentionPageResult {\n entries: AttentionEntry[];\n /** More rows behind the scan once every returned entry is consumed. */\n hasMore: boolean;\n /** Resume point for a scan-budget stop past the last returned entry. */\n continuation?: AttentionStreamPosition;\n}\n\nexport interface AttentionCounts {\n open: number;\n unread: number;\n}\n\nexport interface AttentionLatest {\n key: string;\n at: Date;\n unread: boolean;\n}\n\nexport interface AttentionProvider {\n kind: FactoryAttentionKind;\n counts(scope: AttentionScope): Promise<AttentionCounts>;\n latest(scope: AttentionScope): Promise<AttentionLatest | null>;\n page(scope: AttentionScope, args: AttentionPageArgs): Promise<AttentionPageResult>;\n markAllRead(\n scope: AttentionScope,\n args: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }>;\n}\n\nconst SCAN_PAGE_SIZE = 50;\n// Receipt filtering is bounded per request; the response cursor resumes after the last scan.\nconst MAX_RECEIPT_SCAN_PAGES = 4;\n\ninterface ScanBatch<T> {\n rows: T[];\n /** Set only when the page budget stopped a scan that still had rows behind it. */\n continuation?: AttentionStreamPosition;\n}\n\n/**\n * One bounded keyset scan, shared by every provider read. Walks newest-first\n * from `before` until the stream runs dry or the page budget is spent; the\n * final batch of a budget stop carries the position to resume from.\n */\nasync function* scanBatches<T>(\n fetchBatch: (before?: AttentionStreamPosition) => Promise<{ rows: T[]; hasMore: boolean }>,\n positionOf: (row: T) => AttentionStreamPosition,\n before?: AttentionStreamPosition,\n): AsyncGenerator<ScanBatch<T>> {\n let cursor = before;\n for (let scanned = 1; scanned <= MAX_RECEIPT_SCAN_PAGES; scanned += 1) {\n const { rows, hasMore } = await fetchBatch(cursor);\n const last = rows.at(-1);\n if (!last) return;\n const position = positionOf(last);\n yield { rows, ...(hasMore && scanned === MAX_RECEIPT_SCAN_PAGES ? { continuation: position } : {}) };\n if (!hasMore) return;\n cursor = position;\n }\n}\n\n/** Fills a page from the scan, stopping at `limit` entries or at the scan budget. */\nasync function collectPage<T>(\n batches: AsyncGenerator<ScanBatch<T>>,\n limit: number,\n toEntries: (rows: T[]) => Promise<AttentionEntry[]>,\n): Promise<AttentionPageResult> {\n const entries: AttentionEntry[] = [];\n for await (const batch of batches) {\n for (const entry of await toEntries(batch.rows)) {\n if (entries.length === limit) return { entries, hasMore: true };\n entries.push(entry);\n }\n if (batch.continuation) return { entries, hasMore: true, continuation: batch.continuation };\n }\n return { entries, hasMore: false };\n}\n\n/** Marks every scanned batch read, reporting where a budget stop left off. */\nasync function markScanRead<T>(\n batches: AsyncGenerator<ScanBatch<T>>,\n markBatch: (rows: T[]) => Promise<void>,\n): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n for await (const batch of batches) {\n await markBatch(batch.rows);\n if (batch.continuation) return { hasMore: true, continuation: batch.continuation };\n }\n return { hasMore: false };\n}\n\nexport function factoryDecisionType(decision: FactoryDeferredDecisionRecord): string {\n return typeof decision.decision.type === 'string' ? decision.decision.type.slice(0, 64) : 'unknown';\n}\n\nfunction failureOccurredAt(decision: FactoryDeferredDecisionRecord): Date {\n return decision.completedAt ?? decision.updatedAt;\n}\n\nfunction matchesView(view: FactoryAttentionView, receipt: FactoryAttentionReceiptRecord | undefined): boolean {\n if (view === 'archived') return receipt?.state === 'archived';\n if (view === 'unread') return receipt === undefined;\n return receipt?.state !== 'archived';\n}\n\nfunction attentionTarget(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined) {\n if (!item) return { kind: 'rules' as const };\n const role = typeof decision.decision.role === 'string' ? decision.decision.role : undefined;\n const session = role ? item.sessions[role] : undefined;\n if (session) {\n return {\n kind: 'thread' as const,\n sessionId: session.sessionId,\n threadId: session.threadId,\n };\n }\n return {\n kind: 'work-item' as const,\n workItemId: item.id,\n board: workItemBoard(item),\n };\n}\n\nfunction workItemBoard(item: WorkItemRow): 'review' | 'work' {\n const review = item.externalSource?.integrationId === 'github' && item.externalSource.type === 'pull-request';\n return review ? 'review' : 'work';\n}\n\nexport class AutomationFailedAttentionProvider implements AttentionProvider {\n readonly kind = 'automation-failed' as const;\n readonly #workItems: WorkItemsStorage;\n\n constructor({ workItems }: { workItems: WorkItemsStorage }) {\n this.#workItems = workItems;\n }\n\n async counts(scope: AttentionScope): Promise<AttentionCounts> {\n const [failedCount, receiptCount, archivedCount] = await Promise.all([\n this.#workItems.countDeferredDecisionsByStatuses({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n statuses: ['failed'],\n }),\n this.#workItems.countAttentionReceipts({ ...receiptScope(scope), kind: this.kind }),\n this.#workItems.countAttentionReceipts({ ...receiptScope(scope), kind: this.kind, state: 'archived' }),\n ]);\n return {\n open: Math.max(0, failedCount - archivedCount),\n unread: Math.max(0, failedCount - receiptCount),\n };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const page = await this.#workItems.listFailedDecisionPage({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n limit: 1,\n });\n const newest = page.decisions[0];\n if (!newest) return null;\n const identity = factoryDecisionAttentionIdentity(newest.id, newest.failureOccurrence);\n const receipts = await this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: [identity],\n });\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n at: failureOccurredAt(newest),\n unread: receipts.length === 0,\n };\n }\n\n #scan(scope: AttentionScope, before?: AttentionStreamPosition) {\n return scanBatches<FactoryDeferredDecisionRecord>(\n async cursor => {\n const page = await this.#workItems.listFailedDecisionPage({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n before: cursor,\n limit: SCAN_PAGE_SIZE,\n });\n return { rows: page.decisions, hasMore: page.hasMore };\n },\n decision => ({ occurredAt: failureOccurredAt(decision), id: decision.id }),\n before,\n );\n }\n\n async page(scope: AttentionScope, { view, search, before, limit }: AttentionPageArgs): Promise<AttentionPageResult> {\n return collectPage(this.#scan(scope, before), limit, async decisions => {\n const [receiptByKey, itemById] = await Promise.all([\n this.#receiptsFor(scope, decisions),\n this.#linkedItems(scope, decisions),\n ]);\n const entries: AttentionEntry[] = [];\n for (const decision of decisions) {\n const identity = factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);\n const receipt = receiptByKey.get(factoryAttentionKey(scope.factoryProjectId, identity));\n if (!matchesView(view, receipt)) continue;\n const item = decision.workItemId ? itemById.get(decision.workItemId) : undefined;\n if (search && !matchesFailureSearch(decision, item, search)) continue;\n entries.push({\n occurredAt: failureOccurredAt(decision),\n resumeCursor: { occurredAt: failureOccurredAt(decision), id: decision.id },\n item: toFailureItem(scope, decision, item, receipt),\n });\n }\n return entries;\n });\n }\n\n async #receiptsFor(\n scope: AttentionScope,\n decisions: FactoryDeferredDecisionRecord[],\n ): Promise<Map<string, FactoryAttentionReceiptRecord>> {\n const receipts = await this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: decisions.map(decision => factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence)),\n });\n return new Map(receipts.map(receipt => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]));\n }\n\n async #linkedItems(\n scope: AttentionScope,\n decisions: FactoryDeferredDecisionRecord[],\n ): Promise<Map<string, WorkItemRow>> {\n const items = await this.#workItems.listByIds({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n ids: decisions.flatMap(decision => (decision.workItemId ? [decision.workItemId] : [])),\n });\n return new Map(items.map(item => [item.id, item]));\n }\n\n async markAllRead(\n scope: AttentionScope,\n { before, now }: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n return markScanRead(this.#scan(scope, before), async decisions => {\n await this.#workItems.markAttentionReceiptsRead({\n ...receiptScope(scope),\n identities: decisions.map(decision =>\n factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence),\n ),\n now,\n });\n });\n }\n}\n\ninterface ResolvedMention {\n mention: WorkItemMentionRow;\n comment: WorkItemCommentRow;\n item: WorkItemRow;\n identity: FactoryAttentionIdentity;\n receipt: FactoryAttentionReceiptRecord | undefined;\n}\n\nexport class MentionAttentionProvider implements AttentionProvider {\n readonly kind = 'mention' as const;\n readonly #workItems: WorkItemsStorage;\n readonly #comments: WorkItemCommentsStorage;\n\n constructor({ workItems, comments }: { workItems: WorkItemsStorage; comments: WorkItemCommentsStorage }) {\n this.#workItems = workItems;\n this.#comments = comments;\n }\n\n #scan(scope: AttentionScope, before?: AttentionStreamPosition) {\n return scanBatches<WorkItemMentionRow>(\n async cursor => {\n const rows = await this.#comments.listMentionsForUser({\n ...receiptScope(scope),\n ...(cursor ? { before: cursor } : {}),\n limit: SCAN_PAGE_SIZE + 1,\n });\n return { rows: rows.slice(0, SCAN_PAGE_SIZE), hasMore: rows.length > SCAN_PAGE_SIZE };\n },\n mention => ({ occurredAt: mention.occurredAt, id: mention.id }),\n before,\n );\n }\n\n /**\n * Mention rows joined to what the inbox needs to show one — dropping the\n * ones it cannot: a deleted comment, or a work item gone from the project.\n */\n async #resolve(scope: AttentionScope, mentions: WorkItemMentionRow[]): Promise<ResolvedMention[]> {\n const [receipts, comments, items] = await Promise.all([\n this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: mentions.map(mention => factoryMentionAttentionIdentity(mention.commentId)),\n }),\n this.#comments.listByIds({\n orgId: scope.orgId,\n ids: [...new Set(mentions.map(mention => mention.commentId))],\n }),\n this.#workItems.listByIds({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n ids: [...new Set(mentions.map(mention => mention.workItemId))],\n }),\n ]);\n const receiptByKey = new Map(\n receipts.map(receipt => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]),\n );\n const commentById = new Map(comments.map(comment => [comment.id, comment]));\n const itemById = new Map(items.map(item => [item.id, item]));\n\n const resolved: ResolvedMention[] = [];\n for (const mention of mentions) {\n const comment = commentById.get(mention.commentId);\n const item = itemById.get(mention.workItemId);\n if (!comment || comment.deletedAt || !item) continue;\n const identity = factoryMentionAttentionIdentity(mention.commentId);\n resolved.push({\n mention,\n comment,\n item,\n identity,\n receipt: receiptByKey.get(factoryAttentionKey(scope.factoryProjectId, identity)),\n });\n }\n return resolved;\n }\n\n /**\n * Derived from the same bounded scan as `page`, never from raw aggregates:\n * the badge must count exactly what the views can show, down to stopping at\n * the same scan budget. Aggregates would count orphan mention rows the list\n * can never display or clear.\n */\n async counts(scope: AttentionScope): Promise<AttentionCounts> {\n let open = 0;\n let unread = 0;\n for await (const batch of this.#scan(scope)) {\n for (const { receipt } of await this.#resolve(scope, batch.rows)) {\n if (receipt?.state !== 'archived') open += 1;\n if (receipt === undefined) unread += 1;\n }\n }\n return { open, unread };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const mentions = await this.#comments.listMentionsForUser({ ...receiptScope(scope), limit: 10 });\n const newest = (await this.#resolve(scope, mentions))[0];\n if (!newest) return null;\n return {\n key: factoryAttentionKey(scope.factoryProjectId, newest.identity),\n at: newest.mention.occurredAt,\n unread: newest.receipt === undefined,\n };\n }\n\n async page(scope: AttentionScope, { view, search, before, limit }: AttentionPageArgs): Promise<AttentionPageResult> {\n return collectPage(this.#scan(scope, before), limit, async mentions => {\n const entries: AttentionEntry[] = [];\n for (const resolved of await this.#resolve(scope, mentions)) {\n if (!matchesView(view, resolved.receipt)) continue;\n if (search && !matchesMentionSearch(resolved, search)) continue;\n entries.push({\n occurredAt: resolved.mention.occurredAt,\n resumeCursor: { occurredAt: resolved.mention.occurredAt, id: resolved.mention.id },\n item: toMentionItem(scope, resolved),\n });\n }\n return entries;\n });\n }\n\n async markAllRead(\n scope: AttentionScope,\n { before, now }: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n return markScanRead(this.#scan(scope, before), async mentions => {\n await this.#workItems.markAttentionReceiptsRead({\n ...receiptScope(scope),\n identities: mentions.map(mention => factoryMentionAttentionIdentity(mention.commentId)),\n now,\n });\n });\n }\n}\n\nfunction matchesFailureSearch(\n decision: FactoryDeferredDecisionRecord,\n item: WorkItemRow | undefined,\n search: string,\n): boolean {\n return (\n item?.title.toLowerCase().includes(search) === true ||\n decision.lastError?.toLowerCase().includes(search) === true ||\n factoryDecisionType(decision).toLowerCase().includes(search)\n );\n}\n\nfunction toFailureItem(\n scope: AttentionScope,\n decision: FactoryDeferredDecisionRecord,\n item: WorkItemRow | undefined,\n receipt: FactoryAttentionReceiptRecord | undefined,\n) {\n const failure = factoryDispatchFailureMetadata(decision.failureCode);\n const identity = factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n kind: 'automation-failed' as const,\n decisionId: decision.id,\n occurrence: decision.failureOccurrence,\n workItemId: decision.workItemId,\n title: item?.title ?? failure.label,\n detail: decision.lastError?.slice(0, 512) ?? failure.label,\n decisionType: factoryDecisionType(decision),\n failureCode: decision.failureCode,\n canRetry: failure.canRetry,\n occurredAt: failureOccurredAt(decision).toISOString(),\n read: receipt !== undefined,\n archived: receipt?.state === 'archived',\n target: attentionTarget(decision, item),\n };\n}\n\nfunction matchesMentionSearch({ item, comment }: ResolvedMention, search: string): boolean {\n return (\n item.title.toLowerCase().includes(search) ||\n comment.body.toLowerCase().includes(search) ||\n comment.author.displayName?.toLowerCase().includes(search) === true\n );\n}\n\nfunction toMentionItem(scope: AttentionScope, { mention, comment, item, identity, receipt }: ResolvedMention) {\n const authorName = comment.author.displayName;\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n kind: 'mention' as const,\n commentId: mention.commentId,\n occurrence: 0,\n workItemId: mention.workItemId,\n title: item.title,\n detail: comment.body.slice(0, 512),\n authorId: comment.author.id,\n ...(authorName ? { authorName } : {}),\n occurredAt: mention.occurredAt.toISOString(),\n read: receipt !== undefined,\n archived: receipt?.state === 'archived',\n target: {\n kind: 'work-item' as const,\n workItemId: mention.workItemId,\n board: workItemBoard(item),\n commentId: mention.commentId,\n },\n };\n}\n\nfunction receiptScope(scope: AttentionScope): { orgId: string; factoryProjectId: string; userId: string } {\n return { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId, userId: scope.userId };\n}\n"],"mappings":"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"}
@@ -1,17 +1,19 @@
1
+ /**
2
+ * Attention routes: k-way merge of the per-kind providers on `occurredAt desc`.
3
+ * The wire cursor is a per-kind map — each kind's stream resumes independently,
4
+ * `null` meaning "not started yet", an absent kind meaning "exhausted".
5
+ */
1
6
  import type { ApiRoute } from '@mastra/core/server';
2
- import type { FactoryDeferredDecisionRecord, WorkItemsStorage } from '../storage/domains/work-items/base.js';
3
- interface ResolvedAttentionProject {
4
- orgId: string;
5
- userId: string;
6
- factoryProjectId: string;
7
- }
7
+ import type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';
8
+ import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
9
+ import type { AttentionScope } from './attention-providers.js';
10
+ export { factoryDecisionType } from './attention-providers.js';
8
11
  interface AttentionRouteDependencies {
9
12
  workItems: WorkItemsStorage;
10
- resolveProject(context: unknown): Promise<ResolvedAttentionProject | {
13
+ comments: WorkItemCommentsStorage;
14
+ resolveProject(context: unknown): Promise<AttentionScope | {
11
15
  response: Response;
12
16
  }>;
13
17
  }
14
- export declare function factoryDecisionType(decision: FactoryDeferredDecisionRecord): string;
15
18
  export declare function buildAttentionRoutes(dependencies: AttentionRouteDependencies): ApiRoute[];
16
- export {};
17
19
  //# sourceMappingURL=attention.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attention.d.ts","sourceRoot":"","sources":["../../src/routes/attention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAGV,6BAA6B,EAE7B,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAW/C,UAAU,wBAAwB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,UAAU,0BAA0B;IAClC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,wBAAwB,GAAG;QAAE,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAC9F;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,6BAA6B,GAAG,MAAM,CAEnF;AA6ID,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,0BAA0B,GAAG,QAAQ,EAAE,CA0MzF"}
1
+ {"version":3,"file":"attention.d.ts","sourceRoot":"","sources":["../../src/routes/attention.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAE/C,OAAO,KAAK,EAIV,cAAc,EAGf,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,UAAU,0BAA0B;IAClC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,EAAE,uBAAuB,CAAC;IAClC,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,GAAG;QAAE,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CACpF;AAoLD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,0BAA0B,GAAG,QAAQ,EAAE,CAwGzF"}