@mastra/factory 0.13.0-alpha.4 → 0.13.0-alpha.6

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 (88) hide show
  1. package/dist/boards/define-board.d.ts +39 -0
  2. package/dist/boards/define-board.d.ts.map +1 -0
  3. package/dist/boards/define-board.js +51 -0
  4. package/dist/boards/define-board.js.map +1 -0
  5. package/dist/boards/index.d.ts +10 -0
  6. package/dist/boards/index.d.ts.map +1 -0
  7. package/dist/boards/index.js +19 -0
  8. package/dist/boards/index.js.map +1 -0
  9. package/dist/boards/review.d.ts +4 -0
  10. package/dist/boards/review.d.ts.map +1 -0
  11. package/dist/boards/review.js +76 -0
  12. package/dist/boards/review.js.map +1 -0
  13. package/dist/boards/work.d.ts +4 -0
  14. package/dist/boards/work.d.ts.map +1 -0
  15. package/dist/boards/work.js +152 -0
  16. package/dist/boards/work.js.map +1 -0
  17. package/dist/factory.js +1 -1
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +5 -1
  21. package/dist/integrations/github/acceptance-labels.d.ts +0 -1
  22. package/dist/integrations/github/acceptance-labels.d.ts.map +1 -1
  23. package/dist/integrations/github/acceptance-labels.js +2 -2
  24. package/dist/integrations/github/acceptance-labels.js.map +1 -1
  25. package/dist/integrations/github/routes.d.ts.map +1 -1
  26. package/dist/integrations/github/routes.js +2 -1
  27. package/dist/integrations/github/routes.js.map +1 -1
  28. package/dist/integrations/github/rules.d.ts.map +1 -1
  29. package/dist/integrations/github/rules.js +2 -1
  30. package/dist/integrations/github/rules.js.map +1 -1
  31. package/dist/integrations/linear/agent-tools.d.ts +3 -3
  32. package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
  33. package/dist/integrations/linear/agent-tools.js +5 -3
  34. package/dist/integrations/linear/agent-tools.js.map +1 -1
  35. package/dist/integrations/linear/rules.d.ts.map +1 -1
  36. package/dist/integrations/linear/rules.js +1 -0
  37. package/dist/integrations/linear/rules.js.map +1 -1
  38. package/dist/routes/attention-proposed.d.ts +3 -0
  39. package/dist/routes/attention-proposed.d.ts.map +1 -0
  40. package/dist/routes/attention-proposed.js +21 -0
  41. package/dist/routes/attention-proposed.js.map +1 -0
  42. package/dist/routes/attention-providers.d.ts +16 -4
  43. package/dist/routes/attention-providers.d.ts.map +1 -1
  44. package/dist/routes/attention-providers.js +53 -46
  45. package/dist/routes/attention-providers.js.map +1 -1
  46. package/dist/routes/attention.d.ts.map +1 -1
  47. package/dist/routes/attention.js +22 -28
  48. package/dist/routes/attention.js.map +1 -1
  49. package/dist/routes/surface.d.ts.map +1 -1
  50. package/dist/routes/surface.js +10 -6
  51. package/dist/routes/surface.js.map +1 -1
  52. package/dist/routes/work-items.d.ts.map +1 -1
  53. package/dist/routes/work-items.js +9 -40
  54. package/dist/routes/work-items.js.map +1 -1
  55. package/dist/rules/defaults.d.ts.map +1 -1
  56. package/dist/rules/defaults.js +4 -109
  57. package/dist/rules/defaults.js.map +1 -1
  58. package/dist/rules/dispatcher.d.ts.map +1 -1
  59. package/dist/rules/dispatcher.js +5 -2
  60. package/dist/rules/dispatcher.js.map +1 -1
  61. package/dist/rules/index.js +1 -1
  62. package/dist/rules/processor.d.ts.map +1 -1
  63. package/dist/rules/processor.js +2 -1
  64. package/dist/rules/processor.js.map +1 -1
  65. package/dist/rules/start-coordinator.d.ts +3 -16
  66. package/dist/rules/start-coordinator.d.ts.map +1 -1
  67. package/dist/rules/start-coordinator.js +12 -43
  68. package/dist/rules/start-coordinator.js.map +1 -1
  69. package/dist/rules/transition-service.d.ts.map +1 -1
  70. package/dist/rules/transition-service.js +14 -8
  71. package/dist/rules/transition-service.js.map +1 -1
  72. package/dist/rules/types.d.ts +9 -0
  73. package/dist/rules/types.d.ts.map +1 -1
  74. package/dist/rules/types.js +9 -1
  75. package/dist/rules/types.js.map +1 -1
  76. package/dist/storage/domains/work-items/base.d.ts +8 -18
  77. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  78. package/dist/storage/domains/work-items/base.js +52 -37
  79. package/dist/storage/domains/work-items/base.js.map +1 -1
  80. package/dist/supervisor/health.d.ts.map +1 -1
  81. package/dist/supervisor/health.js +1 -2
  82. package/dist/supervisor/health.js.map +1 -1
  83. package/dist/work-item-branch.d.ts +6 -0
  84. package/dist/work-item-branch.d.ts.map +1 -1
  85. package/dist/work-item-branch.js +15 -9
  86. package/dist/work-item-branch.js.map +1 -1
  87. package/dist/workspace.js +1 -1
  88. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"attention-providers.d.ts","sourceRoot":"","sources":["../../src/routes/attention-providers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"attention-providers.d.ts","sourceRoot":"","sources":["../../src/routes/attention-providers.ts"],"names":[],"mappings":"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"}
@@ -64,9 +64,6 @@ async function markScanRead(batches, markBatch) {
64
64
  function factoryDecisionType(decision) {
65
65
  return typeof decision.decision.type === "string" ? decision.decision.type.slice(0, 64) : "unknown";
66
66
  }
67
- function failureOccurredAt(decision) {
68
- return decision.completedAt ?? decision.updatedAt;
69
- }
70
67
  function matchesView(view, receipt) {
71
68
  if (view === "archived") return receipt?.state === "archived";
72
69
  if (view === "unread") return receipt === void 0;
@@ -90,18 +87,21 @@ function attentionTarget(decision, item) {
90
87
  function workItemBoard(item) {
91
88
  return item.externalSource?.integrationId === "github" && item.externalSource.type === "pull-request" ? "review" : "work";
92
89
  }
93
- var AutomationFailedAttentionProvider = class {
94
- kind = "automation-failed";
90
+ var DecisionAttentionProvider = class {
91
+ kind;
95
92
  #workItems;
96
- constructor({ workItems }) {
93
+ #spec;
94
+ constructor({ workItems }, spec) {
97
95
  this.#workItems = workItems;
96
+ this.#spec = spec;
97
+ this.kind = spec.kind;
98
98
  }
99
99
  async counts(scope) {
100
- const [failedCount, receiptCount, archivedCount] = await Promise.all([
100
+ const [total, receiptCount, archivedCount] = await Promise.all([
101
101
  this.#workItems.countDeferredDecisionsByStatuses({
102
102
  orgId: scope.orgId,
103
103
  factoryProjectId: scope.factoryProjectId,
104
- statuses: ["failed"]
104
+ statuses: [this.#spec.status]
105
105
  }),
106
106
  this.#workItems.countAttentionReceipts({
107
107
  ...receiptScope(scope),
@@ -114,33 +114,35 @@ var AutomationFailedAttentionProvider = class {
114
114
  })
115
115
  ]);
116
116
  return {
117
- open: Math.max(0, failedCount - archivedCount),
118
- unread: Math.max(0, failedCount - receiptCount)
117
+ open: Math.max(0, total - archivedCount),
118
+ unread: Math.max(0, total - receiptCount)
119
119
  };
120
120
  }
121
121
  async latest(scope) {
122
- const newest = (await this.#workItems.listFailedDecisionPage({
122
+ const newest = (await this.#workItems.listDecisionPageByStatus({
123
123
  orgId: scope.orgId,
124
124
  factoryProjectId: scope.factoryProjectId,
125
+ status: this.#spec.status,
125
126
  limit: 1
126
127
  })).decisions[0];
127
128
  if (!newest) return null;
128
- const identity = factoryDecisionAttentionIdentity(newest.id, newest.failureOccurrence);
129
+ const identity = this.#spec.identity(newest);
129
130
  const receipts = await this.#workItems.listAttentionReceipts({
130
131
  ...receiptScope(scope),
131
132
  identities: [identity]
132
133
  });
133
134
  return {
134
135
  key: factoryAttentionKey(scope.factoryProjectId, identity),
135
- at: failureOccurredAt(newest),
136
+ at: this.#spec.occurredAt(newest),
136
137
  unread: receipts.length === 0
137
138
  };
138
139
  }
139
140
  #scan(scope, before) {
140
141
  return scanBatches(async (cursor) => {
141
- const page = await this.#workItems.listFailedDecisionPage({
142
+ const page = await this.#workItems.listDecisionPageByStatus({
142
143
  orgId: scope.orgId,
143
144
  factoryProjectId: scope.factoryProjectId,
145
+ status: this.#spec.status,
144
146
  before: cursor,
145
147
  limit: 50
146
148
  });
@@ -149,7 +151,7 @@ var AutomationFailedAttentionProvider = class {
149
151
  hasMore: page.hasMore
150
152
  };
151
153
  }, (decision) => ({
152
- occurredAt: failureOccurredAt(decision),
154
+ occurredAt: this.#spec.occurredAt(decision),
153
155
  id: decision.id
154
156
  }), before);
155
157
  }
@@ -158,18 +160,33 @@ var AutomationFailedAttentionProvider = class {
158
160
  const [receiptByKey, itemById] = await Promise.all([this.#receiptsFor(scope, decisions), this.#linkedItems(scope, decisions)]);
159
161
  const entries = [];
160
162
  for (const decision of decisions) {
161
- const identity = factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);
163
+ const identity = this.#spec.identity(decision);
162
164
  const receipt = receiptByKey.get(factoryAttentionKey(scope.factoryProjectId, identity));
163
165
  if (!matchesView(view, receipt)) continue;
164
166
  const item = decision.workItemId ? itemById.get(decision.workItemId) : void 0;
165
- if (search && !matchesFailureSearch(decision, item, search)) continue;
167
+ if (search && !this.#spec.matches(decision, item, search)) continue;
168
+ const occurredAt = this.#spec.occurredAt(decision);
166
169
  entries.push({
167
- occurredAt: failureOccurredAt(decision),
170
+ occurredAt,
168
171
  resumeCursor: {
169
- occurredAt: failureOccurredAt(decision),
172
+ occurredAt,
170
173
  id: decision.id
171
174
  },
172
- item: toFailureItem(scope, decision, item, receipt)
175
+ item: {
176
+ key: factoryAttentionKey(scope.factoryProjectId, identity),
177
+ kind: this.kind,
178
+ decisionId: decision.id,
179
+ occurrence: identity.occurrence,
180
+ workItemId: decision.workItemId,
181
+ title: this.#spec.title(decision, item),
182
+ detail: this.#spec.detail(decision, item),
183
+ decisionType: factoryDecisionType(decision),
184
+ ...this.#spec.extra?.(decision),
185
+ occurredAt: occurredAt.toISOString(),
186
+ read: receipt !== void 0,
187
+ archived: receipt?.state === "archived",
188
+ target: attentionTarget(decision, item)
189
+ }
173
190
  });
174
191
  }
175
192
  return entries;
@@ -178,7 +195,7 @@ var AutomationFailedAttentionProvider = class {
178
195
  async #receiptsFor(scope, decisions) {
179
196
  const receipts = await this.#workItems.listAttentionReceipts({
180
197
  ...receiptScope(scope),
181
- identities: decisions.map((decision) => factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence))
198
+ identities: decisions.map((decision) => this.#spec.identity(decision))
182
199
  });
183
200
  return new Map(receipts.map((receipt) => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]));
184
201
  }
@@ -194,12 +211,25 @@ var AutomationFailedAttentionProvider = class {
194
211
  return markScanRead(this.#scan(scope, before), async (decisions) => {
195
212
  await this.#workItems.markAttentionReceiptsRead({
196
213
  ...receiptScope(scope),
197
- identities: decisions.map((decision) => factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence)),
214
+ identities: decisions.map((decision) => this.#spec.identity(decision)),
198
215
  now
199
216
  });
200
217
  });
201
218
  }
202
219
  };
220
+ const failedDecisionAttentionSpec = {
221
+ kind: "automation-failed",
222
+ status: "failed",
223
+ identity: (decision) => factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence),
224
+ occurredAt: (decision) => decision.completedAt ?? decision.updatedAt,
225
+ title: (decision, item) => item?.title ?? factoryDispatchFailureMetadata(decision.failureCode).label,
226
+ detail: (decision) => decision.lastError?.slice(0, 512) ?? factoryDispatchFailureMetadata(decision.failureCode).label,
227
+ extra: (decision) => ({
228
+ failureCode: decision.failureCode,
229
+ canRetry: factoryDispatchFailureMetadata(decision.failureCode).canRetry
230
+ }),
231
+ matches: (decision, item, search) => item?.title.toLowerCase().includes(search) === true || decision.lastError?.toLowerCase().includes(search) === true || factoryDecisionType(decision).toLowerCase().includes(search)
232
+ };
203
233
  function supervisorFindingPayload(row) {
204
234
  return row.finding;
205
235
  }
@@ -448,29 +478,6 @@ var MentionAttentionProvider = class {
448
478
  });
449
479
  }
450
480
  };
451
- function matchesFailureSearch(decision, item, search) {
452
- return item?.title.toLowerCase().includes(search) === true || decision.lastError?.toLowerCase().includes(search) === true || factoryDecisionType(decision).toLowerCase().includes(search);
453
- }
454
- function toFailureItem(scope, decision, item, receipt) {
455
- const failure = factoryDispatchFailureMetadata(decision.failureCode);
456
- const identity = factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);
457
- return {
458
- key: factoryAttentionKey(scope.factoryProjectId, identity),
459
- kind: "automation-failed",
460
- decisionId: decision.id,
461
- occurrence: decision.failureOccurrence,
462
- workItemId: decision.workItemId,
463
- title: item?.title ?? failure.label,
464
- detail: decision.lastError?.slice(0, 512) ?? failure.label,
465
- decisionType: factoryDecisionType(decision),
466
- failureCode: decision.failureCode,
467
- canRetry: failure.canRetry,
468
- occurredAt: failureOccurredAt(decision).toISOString(),
469
- read: receipt !== void 0,
470
- archived: receipt?.state === "archived",
471
- target: attentionTarget(decision, item)
472
- };
473
- }
474
481
  function matchesMentionSearch({ item, comment }, search) {
475
482
  return item.title.toLowerCase().includes(search) || comment.body.toLowerCase().includes(search) || comment.author.displayName?.toLowerCase().includes(search) === true;
476
483
  }
@@ -505,6 +512,6 @@ function receiptScope(scope) {
505
512
  };
506
513
  }
507
514
  //#endregion
508
- export { AutomationFailedAttentionProvider, MentionAttentionProvider, SCAN_PAGE_SIZE, SupervisorFindingAttentionProvider, collectPage, factoryDecisionType, markScanRead, matchesView, receiptScope, scanBatches, workItemBoard };
515
+ export { DecisionAttentionProvider, MentionAttentionProvider, SCAN_PAGE_SIZE, SupervisorFindingAttentionProvider, collectPage, factoryDecisionType, failedDecisionAttentionSpec, markScanRead, matchesView, receiptScope, scanBatches, workItemBoard };
509
516
 
510
517
  //# sourceMappingURL=attention-providers.js.map
@@ -1 +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 FactorySupervisorFindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport {\n factoryAttentionKey,\n factoryDecisionAttentionIdentity,\n factoryMentionAttentionIdentity,\n factorySupervisorFindingAttentionIdentity,\n} from '../storage/domains/work-items/base.js';\nimport type { FactoryHealthFinding } from '../supervisor/health.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\nexport const 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 */\nexport async 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. */\nexport async 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. */\nexport async 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\nexport function 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\nexport function 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\nfunction supervisorFindingPayload(row: FactorySupervisorFindingRecord): FactoryHealthFinding {\n return row.finding as unknown as FactoryHealthFinding;\n}\n\nexport class SupervisorFindingAttentionProvider implements AttentionProvider {\n readonly kind = 'supervisor-finding' 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 let open = 0;\n let unread = 0;\n const batches = scanBatches<FactorySupervisorFindingRecord>(\n before =>\n this.#workItems.listSupervisorFindingPage({ ...scope, ...(before ? { before } : {}), limit: SCAN_PAGE_SIZE }),\n row => ({ occurredAt: row.updatedAt, id: row.id }),\n );\n for await (const page of batches) {\n const identities = page.rows.map(row =>\n factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence),\n );\n const receipts = await this.#workItems.listAttentionReceipts({ ...scope, identities });\n const byIdentity = new Map(\n receipts.map(receipt => [`${receipt.sourceId}\\0${receipt.occurrence}`, receipt] as const),\n );\n for (const row of page.rows) {\n const receipt = byIdentity.get(`${row.findingKey}\\0${row.occurrence}`);\n if (receipt?.state === 'archived') continue;\n open += 1;\n if (!receipt) unread += 1;\n }\n }\n return { open, unread };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const page = await this.#workItems.listSupervisorFindingPage({ ...scope, limit: 1 });\n const newest = page.rows[0];\n if (!newest) return null;\n const identity = factorySupervisorFindingAttentionIdentity(newest.findingKey, newest.occurrence);\n const receipts = await this.#workItems.listAttentionReceipts({ ...scope, identities: [identity] });\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n at: newest.updatedAt,\n unread: receipts.length === 0,\n };\n }\n\n page(scope: AttentionScope, args: AttentionPageArgs): Promise<AttentionPageResult> {\n return collectPage(\n scanBatches<FactorySupervisorFindingRecord>(\n before =>\n this.#workItems.listSupervisorFindingPage({ ...scope, ...(before ? { before } : {}), limit: SCAN_PAGE_SIZE }),\n row => ({ occurredAt: row.updatedAt, id: row.id }),\n args.before,\n ),\n args.limit,\n async rows => {\n const identities = rows.map(row => factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence));\n const receipts = await this.#workItems.listAttentionReceipts({ ...scope, identities });\n const byIdentity = new Map(\n receipts.map(receipt => [`${receipt.sourceId}\\0${receipt.occurrence}`, receipt] as const),\n );\n return rows.flatMap(row => {\n const finding = supervisorFindingPayload(row);\n const receipt = byIdentity.get(`${row.findingKey}\\0${row.occurrence}`);\n if (!matchesView(args.view, receipt)) return [];\n const text = `${finding.title} ${finding.evidence}`.toLowerCase();\n if (args.search && !text.includes(args.search)) return [];\n const identity = factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence);\n return [\n {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n occurredAt: row.updatedAt,\n resumeCursor: { occurredAt: row.updatedAt, id: row.id },\n receipt,\n item: {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n kind: this.kind,\n findingKey: row.findingKey,\n occurrence: row.occurrence,\n findingTitle: finding.title,\n evidence: finding.evidence,\n title: finding.title,\n detail: finding.evidence,\n ageMs: finding.ageMs,\n suggestedRepair: finding.suggestedRepair,\n workItemId: finding.workItemId,\n occurredAt: row.updatedAt.toISOString(),\n read: Boolean(receipt),\n archived: receipt?.state === 'archived',\n target: finding.workItemId\n ? { kind: 'work-item', workItemId: finding.workItemId, board: 'work' }\n : { kind: 'rules' },\n },\n },\n ];\n });\n },\n );\n }\n\n markAllRead(\n scope: AttentionScope,\n args: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n return markScanRead(\n scanBatches<FactorySupervisorFindingRecord>(\n before =>\n this.#workItems.listSupervisorFindingPage({ ...scope, ...(before ? { before } : {}), limit: SCAN_PAGE_SIZE }),\n row => ({ occurredAt: row.updatedAt, id: row.id }),\n args.before,\n ),\n rows =>\n this.#workItems.markAttentionReceiptsRead({\n ...scope,\n identities: rows.map(row => factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence)),\n now: args.now,\n }),\n );\n }\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\nexport function 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
+ {"version":3,"file":"attention-providers.js","names":["#workItems","#spec","#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 FactoryDispatchStatus,\n FactorySupervisorFindingRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport {\n factoryAttentionKey,\n factoryDecisionAttentionIdentity,\n factoryMentionAttentionIdentity,\n factorySupervisorFindingAttentionIdentity,\n} from '../storage/domains/work-items/base.js';\nimport type { FactoryHealthFinding } from '../supervisor/health.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\nexport const 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 */\nexport async 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. */\nexport async 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. */\nexport async 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\nexport function 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\nexport function workItemBoard(item: WorkItemRow): 'review' | 'work' {\n const review = item.externalSource?.integrationId === 'github' && item.externalSource.type === 'pull-request';\n return review ? 'review' : 'work';\n}\n\n/** One deferred-decision status seen as attention: failures and parked runs differ only by this. */\nexport interface DecisionAttentionSpec {\n kind: FactoryAttentionKind;\n status: FactoryDispatchStatus;\n identity(decision: FactoryDeferredDecisionRecord): FactoryAttentionIdentity;\n occurredAt(decision: FactoryDeferredDecisionRecord): Date;\n title(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined): string;\n detail(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined): string;\n extra?(decision: FactoryDeferredDecisionRecord): Record<string, unknown>;\n matches(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined, search: string): boolean;\n}\n\nexport class DecisionAttentionProvider implements AttentionProvider {\n readonly kind: FactoryAttentionKind;\n readonly #workItems: WorkItemsStorage;\n readonly #spec: DecisionAttentionSpec;\n\n constructor({ workItems }: { workItems: WorkItemsStorage }, spec: DecisionAttentionSpec) {\n this.#workItems = workItems;\n this.#spec = spec;\n this.kind = spec.kind;\n }\n\n async counts(scope: AttentionScope): Promise<AttentionCounts> {\n const [total, receiptCount, archivedCount] = await Promise.all([\n this.#workItems.countDeferredDecisionsByStatuses({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n statuses: [this.#spec.status],\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, total - archivedCount),\n unread: Math.max(0, total - receiptCount),\n };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const page = await this.#workItems.listDecisionPageByStatus({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n status: this.#spec.status,\n limit: 1,\n });\n const newest = page.decisions[0];\n if (!newest) return null;\n const identity = this.#spec.identity(newest);\n const receipts = await this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: [identity],\n });\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n at: this.#spec.occurredAt(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.listDecisionPageByStatus({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n status: this.#spec.status,\n before: cursor,\n limit: SCAN_PAGE_SIZE,\n });\n return { rows: page.decisions, hasMore: page.hasMore };\n },\n decision => ({ occurredAt: this.#spec.occurredAt(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 = this.#spec.identity(decision);\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 && !this.#spec.matches(decision, item, search)) continue;\n const occurredAt = this.#spec.occurredAt(decision);\n entries.push({\n occurredAt,\n resumeCursor: { occurredAt, id: decision.id },\n item: {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n kind: this.kind,\n decisionId: decision.id,\n occurrence: identity.occurrence,\n workItemId: decision.workItemId,\n title: this.#spec.title(decision, item),\n detail: this.#spec.detail(decision, item),\n decisionType: factoryDecisionType(decision),\n ...this.#spec.extra?.(decision),\n occurredAt: occurredAt.toISOString(),\n read: receipt !== undefined,\n archived: receipt?.state === 'archived',\n target: attentionTarget(decision, item),\n },\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 => this.#spec.identity(decision)),\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 => this.#spec.identity(decision)),\n now,\n });\n });\n }\n}\n\nexport const failedDecisionAttentionSpec: DecisionAttentionSpec = {\n kind: 'automation-failed',\n status: 'failed',\n identity: decision => factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence),\n occurredAt: decision => decision.completedAt ?? decision.updatedAt,\n title: (decision, item) => item?.title ?? factoryDispatchFailureMetadata(decision.failureCode).label,\n detail: decision => decision.lastError?.slice(0, 512) ?? factoryDispatchFailureMetadata(decision.failureCode).label,\n extra: decision => ({\n failureCode: decision.failureCode,\n canRetry: factoryDispatchFailureMetadata(decision.failureCode).canRetry,\n }),\n matches: (decision, item, search) =>\n item?.title.toLowerCase().includes(search) === true ||\n decision.lastError?.toLowerCase().includes(search) === true ||\n factoryDecisionType(decision).toLowerCase().includes(search),\n};\n\ninterface ResolvedMention {\n mention: WorkItemMentionRow;\n comment: WorkItemCommentRow;\n item: WorkItemRow;\n identity: FactoryAttentionIdentity;\n receipt: FactoryAttentionReceiptRecord | undefined;\n}\n\nfunction supervisorFindingPayload(row: FactorySupervisorFindingRecord): FactoryHealthFinding {\n return row.finding as unknown as FactoryHealthFinding;\n}\n\nexport class SupervisorFindingAttentionProvider implements AttentionProvider {\n readonly kind = 'supervisor-finding' 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 let open = 0;\n let unread = 0;\n const batches = scanBatches<FactorySupervisorFindingRecord>(\n before =>\n this.#workItems.listSupervisorFindingPage({ ...scope, ...(before ? { before } : {}), limit: SCAN_PAGE_SIZE }),\n row => ({ occurredAt: row.updatedAt, id: row.id }),\n );\n for await (const page of batches) {\n const identities = page.rows.map(row =>\n factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence),\n );\n const receipts = await this.#workItems.listAttentionReceipts({ ...scope, identities });\n const byIdentity = new Map(\n receipts.map(receipt => [`${receipt.sourceId}\\0${receipt.occurrence}`, receipt] as const),\n );\n for (const row of page.rows) {\n const receipt = byIdentity.get(`${row.findingKey}\\0${row.occurrence}`);\n if (receipt?.state === 'archived') continue;\n open += 1;\n if (!receipt) unread += 1;\n }\n }\n return { open, unread };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const page = await this.#workItems.listSupervisorFindingPage({ ...scope, limit: 1 });\n const newest = page.rows[0];\n if (!newest) return null;\n const identity = factorySupervisorFindingAttentionIdentity(newest.findingKey, newest.occurrence);\n const receipts = await this.#workItems.listAttentionReceipts({ ...scope, identities: [identity] });\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n at: newest.updatedAt,\n unread: receipts.length === 0,\n };\n }\n\n page(scope: AttentionScope, args: AttentionPageArgs): Promise<AttentionPageResult> {\n return collectPage(\n scanBatches<FactorySupervisorFindingRecord>(\n before =>\n this.#workItems.listSupervisorFindingPage({ ...scope, ...(before ? { before } : {}), limit: SCAN_PAGE_SIZE }),\n row => ({ occurredAt: row.updatedAt, id: row.id }),\n args.before,\n ),\n args.limit,\n async rows => {\n const identities = rows.map(row => factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence));\n const receipts = await this.#workItems.listAttentionReceipts({ ...scope, identities });\n const byIdentity = new Map(\n receipts.map(receipt => [`${receipt.sourceId}\\0${receipt.occurrence}`, receipt] as const),\n );\n return rows.flatMap(row => {\n const finding = supervisorFindingPayload(row);\n const receipt = byIdentity.get(`${row.findingKey}\\0${row.occurrence}`);\n if (!matchesView(args.view, receipt)) return [];\n const text = `${finding.title} ${finding.evidence}`.toLowerCase();\n if (args.search && !text.includes(args.search)) return [];\n const identity = factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence);\n return [\n {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n occurredAt: row.updatedAt,\n resumeCursor: { occurredAt: row.updatedAt, id: row.id },\n receipt,\n item: {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n kind: this.kind,\n findingKey: row.findingKey,\n occurrence: row.occurrence,\n findingTitle: finding.title,\n evidence: finding.evidence,\n title: finding.title,\n detail: finding.evidence,\n ageMs: finding.ageMs,\n suggestedRepair: finding.suggestedRepair,\n workItemId: finding.workItemId,\n occurredAt: row.updatedAt.toISOString(),\n read: Boolean(receipt),\n archived: receipt?.state === 'archived',\n target: finding.workItemId\n ? { kind: 'work-item', workItemId: finding.workItemId, board: 'work' }\n : { kind: 'rules' },\n },\n },\n ];\n });\n },\n );\n }\n\n markAllRead(\n scope: AttentionScope,\n args: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n return markScanRead(\n scanBatches<FactorySupervisorFindingRecord>(\n before =>\n this.#workItems.listSupervisorFindingPage({ ...scope, ...(before ? { before } : {}), limit: SCAN_PAGE_SIZE }),\n row => ({ occurredAt: row.updatedAt, id: row.id }),\n args.before,\n ),\n rows =>\n this.#workItems.markAttentionReceiptsRead({\n ...scope,\n identities: rows.map(row => factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence)),\n now: args.now,\n }),\n );\n }\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 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\nexport function 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 +1 @@
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;AAG/C,OAAO,KAAK,EAIV,cAAc,EAGf,MAAM,0BAA0B,CAAC;AAOlC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAO/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;AAqMD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,0BAA0B,GAAG,QAAQ,EAAE,CA+HzF"}
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;AAI/C,OAAO,KAAK,EAIV,cAAc,EAGf,MAAM,0BAA0B,CAAC;AAQlC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAO/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;AAgND,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,0BAA0B,GAAG,QAAQ,EAAE,CA0HzF"}
@@ -1,6 +1,7 @@
1
1
  import { factoryAttentionKey } from "../storage/domains/work-items/base.js";
2
- import { AutomationFailedAttentionProvider, MentionAttentionProvider, SupervisorFindingAttentionProvider, factoryDecisionType } from "./attention-providers.js";
2
+ import { DecisionAttentionProvider, MentionAttentionProvider, SupervisorFindingAttentionProvider, factoryDecisionType, failedDecisionAttentionSpec } from "./attention-providers.js";
3
3
  import { ActivityAttentionProvider } from "./attention-activity.js";
4
+ import { proposedDecisionAttentionSpec } from "./attention-proposed.js";
4
5
  import { registerApiRoute } from "@mastra/core/server";
5
6
  //#region src/routes/attention.ts
6
7
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -17,11 +18,12 @@ function parseAttentionLimit(raw) {
17
18
  return Math.max(1, Math.min(MAX_PAGE_SIZE, parsed));
18
19
  }
19
20
  function isAttentionKind(value) {
20
- return value === "automation-failed" || value === "mention" || value === "activity" || value === "supervisor-finding";
21
+ return value === "automation-failed" || value === "automation-proposed" || value === "mention" || value === "activity" || value === "supervisor-finding";
21
22
  }
22
23
  /** Kinds the sidebar badge and the notification sound answer to. */
23
24
  const BADGE_KINDS = /* @__PURE__ */ new Set([
24
25
  "automation-failed",
26
+ "automation-proposed",
25
27
  "mention",
26
28
  "supervisor-finding"
27
29
  ]);
@@ -171,7 +173,8 @@ function receiptRoute(dependencies, verb, action) {
171
173
  function buildAttentionRoutes(dependencies) {
172
174
  const { workItems, comments } = dependencies;
173
175
  const providers = [
174
- new AutomationFailedAttentionProvider({ workItems }),
176
+ new DecisionAttentionProvider({ workItems }, failedDecisionAttentionSpec),
177
+ new DecisionAttentionProvider({ workItems }, proposedDecisionAttentionSpec),
175
178
  new SupervisorFindingAttentionProvider({ workItems }),
176
179
  new MentionAttentionProvider({
177
180
  workItems,
@@ -201,32 +204,24 @@ function buildAttentionRoutes(dependencies) {
201
204
  const search = context.req.query("search")?.trim().toLowerCase().slice(0, 200);
202
205
  const limit = parseAttentionLimit(context.req.query("limit"));
203
206
  const active = providers.filter((provider) => kindInTier(tier, provider.kind) && (!before || before.has(provider.kind)));
204
- const [approvalCount, summaries, pages] = await Promise.all([
205
- workItems.countDeferredDecisionsByStatuses({
206
- orgId: resolved.orgId,
207
- factoryProjectId: resolved.factoryProjectId,
208
- statuses: ["proposed"]
209
- }),
210
- Promise.all(providers.map(async (provider) => ({
211
- kind: provider.kind,
212
- counts: await provider.counts(resolved),
213
- latest: await provider.latest(resolved)
214
- }))),
215
- Promise.all(active.map(async (provider) => ({
216
- kind: provider.kind,
217
- incoming: before?.get(provider.kind),
218
- result: await provider.page(resolved, {
219
- view,
220
- ...search ? { search } : {},
221
- before: before?.get(provider.kind),
222
- limit
223
- })
224
- })))
225
- ]);
207
+ const [summaries, pages] = await Promise.all([Promise.all(providers.map(async (provider) => ({
208
+ kind: provider.kind,
209
+ counts: await provider.counts(resolved),
210
+ latest: await provider.latest(resolved)
211
+ }))), Promise.all(active.map(async (provider) => ({
212
+ kind: provider.kind,
213
+ incoming: before?.get(provider.kind),
214
+ result: await provider.page(resolved, {
215
+ view,
216
+ ...search ? { search } : {},
217
+ before: before?.get(provider.kind),
218
+ limit
219
+ })
220
+ })))]);
226
221
  const badge = summaries.filter((summary) => BADGE_KINDS.has(summary.kind));
227
222
  const activity = summaries.filter((summary) => !BADGE_KINDS.has(summary.kind));
228
223
  const sum = (rows, field) => rows.reduce((total, row) => total + row.counts[field], 0);
229
- const openCount = sum(badge, "open") + approvalCount;
224
+ const openCount = sum(badge, "open");
230
225
  const unreadCount = sum(badge, "unread");
231
226
  const latests = badge.map((summary) => summary.latest);
232
227
  const unreadLatests = latests.filter((latest) => latest?.unread ?? false);
@@ -235,8 +230,7 @@ function buildAttentionRoutes(dependencies) {
235
230
  return context.json({
236
231
  items: merged.items,
237
232
  openCount,
238
- approvalCount,
239
- badgeCount: unreadCount + approvalCount,
233
+ badgeCount: unreadCount,
240
234
  unreadCount,
241
235
  activityUnreadCount: sum(activity, "unread"),
242
236
  latestOccurrenceKey: latest?.key ?? null,
@@ -1 +1 @@
1
- {"version":3,"file":"attention.js","names":[],"sources":["../../src/routes/attention.ts"],"sourcesContent":["/**\n * Attention routes: k-way merge of the per-kind providers on `occurredAt desc`.\n * The wire cursor is a per-kind map — each kind's stream resumes independently,\n * `null` meaning \"not started yet\", an absent kind meaning \"exhausted\".\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\n\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type {\n FactoryAttentionKind,\n FactoryAttentionReceiptAction,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { factoryAttentionKey } from '../storage/domains/work-items/base.js';\nimport { ActivityAttentionProvider } from './attention-activity.js';\nimport type {\n AttentionLatest,\n AttentionPageResult,\n AttentionProvider,\n AttentionScope,\n AttentionStreamPosition,\n FactoryAttentionView,\n} from './attention-providers.js';\nimport {\n AutomationFailedAttentionProvider,\n MentionAttentionProvider,\n SupervisorFindingAttentionProvider,\n} from './attention-providers.js';\n\nexport { factoryDecisionType } from './attention-providers.js';\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst SUPERVISOR_FINDING_KEY_RE = /^[a-z0-9:_-]{1,256}$/i;\nconst DEFAULT_PAGE_SIZE = 25;\nconst MAX_PAGE_SIZE = 50;\n\ninterface AttentionRouteDependencies {\n workItems: WorkItemsStorage;\n comments: WorkItemCommentsStorage;\n resolveProject(context: unknown): Promise<AttentionScope | { response: Response }>;\n}\n\ntype AttentionCursorMap = Map<FactoryAttentionKind, AttentionStreamPosition | undefined>;\n\nfunction parseAttentionView(raw: string | undefined): FactoryAttentionView | undefined {\n if (!raw || raw === 'open') return 'open';\n if (raw === 'unread' || raw === 'archived') return raw;\n return undefined;\n}\n\nfunction parseAttentionLimit(raw: string | undefined): number {\n const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_PAGE_SIZE;\n if (!Number.isFinite(parsed)) return DEFAULT_PAGE_SIZE;\n return Math.max(1, Math.min(MAX_PAGE_SIZE, parsed));\n}\n\nfunction isAttentionKind(value: string): value is FactoryAttentionKind {\n return value === 'automation-failed' || value === 'mention' || value === 'activity' || value === 'supervisor-finding';\n}\n\n/** Kinds the sidebar badge and the notification sound answer to. */\nconst BADGE_KINDS: ReadonlySet<FactoryAttentionKind> = new Set(['automation-failed', 'mention', 'supervisor-finding']);\n\ntype AttentionTier = 'all' | 'badge' | 'activity';\n\nfunction parseAttentionTier(raw: string | undefined): AttentionTier | undefined {\n if (raw === undefined) return 'all';\n return raw === 'badge' || raw === 'activity' ? raw : undefined;\n}\n\nfunction kindInTier(tier: AttentionTier, kind: FactoryAttentionKind): boolean {\n if (tier === 'all') return true;\n return tier === 'badge' ? BADGE_KINDS.has(kind) : !BADGE_KINDS.has(kind);\n}\n\nfunction encodeAttentionCursor(cursors: AttentionCursorMap): string {\n const wire: Record<string, [string, string] | null> = {};\n for (const [kind, position] of cursors) {\n wire[kind] = position ? [position.occurredAt.toISOString(), position.id] : null;\n }\n return Buffer.from(JSON.stringify(wire), 'utf8').toString('base64url');\n}\n\nfunction parseStreamPosition(value: unknown): AttentionStreamPosition | undefined {\n if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== 'string' || typeof value[1] !== 'string') {\n return undefined;\n }\n const occurredAt = new Date(value[0]);\n if (Number.isNaN(occurredAt.getTime()) || !UUID_RE.test(value[1])) return undefined;\n return { occurredAt, id: value[1] };\n}\n\nfunction parseAttentionCursor(raw: string | undefined): AttentionCursorMap | undefined {\n if (!raw) return undefined;\n try {\n const decoded: unknown = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8'));\n // Cursors minted before the inbox merged kinds are a bare position over the\n // only stream there was. Held by anyone mid-list when this deploys, so they\n // resume that stream rather than 400: mentions arrive on their next load.\n if (Array.isArray(decoded)) {\n const legacy = parseStreamPosition(decoded);\n return legacy ? new Map([['automation-failed', legacy]]) : undefined;\n }\n if (!decoded || typeof decoded !== 'object') return undefined;\n const cursors: AttentionCursorMap = new Map();\n for (const [kind, value] of Object.entries(decoded)) {\n if (!isAttentionKind(kind)) return undefined;\n if (value === null) {\n cursors.set(kind, undefined);\n continue;\n }\n const position = parseStreamPosition(value);\n if (!position) return undefined;\n cursors.set(kind, position);\n }\n return cursors.size > 0 ? cursors : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction parseOccurrence(raw: string | undefined): number | undefined {\n if (!raw || !/^(0|[1-9]\\d*)$/.test(raw)) return undefined;\n const occurrence = Number(raw);\n return Number.isSafeInteger(occurrence) ? occurrence : undefined;\n}\n\ninterface MergedAttentionPage {\n items: Array<Record<string, unknown>>;\n hasMore: boolean;\n nextCursor?: string;\n}\n\n/**\n * Take the newest `limit` entries across provider pages. Each kind's next\n * cursor is the resume position of its last consumed entry; a kind consumed to\n * the end inherits the provider's own continuation.\n */\nfunction mergeAttentionPages(\n pages: Array<{\n kind: FactoryAttentionKind;\n incoming: AttentionStreamPosition | undefined;\n result: AttentionPageResult;\n }>,\n limit: number,\n): MergedAttentionPage {\n const consumed = new Map(pages.map(page => [page.kind, 0]));\n const items: Array<Record<string, unknown>> = [];\n while (items.length < limit) {\n let best: { kind: FactoryAttentionKind; at: number } | undefined;\n for (const page of pages) {\n const next = page.result.entries[consumed.get(page.kind) ?? 0];\n if (!next) continue;\n const at = next.occurredAt.getTime();\n if (!best || at > best.at) best = { kind: page.kind, at };\n }\n if (!best) break;\n const index = consumed.get(best.kind) ?? 0;\n const entry = pages.find(page => page.kind === best.kind)?.result.entries[index];\n if (!entry) break;\n items.push(entry.item);\n consumed.set(best.kind, index + 1);\n }\n\n const nextCursors: AttentionCursorMap = new Map();\n let hasMore = false;\n for (const page of pages) {\n const used = consumed.get(page.kind) ?? 0;\n const entries = page.result.entries;\n if (used < entries.length) {\n hasMore = true;\n const lastConsumed = used > 0 ? entries[used - 1] : undefined;\n nextCursors.set(page.kind, lastConsumed ? lastConsumed.resumeCursor : page.incoming);\n continue;\n }\n if (page.result.hasMore) {\n hasMore = true;\n nextCursors.set(page.kind, page.result.continuation ?? entries.at(-1)?.resumeCursor ?? page.incoming);\n }\n }\n return {\n items,\n hasMore,\n ...(hasMore && nextCursors.size > 0 ? { nextCursor: encodeAttentionCursor(nextCursors) } : {}),\n };\n}\n\nfunction newestLatest(latests: Array<AttentionLatest | null>): AttentionLatest | null {\n let newest: AttentionLatest | null = null;\n for (const latest of latests) {\n if (!latest) continue;\n if (!newest || latest.at.getTime() > newest.at.getTime()) newest = latest;\n }\n return newest;\n}\n\nfunction receiptRoute(\n dependencies: AttentionRouteDependencies,\n verb: 'read' | 'archive' | 'restore',\n action: FactoryAttentionReceiptAction,\n): ApiRoute {\n return registerApiRoute(`/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/${verb}`, {\n method: 'POST',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const kind = context.req.param('kind');\n const sourceId = context.req.param('sourceId');\n const occurrence = parseOccurrence(context.req.param('occurrence'));\n const validSourceId =\n kind === 'supervisor-finding' ? SUPERVISOR_FINDING_KEY_RE.test(sourceId) : UUID_RE.test(sourceId);\n if (!kind || !isAttentionKind(kind) || !sourceId || !validSourceId || occurrence === undefined) {\n return context.json({ error: 'invalid_attention_item' }, 422);\n }\n await dependencies.workItems.ensureReady();\n const receipt = await dependencies.workItems.setAttentionReceipt({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n identity: { kind, sourceId, occurrence },\n action,\n now: new Date(),\n });\n if (!receipt) return context.json({ error: 'attention_item_not_current' }, 409);\n return context.json({\n receipt: {\n key: factoryAttentionKey(resolved.factoryProjectId, receipt),\n state: receipt.state,\n readAt: receipt.readAt.toISOString(),\n archivedAt: receipt.archivedAt?.toISOString() ?? null,\n },\n });\n },\n });\n}\n\nexport function buildAttentionRoutes(dependencies: AttentionRouteDependencies): ApiRoute[] {\n const { workItems, comments } = dependencies;\n const providers: AttentionProvider[] = [\n new AutomationFailedAttentionProvider({ workItems }),\n new SupervisorFindingAttentionProvider({ workItems }),\n new MentionAttentionProvider({ workItems, comments }),\n new ActivityAttentionProvider({ workItems, comments }),\n ];\n\n return [\n registerApiRoute('/web/factory/projects/:id/attention', {\n method: 'GET',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const view = parseAttentionView(context.req.query('view'));\n if (view === undefined) return context.json({ error: 'invalid_attention_view' }, 400);\n // `tier` scopes the item list only; the counts always describe every\n // tier, so the badge popover can page badge kinds without losing the\n // activity numbers.\n const tier = parseAttentionTier(context.req.query('tier'));\n if (tier === undefined) return context.json({ error: 'invalid_attention_tier' }, 400);\n const cursorRaw = context.req.query('before');\n const before = parseAttentionCursor(cursorRaw);\n if (cursorRaw && !before) return context.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n await comments.ensureReady();\n\n const search = context.req.query('search')?.trim().toLowerCase().slice(0, 200);\n const limit = parseAttentionLimit(context.req.query('limit'));\n const active = providers.filter(\n provider => kindInTier(tier, provider.kind) && (!before || before.has(provider.kind)),\n );\n\n const [approvalCount, summaries, pages] = await Promise.all([\n workItems.countDeferredDecisionsByStatuses({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n statuses: ['proposed'],\n }),\n Promise.all(\n providers.map(async provider => ({\n kind: provider.kind,\n counts: await provider.counts(resolved),\n latest: await provider.latest(resolved),\n })),\n ),\n Promise.all(\n active.map(async provider => ({\n kind: provider.kind,\n incoming: before?.get(provider.kind),\n result: await provider.page(resolved, {\n view,\n ...(search ? { search } : {}),\n before: before?.get(provider.kind),\n limit,\n }),\n })),\n ),\n ]);\n\n // The badge tier and the activity tier are counted apart: activity\n // leaking into `latests` would ring the notification sound on every\n // teammate comment.\n const badge = summaries.filter(summary => BADGE_KINDS.has(summary.kind));\n const activity = summaries.filter(summary => !BADGE_KINDS.has(summary.kind));\n const sum = (rows: typeof summaries, field: 'open' | 'unread') =>\n rows.reduce((total, row) => total + row.counts[field], 0);\n const openCount = sum(badge, 'open') + approvalCount;\n const unreadCount = sum(badge, 'unread');\n // An unread item must never be masked by a newer already-read one of\n // another kind — the streams are independent.\n const latests = badge.map(summary => summary.latest);\n const unreadLatests = latests.filter(latest => latest?.unread ?? false);\n const latest = unreadLatests.length > 0 ? newestLatest(unreadLatests) : newestLatest(latests);\n const merged = mergeAttentionPages(pages, limit);\n\n return context.json({\n items: merged.items,\n openCount,\n approvalCount,\n badgeCount: unreadCount + approvalCount,\n unreadCount,\n activityUnreadCount: sum(activity, 'unread'),\n latestOccurrenceKey: latest?.key ?? null,\n latestOccurrenceAt: latest?.at.toISOString() ?? null,\n latestOccurrenceUnread: latest?.unread ?? false,\n hasMore: merged.hasMore,\n ...(merged.nextCursor ? { nextCursor: merged.nextCursor } : {}),\n });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/attention/read-all', {\n method: 'POST',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const cursorRaw = context.req.query('before');\n const before = parseAttentionCursor(cursorRaw);\n if (cursorRaw && !before) return context.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n await comments.ensureReady();\n\n const now = new Date();\n const active = providers.filter(provider => !before || before.has(provider.kind));\n const nextCursors: AttentionCursorMap = new Map();\n let hasMore = false;\n for (const provider of active) {\n const result = await provider.markAllRead(resolved, { before: before?.get(provider.kind), now });\n if (result.hasMore) {\n hasMore = true;\n if (result.continuation) nextCursors.set(provider.kind, result.continuation);\n }\n }\n return context.json({\n ok: true,\n hasMore,\n ...(hasMore && nextCursors.size > 0 ? { nextCursor: encodeAttentionCursor(nextCursors) } : {}),\n });\n },\n }),\n receiptRoute(dependencies, 'read', 'read'),\n receiptRoute(dependencies, 'archive', 'archive'),\n receiptRoute(dependencies, 'restore', 'restore'),\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"attention.js","names":[],"sources":["../../src/routes/attention.ts"],"sourcesContent":["/**\n * Attention routes: k-way merge of the per-kind providers on `occurredAt desc`.\n * The wire cursor is a per-kind map — each kind's stream resumes independently,\n * `null` meaning \"not started yet\", an absent kind meaning \"exhausted\".\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\n\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type {\n FactoryAttentionKind,\n FactoryAttentionReceiptAction,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { factoryAttentionKey } from '../storage/domains/work-items/base.js';\nimport { ActivityAttentionProvider } from './attention-activity.js';\nimport { proposedDecisionAttentionSpec } from './attention-proposed.js';\nimport type {\n AttentionLatest,\n AttentionPageResult,\n AttentionProvider,\n AttentionScope,\n AttentionStreamPosition,\n FactoryAttentionView,\n} from './attention-providers.js';\nimport {\n DecisionAttentionProvider,\n failedDecisionAttentionSpec,\n MentionAttentionProvider,\n SupervisorFindingAttentionProvider,\n} from './attention-providers.js';\n\nexport { factoryDecisionType } from './attention-providers.js';\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst SUPERVISOR_FINDING_KEY_RE = /^[a-z0-9:_-]{1,256}$/i;\nconst DEFAULT_PAGE_SIZE = 25;\nconst MAX_PAGE_SIZE = 50;\n\ninterface AttentionRouteDependencies {\n workItems: WorkItemsStorage;\n comments: WorkItemCommentsStorage;\n resolveProject(context: unknown): Promise<AttentionScope | { response: Response }>;\n}\n\ntype AttentionCursorMap = Map<FactoryAttentionKind, AttentionStreamPosition | undefined>;\n\nfunction parseAttentionView(raw: string | undefined): FactoryAttentionView | undefined {\n if (!raw || raw === 'open') return 'open';\n if (raw === 'unread' || raw === 'archived') return raw;\n return undefined;\n}\n\nfunction parseAttentionLimit(raw: string | undefined): number {\n const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_PAGE_SIZE;\n if (!Number.isFinite(parsed)) return DEFAULT_PAGE_SIZE;\n return Math.max(1, Math.min(MAX_PAGE_SIZE, parsed));\n}\n\nfunction isAttentionKind(value: string): value is FactoryAttentionKind {\n return (\n value === 'automation-failed' ||\n value === 'automation-proposed' ||\n value === 'mention' ||\n value === 'activity' ||\n value === 'supervisor-finding'\n );\n}\n\n/** Kinds the sidebar badge and the notification sound answer to. */\nconst BADGE_KINDS: ReadonlySet<FactoryAttentionKind> = new Set([\n 'automation-failed',\n 'automation-proposed',\n 'mention',\n 'supervisor-finding',\n]);\n\ntype AttentionTier = 'all' | 'badge' | 'activity';\n\nfunction parseAttentionTier(raw: string | undefined): AttentionTier | undefined {\n if (raw === undefined) return 'all';\n return raw === 'badge' || raw === 'activity' ? raw : undefined;\n}\n\nfunction kindInTier(tier: AttentionTier, kind: FactoryAttentionKind): boolean {\n if (tier === 'all') return true;\n return tier === 'badge' ? BADGE_KINDS.has(kind) : !BADGE_KINDS.has(kind);\n}\n\nfunction encodeAttentionCursor(cursors: AttentionCursorMap): string {\n const wire: Record<string, [string, string] | null> = {};\n for (const [kind, position] of cursors) {\n wire[kind] = position ? [position.occurredAt.toISOString(), position.id] : null;\n }\n return Buffer.from(JSON.stringify(wire), 'utf8').toString('base64url');\n}\n\nfunction parseStreamPosition(value: unknown): AttentionStreamPosition | undefined {\n if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== 'string' || typeof value[1] !== 'string') {\n return undefined;\n }\n const occurredAt = new Date(value[0]);\n if (Number.isNaN(occurredAt.getTime()) || !UUID_RE.test(value[1])) return undefined;\n return { occurredAt, id: value[1] };\n}\n\nfunction parseAttentionCursor(raw: string | undefined): AttentionCursorMap | undefined {\n if (!raw) return undefined;\n try {\n const decoded: unknown = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8'));\n // Cursors minted before the inbox merged kinds are a bare position over the\n // only stream there was. Held by anyone mid-list when this deploys, so they\n // resume that stream rather than 400: mentions arrive on their next load.\n if (Array.isArray(decoded)) {\n const legacy = parseStreamPosition(decoded);\n return legacy ? new Map([['automation-failed', legacy]]) : undefined;\n }\n if (!decoded || typeof decoded !== 'object') return undefined;\n const cursors: AttentionCursorMap = new Map();\n for (const [kind, value] of Object.entries(decoded)) {\n if (!isAttentionKind(kind)) return undefined;\n if (value === null) {\n cursors.set(kind, undefined);\n continue;\n }\n const position = parseStreamPosition(value);\n if (!position) return undefined;\n cursors.set(kind, position);\n }\n return cursors.size > 0 ? cursors : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction parseOccurrence(raw: string | undefined): number | undefined {\n if (!raw || !/^(0|[1-9]\\d*)$/.test(raw)) return undefined;\n const occurrence = Number(raw);\n return Number.isSafeInteger(occurrence) ? occurrence : undefined;\n}\n\ninterface MergedAttentionPage {\n items: Array<Record<string, unknown>>;\n hasMore: boolean;\n nextCursor?: string;\n}\n\n/**\n * Take the newest `limit` entries across provider pages. Each kind's next\n * cursor is the resume position of its last consumed entry; a kind consumed to\n * the end inherits the provider's own continuation.\n */\nfunction mergeAttentionPages(\n pages: Array<{\n kind: FactoryAttentionKind;\n incoming: AttentionStreamPosition | undefined;\n result: AttentionPageResult;\n }>,\n limit: number,\n): MergedAttentionPage {\n const consumed = new Map(pages.map(page => [page.kind, 0]));\n const items: Array<Record<string, unknown>> = [];\n while (items.length < limit) {\n let best: { kind: FactoryAttentionKind; at: number } | undefined;\n for (const page of pages) {\n const next = page.result.entries[consumed.get(page.kind) ?? 0];\n if (!next) continue;\n const at = next.occurredAt.getTime();\n if (!best || at > best.at) best = { kind: page.kind, at };\n }\n if (!best) break;\n const index = consumed.get(best.kind) ?? 0;\n const entry = pages.find(page => page.kind === best.kind)?.result.entries[index];\n if (!entry) break;\n items.push(entry.item);\n consumed.set(best.kind, index + 1);\n }\n\n const nextCursors: AttentionCursorMap = new Map();\n let hasMore = false;\n for (const page of pages) {\n const used = consumed.get(page.kind) ?? 0;\n const entries = page.result.entries;\n if (used < entries.length) {\n hasMore = true;\n const lastConsumed = used > 0 ? entries[used - 1] : undefined;\n nextCursors.set(page.kind, lastConsumed ? lastConsumed.resumeCursor : page.incoming);\n continue;\n }\n if (page.result.hasMore) {\n hasMore = true;\n nextCursors.set(page.kind, page.result.continuation ?? entries.at(-1)?.resumeCursor ?? page.incoming);\n }\n }\n return {\n items,\n hasMore,\n ...(hasMore && nextCursors.size > 0 ? { nextCursor: encodeAttentionCursor(nextCursors) } : {}),\n };\n}\n\nfunction newestLatest(latests: Array<AttentionLatest | null>): AttentionLatest | null {\n let newest: AttentionLatest | null = null;\n for (const latest of latests) {\n if (!latest) continue;\n if (!newest || latest.at.getTime() > newest.at.getTime()) newest = latest;\n }\n return newest;\n}\n\nfunction receiptRoute(\n dependencies: AttentionRouteDependencies,\n verb: 'read' | 'archive' | 'restore',\n action: FactoryAttentionReceiptAction,\n): ApiRoute {\n return registerApiRoute(`/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/${verb}`, {\n method: 'POST',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const kind = context.req.param('kind');\n const sourceId = context.req.param('sourceId');\n const occurrence = parseOccurrence(context.req.param('occurrence'));\n const validSourceId =\n kind === 'supervisor-finding' ? SUPERVISOR_FINDING_KEY_RE.test(sourceId) : UUID_RE.test(sourceId);\n if (!kind || !isAttentionKind(kind) || !sourceId || !validSourceId || occurrence === undefined) {\n return context.json({ error: 'invalid_attention_item' }, 422);\n }\n await dependencies.workItems.ensureReady();\n const receipt = await dependencies.workItems.setAttentionReceipt({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n identity: { kind, sourceId, occurrence },\n action,\n now: new Date(),\n });\n if (!receipt) return context.json({ error: 'attention_item_not_current' }, 409);\n return context.json({\n receipt: {\n key: factoryAttentionKey(resolved.factoryProjectId, receipt),\n state: receipt.state,\n readAt: receipt.readAt.toISOString(),\n archivedAt: receipt.archivedAt?.toISOString() ?? null,\n },\n });\n },\n });\n}\n\nexport function buildAttentionRoutes(dependencies: AttentionRouteDependencies): ApiRoute[] {\n const { workItems, comments } = dependencies;\n const providers: AttentionProvider[] = [\n new DecisionAttentionProvider({ workItems }, failedDecisionAttentionSpec),\n new DecisionAttentionProvider({ workItems }, proposedDecisionAttentionSpec),\n new SupervisorFindingAttentionProvider({ workItems }),\n new MentionAttentionProvider({ workItems, comments }),\n new ActivityAttentionProvider({ workItems, comments }),\n ];\n\n return [\n registerApiRoute('/web/factory/projects/:id/attention', {\n method: 'GET',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const view = parseAttentionView(context.req.query('view'));\n if (view === undefined) return context.json({ error: 'invalid_attention_view' }, 400);\n // `tier` scopes the item list only; the counts always describe every\n // tier, so the badge popover can page badge kinds without losing the\n // activity numbers.\n const tier = parseAttentionTier(context.req.query('tier'));\n if (tier === undefined) return context.json({ error: 'invalid_attention_tier' }, 400);\n const cursorRaw = context.req.query('before');\n const before = parseAttentionCursor(cursorRaw);\n if (cursorRaw && !before) return context.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n await comments.ensureReady();\n\n const search = context.req.query('search')?.trim().toLowerCase().slice(0, 200);\n const limit = parseAttentionLimit(context.req.query('limit'));\n const active = providers.filter(\n provider => kindInTier(tier, provider.kind) && (!before || before.has(provider.kind)),\n );\n\n const [summaries, pages] = await Promise.all([\n Promise.all(\n providers.map(async provider => ({\n kind: provider.kind,\n counts: await provider.counts(resolved),\n latest: await provider.latest(resolved),\n })),\n ),\n Promise.all(\n active.map(async provider => ({\n kind: provider.kind,\n incoming: before?.get(provider.kind),\n result: await provider.page(resolved, {\n view,\n ...(search ? { search } : {}),\n before: before?.get(provider.kind),\n limit,\n }),\n })),\n ),\n ]);\n\n // The badge tier and the activity tier are counted apart: activity\n // leaking into `latests` would ring the notification sound on every\n // teammate comment.\n const badge = summaries.filter(summary => BADGE_KINDS.has(summary.kind));\n const activity = summaries.filter(summary => !BADGE_KINDS.has(summary.kind));\n const sum = (rows: typeof summaries, field: 'open' | 'unread') =>\n rows.reduce((total, row) => total + row.counts[field], 0);\n const openCount = sum(badge, 'open');\n const unreadCount = sum(badge, 'unread');\n // An unread item must never be masked by a newer already-read one of\n // another kind — the streams are independent.\n const latests = badge.map(summary => summary.latest);\n const unreadLatests = latests.filter(latest => latest?.unread ?? false);\n const latest = unreadLatests.length > 0 ? newestLatest(unreadLatests) : newestLatest(latests);\n const merged = mergeAttentionPages(pages, limit);\n\n return context.json({\n items: merged.items,\n openCount,\n badgeCount: unreadCount,\n unreadCount,\n activityUnreadCount: sum(activity, 'unread'),\n latestOccurrenceKey: latest?.key ?? null,\n latestOccurrenceAt: latest?.at.toISOString() ?? null,\n latestOccurrenceUnread: latest?.unread ?? false,\n hasMore: merged.hasMore,\n ...(merged.nextCursor ? { nextCursor: merged.nextCursor } : {}),\n });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/attention/read-all', {\n method: 'POST',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const cursorRaw = context.req.query('before');\n const before = parseAttentionCursor(cursorRaw);\n if (cursorRaw && !before) return context.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n await comments.ensureReady();\n\n const now = new Date();\n const active = providers.filter(provider => !before || before.has(provider.kind));\n const nextCursors: AttentionCursorMap = new Map();\n let hasMore = false;\n for (const provider of active) {\n const result = await provider.markAllRead(resolved, { before: before?.get(provider.kind), now });\n if (result.hasMore) {\n hasMore = true;\n if (result.continuation) nextCursors.set(provider.kind, result.continuation);\n }\n }\n return context.json({\n ok: true,\n hasMore,\n ...(hasMore && nextCursors.size > 0 ? { nextCursor: encodeAttentionCursor(nextCursors) } : {}),\n });\n },\n }),\n receiptRoute(dependencies, 'read', 'read'),\n receiptRoute(dependencies, 'archive', 'archive'),\n receiptRoute(dependencies, 'restore', 'restore'),\n ];\n}\n"],"mappings":"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"}