@mastra/factory 0.12.1-alpha.0 → 0.13.0-alpha.10

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 (211) hide show
  1. package/README.md +163 -0
  2. package/dist/auth.d.ts +2 -0
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +35 -3
  5. package/dist/auth.js.map +1 -1
  6. package/dist/boards/define-board.d.ts +42 -0
  7. package/dist/boards/define-board.d.ts.map +1 -0
  8. package/dist/boards/define-board.js +53 -0
  9. package/dist/boards/define-board.js.map +1 -0
  10. package/dist/boards/index.d.ts +12 -0
  11. package/dist/boards/index.d.ts.map +1 -0
  12. package/dist/boards/index.js +20 -0
  13. package/dist/boards/index.js.map +1 -0
  14. package/dist/boards/registry.d.ts +16 -0
  15. package/dist/boards/registry.d.ts.map +1 -0
  16. package/dist/boards/registry.js +27 -0
  17. package/dist/boards/registry.js.map +1 -0
  18. package/dist/boards/review.d.ts +4 -0
  19. package/dist/boards/review.d.ts.map +1 -0
  20. package/dist/boards/review.js +76 -0
  21. package/dist/boards/review.js.map +1 -0
  22. package/dist/boards/transition-policy.d.ts +63 -0
  23. package/dist/boards/transition-policy.d.ts.map +1 -0
  24. package/dist/boards/transition-policy.js +36 -0
  25. package/dist/boards/transition-policy.js.map +1 -0
  26. package/dist/boards/work-transition-policy.d.ts +3 -0
  27. package/dist/boards/work-transition-policy.d.ts.map +1 -0
  28. package/dist/boards/work-transition-policy.js +31 -0
  29. package/dist/boards/work-transition-policy.js.map +1 -0
  30. package/dist/boards/work.d.ts +4 -0
  31. package/dist/boards/work.d.ts.map +1 -0
  32. package/dist/boards/work.js +154 -0
  33. package/dist/boards/work.js.map +1 -0
  34. package/dist/factory.d.ts +6 -1
  35. package/dist/factory.d.ts.map +1 -1
  36. package/dist/factory.js +101 -10
  37. package/dist/factory.js.map +1 -1
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +6 -1
  41. package/dist/integrations/github/acceptance-labels.d.ts +0 -1
  42. package/dist/integrations/github/acceptance-labels.d.ts.map +1 -1
  43. package/dist/integrations/github/acceptance-labels.js +2 -2
  44. package/dist/integrations/github/acceptance-labels.js.map +1 -1
  45. package/dist/integrations/github/default-rules.d.ts +194 -0
  46. package/dist/integrations/github/default-rules.d.ts.map +1 -0
  47. package/dist/integrations/github/default-rules.js +230 -0
  48. package/dist/integrations/github/default-rules.js.map +1 -0
  49. package/dist/integrations/github/integration.d.ts +4 -0
  50. package/dist/integrations/github/integration.d.ts.map +1 -1
  51. package/dist/integrations/github/integration.js +6 -0
  52. package/dist/integrations/github/integration.js.map +1 -1
  53. package/dist/integrations/github/provenance.d.ts +2 -0
  54. package/dist/integrations/github/provenance.d.ts.map +1 -1
  55. package/dist/integrations/github/provenance.js +3 -2
  56. package/dist/integrations/github/provenance.js.map +1 -1
  57. package/dist/integrations/github/routes.d.ts.map +1 -1
  58. package/dist/integrations/github/routes.js +15 -41
  59. package/dist/integrations/github/routes.js.map +1 -1
  60. package/dist/integrations/github/rules.d.ts +3 -1
  61. package/dist/integrations/github/rules.d.ts.map +1 -1
  62. package/dist/integrations/github/rules.js +36 -7
  63. package/dist/integrations/github/rules.js.map +1 -1
  64. package/dist/integrations/github/webhook.d.ts.map +1 -1
  65. package/dist/integrations/github/webhook.js +35 -3
  66. package/dist/integrations/github/webhook.js.map +1 -1
  67. package/dist/integrations/linear/agent-tools.d.ts +3 -3
  68. package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
  69. package/dist/integrations/linear/agent-tools.js +5 -3
  70. package/dist/integrations/linear/agent-tools.js.map +1 -1
  71. package/dist/integrations/linear/default-rules.d.ts +44 -0
  72. package/dist/integrations/linear/default-rules.d.ts.map +1 -0
  73. package/dist/integrations/linear/default-rules.js +65 -0
  74. package/dist/integrations/linear/default-rules.js.map +1 -0
  75. package/dist/integrations/linear/integration.d.ts +5 -1
  76. package/dist/integrations/linear/integration.d.ts.map +1 -1
  77. package/dist/integrations/linear/integration.js +7 -1
  78. package/dist/integrations/linear/integration.js.map +1 -1
  79. package/dist/integrations/linear/issue-reconciler.d.ts +1 -1
  80. package/dist/integrations/linear/issue-reconciler.d.ts.map +1 -1
  81. package/dist/integrations/linear/issue-reconciler.js +2 -1
  82. package/dist/integrations/linear/issue-reconciler.js.map +1 -1
  83. package/dist/integrations/linear/rules.d.ts +5 -1
  84. package/dist/integrations/linear/rules.d.ts.map +1 -1
  85. package/dist/integrations/linear/rules.js +5 -4
  86. package/dist/integrations/linear/rules.js.map +1 -1
  87. package/dist/integrations/platform/github/integration.d.ts +4 -0
  88. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  89. package/dist/integrations/platform/github/integration.js +6 -0
  90. package/dist/integrations/platform/github/integration.js.map +1 -1
  91. package/dist/integrations/platform/linear/integration.d.ts +5 -1
  92. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  93. package/dist/integrations/platform/linear/integration.js +9 -3
  94. package/dist/integrations/platform/linear/integration.js.map +1 -1
  95. package/dist/integrations/slack/slack.d.ts +3 -1
  96. package/dist/integrations/slack/slack.d.ts.map +1 -1
  97. package/dist/integrations/slack/slack.js +20 -2
  98. package/dist/integrations/slack/slack.js.map +1 -1
  99. package/dist/routes/attention-proposed.d.ts +3 -0
  100. package/dist/routes/attention-proposed.d.ts.map +1 -0
  101. package/dist/routes/attention-proposed.js +21 -0
  102. package/dist/routes/attention-proposed.js.map +1 -0
  103. package/dist/routes/attention-providers.d.ts +33 -4
  104. package/dist/routes/attention-providers.d.ts.map +1 -1
  105. package/dist/routes/attention-providers.js +180 -47
  106. package/dist/routes/attention-providers.js.map +1 -1
  107. package/dist/routes/attention.d.ts.map +1 -1
  108. package/dist/routes/attention.js +31 -30
  109. package/dist/routes/attention.js.map +1 -1
  110. package/dist/routes/config.d.ts +2 -0
  111. package/dist/routes/config.d.ts.map +1 -1
  112. package/dist/routes/config.js +3 -2
  113. package/dist/routes/config.js.map +1 -1
  114. package/dist/routes/projects.d.ts +9 -1
  115. package/dist/routes/projects.d.ts.map +1 -1
  116. package/dist/routes/projects.js +26 -5
  117. package/dist/routes/projects.js.map +1 -1
  118. package/dist/routes/supervisor.d.ts +23 -0
  119. package/dist/routes/supervisor.d.ts.map +1 -0
  120. package/dist/routes/supervisor.js +34 -0
  121. package/dist/routes/supervisor.js.map +1 -0
  122. package/dist/routes/surface.d.ts +3 -0
  123. package/dist/routes/surface.d.ts.map +1 -1
  124. package/dist/routes/surface.js +11 -6
  125. package/dist/routes/surface.js.map +1 -1
  126. package/dist/routes/work-items.d.ts +3 -0
  127. package/dist/routes/work-items.d.ts.map +1 -1
  128. package/dist/routes/work-items.js +60 -53
  129. package/dist/routes/work-items.js.map +1 -1
  130. package/dist/rules/defaults.d.ts.map +1 -1
  131. package/dist/rules/defaults.js +2 -418
  132. package/dist/rules/defaults.js.map +1 -1
  133. package/dist/rules/dispatcher.d.ts +1 -0
  134. package/dist/rules/dispatcher.d.ts.map +1 -1
  135. package/dist/rules/dispatcher.js +87 -26
  136. package/dist/rules/dispatcher.js.map +1 -1
  137. package/dist/rules/index.d.ts +2 -2
  138. package/dist/rules/index.d.ts.map +1 -1
  139. package/dist/rules/index.js +2 -2
  140. package/dist/rules/processor.d.ts +2 -0
  141. package/dist/rules/processor.d.ts.map +1 -1
  142. package/dist/rules/processor.js +10 -7
  143. package/dist/rules/processor.js.map +1 -1
  144. package/dist/rules/resolve.d.ts +4 -5
  145. package/dist/rules/resolve.d.ts.map +1 -1
  146. package/dist/rules/resolve.js +4 -9
  147. package/dist/rules/resolve.js.map +1 -1
  148. package/dist/rules/start-coordinator.d.ts +3 -16
  149. package/dist/rules/start-coordinator.d.ts.map +1 -1
  150. package/dist/rules/start-coordinator.js +12 -43
  151. package/dist/rules/start-coordinator.js.map +1 -1
  152. package/dist/rules/terminal-cleanup.d.ts +3 -1
  153. package/dist/rules/terminal-cleanup.d.ts.map +1 -1
  154. package/dist/rules/terminal-cleanup.js +5 -0
  155. package/dist/rules/terminal-cleanup.js.map +1 -1
  156. package/dist/rules/transition-service.d.ts +3 -0
  157. package/dist/rules/transition-service.d.ts.map +1 -1
  158. package/dist/rules/transition-service.js +56 -31
  159. package/dist/rules/transition-service.js.map +1 -1
  160. package/dist/rules/types.d.ts +18 -17
  161. package/dist/rules/types.d.ts.map +1 -1
  162. package/dist/rules/types.js +10 -2
  163. package/dist/rules/types.js.map +1 -1
  164. package/dist/rules/validation.d.ts.map +1 -1
  165. package/dist/rules/validation.js +2 -38
  166. package/dist/rules/validation.js.map +1 -1
  167. package/dist/sandbox/session-retirement.js +1 -1
  168. package/dist/session/org-seed.d.ts +1 -1
  169. package/dist/session/org-seed.js +1 -1
  170. package/dist/session/org-seed.js.map +1 -1
  171. package/dist/storage/domains/comments/domain.js +1 -1
  172. package/dist/storage/domains/comments/routes.js +1 -1
  173. package/dist/storage/domains/work-items/base.d.ts +54 -19
  174. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  175. package/dist/storage/domains/work-items/base.js +215 -38
  176. package/dist/storage/domains/work-items/base.js.map +1 -1
  177. package/dist/supervisor/health-worker.d.ts +17 -0
  178. package/dist/supervisor/health-worker.d.ts.map +1 -0
  179. package/dist/supervisor/health-worker.js +72 -0
  180. package/dist/supervisor/health-worker.js.map +1 -0
  181. package/dist/supervisor/health.d.ts +81 -0
  182. package/dist/supervisor/health.d.ts.map +1 -0
  183. package/dist/supervisor/health.js +244 -0
  184. package/dist/supervisor/health.js.map +1 -0
  185. package/dist/supervisor/instructions.d.ts +6 -0
  186. package/dist/supervisor/instructions.d.ts.map +1 -0
  187. package/dist/supervisor/instructions.js +64 -0
  188. package/dist/supervisor/instructions.js.map +1 -0
  189. package/dist/supervisor/read-tools.d.ts +41 -0
  190. package/dist/supervisor/read-tools.d.ts.map +1 -0
  191. package/dist/supervisor/read-tools.js +338 -0
  192. package/dist/supervisor/read-tools.js.map +1 -0
  193. package/dist/supervisor/session.d.ts +38 -0
  194. package/dist/supervisor/session.d.ts.map +1 -0
  195. package/dist/supervisor/session.js +63 -0
  196. package/dist/supervisor/session.js.map +1 -0
  197. package/dist/supervisor/write-tools.d.ts +26 -0
  198. package/dist/supervisor/write-tools.d.ts.map +1 -0
  199. package/dist/supervisor/write-tools.js +227 -0
  200. package/dist/supervisor/write-tools.js.map +1 -0
  201. package/dist/work-item-branch.d.ts +6 -0
  202. package/dist/work-item-branch.d.ts.map +1 -1
  203. package/dist/work-item-branch.js +15 -9
  204. package/dist/work-item-branch.js.map +1 -1
  205. package/dist/workspace.d.ts +3 -0
  206. package/dist/workspace.d.ts.map +1 -1
  207. package/dist/workspace.js +15 -5
  208. package/dist/workspace.js.map +1 -1
  209. package/factory-skills/configure-factory-rules/SKILL.md +42 -11
  210. package/factory-skills/factory-triage/SKILL.md +1 -1
  211. package/package.json +13 -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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"}
@@ -1,4 +1,4 @@
1
- import { factoryAttentionKey, factoryDecisionAttentionIdentity, factoryMentionAttentionIdentity } from "../storage/domains/work-items/base.js";
1
+ import { factoryAttentionKey, factoryDecisionAttentionIdentity, factoryMentionAttentionIdentity, factorySupervisorFindingAttentionIdentity } from "../storage/domains/work-items/base.js";
2
2
  import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
3
3
  //#region src/routes/attention-providers.ts
4
4
  /**
@@ -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,151 @@ 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
+ };
233
+ function supervisorFindingPayload(row) {
234
+ return row.finding;
235
+ }
236
+ var SupervisorFindingAttentionProvider = class {
237
+ kind = "supervisor-finding";
238
+ #workItems;
239
+ constructor({ workItems }) {
240
+ this.#workItems = workItems;
241
+ }
242
+ async counts(scope) {
243
+ let open = 0;
244
+ let unread = 0;
245
+ const batches = scanBatches((before) => this.#workItems.listSupervisorFindingPage({
246
+ ...scope,
247
+ ...before ? { before } : {},
248
+ limit: 50
249
+ }), (row) => ({
250
+ occurredAt: row.updatedAt,
251
+ id: row.id
252
+ }));
253
+ for await (const page of batches) {
254
+ const identities = page.rows.map((row) => factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence));
255
+ const receipts = await this.#workItems.listAttentionReceipts({
256
+ ...scope,
257
+ identities
258
+ });
259
+ const byIdentity = new Map(receipts.map((receipt) => [`${receipt.sourceId}\0${receipt.occurrence}`, receipt]));
260
+ for (const row of page.rows) {
261
+ const receipt = byIdentity.get(`${row.findingKey}\0${row.occurrence}`);
262
+ if (receipt?.state === "archived") continue;
263
+ open += 1;
264
+ if (!receipt) unread += 1;
265
+ }
266
+ }
267
+ return {
268
+ open,
269
+ unread
270
+ };
271
+ }
272
+ async latest(scope) {
273
+ const newest = (await this.#workItems.listSupervisorFindingPage({
274
+ ...scope,
275
+ limit: 1
276
+ })).rows[0];
277
+ if (!newest) return null;
278
+ const identity = factorySupervisorFindingAttentionIdentity(newest.findingKey, newest.occurrence);
279
+ const receipts = await this.#workItems.listAttentionReceipts({
280
+ ...scope,
281
+ identities: [identity]
282
+ });
283
+ return {
284
+ key: factoryAttentionKey(scope.factoryProjectId, identity),
285
+ at: newest.updatedAt,
286
+ unread: receipts.length === 0
287
+ };
288
+ }
289
+ page(scope, args) {
290
+ return collectPage(scanBatches((before) => this.#workItems.listSupervisorFindingPage({
291
+ ...scope,
292
+ ...before ? { before } : {},
293
+ limit: 50
294
+ }), (row) => ({
295
+ occurredAt: row.updatedAt,
296
+ id: row.id
297
+ }), args.before), args.limit, async (rows) => {
298
+ const identities = rows.map((row) => factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence));
299
+ const receipts = await this.#workItems.listAttentionReceipts({
300
+ ...scope,
301
+ identities
302
+ });
303
+ const byIdentity = new Map(receipts.map((receipt) => [`${receipt.sourceId}\0${receipt.occurrence}`, receipt]));
304
+ return rows.flatMap((row) => {
305
+ const finding = supervisorFindingPayload(row);
306
+ const receipt = byIdentity.get(`${row.findingKey}\0${row.occurrence}`);
307
+ if (!matchesView(args.view, receipt)) return [];
308
+ const text = `${finding.title} ${finding.evidence}`.toLowerCase();
309
+ if (args.search && !text.includes(args.search)) return [];
310
+ const identity = factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence);
311
+ return [{
312
+ key: factoryAttentionKey(scope.factoryProjectId, identity),
313
+ occurredAt: row.updatedAt,
314
+ resumeCursor: {
315
+ occurredAt: row.updatedAt,
316
+ id: row.id
317
+ },
318
+ receipt,
319
+ item: {
320
+ key: factoryAttentionKey(scope.factoryProjectId, identity),
321
+ kind: this.kind,
322
+ findingKey: row.findingKey,
323
+ occurrence: row.occurrence,
324
+ findingTitle: finding.title,
325
+ evidence: finding.evidence,
326
+ title: finding.title,
327
+ detail: finding.evidence,
328
+ ageMs: finding.ageMs,
329
+ suggestedRepair: finding.suggestedRepair,
330
+ workItemId: finding.workItemId,
331
+ occurredAt: row.updatedAt.toISOString(),
332
+ read: Boolean(receipt),
333
+ archived: receipt?.state === "archived",
334
+ target: finding.workItemId ? {
335
+ kind: "work-item",
336
+ workItemId: finding.workItemId,
337
+ board: "work"
338
+ } : { kind: "rules" }
339
+ }
340
+ }];
341
+ });
342
+ });
343
+ }
344
+ markAllRead(scope, args) {
345
+ return markScanRead(scanBatches((before) => this.#workItems.listSupervisorFindingPage({
346
+ ...scope,
347
+ ...before ? { before } : {},
348
+ limit: 50
349
+ }), (row) => ({
350
+ occurredAt: row.updatedAt,
351
+ id: row.id
352
+ }), args.before), (rows) => this.#workItems.markAttentionReceiptsRead({
353
+ ...scope,
354
+ identities: rows.map((row) => factorySupervisorFindingAttentionIdentity(row.findingKey, row.occurrence)),
355
+ now: args.now
356
+ }));
357
+ }
358
+ };
203
359
  var MentionAttentionProvider = class {
204
360
  kind = "mention";
205
361
  #workItems;
@@ -322,29 +478,6 @@ var MentionAttentionProvider = class {
322
478
  });
323
479
  }
324
480
  };
325
- function matchesFailureSearch(decision, item, search) {
326
- return item?.title.toLowerCase().includes(search) === true || decision.lastError?.toLowerCase().includes(search) === true || factoryDecisionType(decision).toLowerCase().includes(search);
327
- }
328
- function toFailureItem(scope, decision, item, receipt) {
329
- const failure = factoryDispatchFailureMetadata(decision.failureCode);
330
- const identity = factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);
331
- return {
332
- key: factoryAttentionKey(scope.factoryProjectId, identity),
333
- kind: "automation-failed",
334
- decisionId: decision.id,
335
- occurrence: decision.failureOccurrence,
336
- workItemId: decision.workItemId,
337
- title: item?.title ?? failure.label,
338
- detail: decision.lastError?.slice(0, 512) ?? failure.label,
339
- decisionType: factoryDecisionType(decision),
340
- failureCode: decision.failureCode,
341
- canRetry: failure.canRetry,
342
- occurredAt: failureOccurredAt(decision).toISOString(),
343
- read: receipt !== void 0,
344
- archived: receipt?.state === "archived",
345
- target: attentionTarget(decision, item)
346
- };
347
- }
348
481
  function matchesMentionSearch({ item, comment }, search) {
349
482
  return item.title.toLowerCase().includes(search) || comment.body.toLowerCase().includes(search) || comment.author.displayName?.toLowerCase().includes(search) === true;
350
483
  }
@@ -379,6 +512,6 @@ function receiptScope(scope) {
379
512
  };
380
513
  }
381
514
  //#endregion
382
- export { AutomationFailedAttentionProvider, MentionAttentionProvider, SCAN_PAGE_SIZE, collectPage, factoryDecisionType, markScanRead, matchesView, receiptScope, scanBatches, workItemBoard };
515
+ export { DecisionAttentionProvider, MentionAttentionProvider, SCAN_PAGE_SIZE, SupervisorFindingAttentionProvider, collectPage, factoryDecisionType, failedDecisionAttentionSpec, markScanRead, matchesView, receiptScope, scanBatches, workItemBoard };
383
516
 
384
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 WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport {\n factoryAttentionKey,\n factoryDecisionAttentionIdentity,\n factoryMentionAttentionIdentity,\n} from '../storage/domains/work-items/base.js';\n\nexport type FactoryAttentionView = 'open' | 'unread' | 'archived';\n\nexport interface AttentionScope {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n}\n\nexport interface AttentionStreamPosition {\n occurredAt: Date;\n id: string;\n}\n\nexport interface AttentionEntry {\n occurredAt: Date;\n /** Position resuming the provider's stream right after this entry. */\n resumeCursor: AttentionStreamPosition;\n item: Record<string, unknown>;\n}\n\nexport interface AttentionPageArgs {\n view: FactoryAttentionView;\n search?: string;\n before?: AttentionStreamPosition;\n limit: number;\n}\n\nexport interface AttentionPageResult {\n entries: AttentionEntry[];\n /** More rows behind the scan once every returned entry is consumed. */\n hasMore: boolean;\n /** Resume point for a scan-budget stop past the last returned entry. */\n continuation?: AttentionStreamPosition;\n}\n\nexport interface AttentionCounts {\n open: number;\n unread: number;\n}\n\nexport interface AttentionLatest {\n key: string;\n at: Date;\n unread: boolean;\n}\n\nexport interface AttentionProvider {\n kind: FactoryAttentionKind;\n counts(scope: AttentionScope): Promise<AttentionCounts>;\n latest(scope: AttentionScope): Promise<AttentionLatest | null>;\n page(scope: AttentionScope, args: AttentionPageArgs): Promise<AttentionPageResult>;\n markAllRead(\n scope: AttentionScope,\n args: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }>;\n}\n\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\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":";;;;;;;;AAmFA,MAAa,iBAAiB;AAE9B,MAAM,yBAAyB;;;;;;AAa/B,gBAAuB,YACrB,YACA,YACA,QAC8B;CAC9B,IAAI,SAAS;CACb,KAAK,IAAI,UAAU,GAAG,WAAW,wBAAwB,WAAW,GAAG;EACrE,MAAM,EAAE,MAAM,YAAY,MAAM,WAAW,MAAM;EACjD,MAAM,OAAO,KAAK,GAAG,EAAE;EACvB,IAAI,CAAC,MAAM;EACX,MAAM,WAAW,WAAW,IAAI;EAChC,MAAM;GAAE;GAAM,GAAI,WAAW,YAAY,yBAAyB,EAAE,cAAc,SAAS,IAAI,CAAC;EAAG;EACnG,IAAI,CAAC,SAAS;EACd,SAAS;CACX;AACF;;AAGA,eAAsB,YACpB,SACA,OACA,WAC8B;CAC9B,MAAM,UAA4B,CAAC;CACnC,WAAW,MAAM,SAAS,SAAS;EACjC,KAAK,MAAM,SAAS,MAAM,UAAU,MAAM,IAAI,GAAG;GAC/C,IAAI,QAAQ,WAAW,OAAO,OAAO;IAAE;IAAS,SAAS;GAAK;GAC9D,QAAQ,KAAK,KAAK;EACpB;EACA,IAAI,MAAM,cAAc,OAAO;GAAE;GAAS,SAAS;GAAM,cAAc,MAAM;EAAa;CAC5F;CACA,OAAO;EAAE;EAAS,SAAS;CAAM;AACnC;;AAGA,eAAsB,aACpB,SACA,WACuE;CACvE,WAAW,MAAM,SAAS,SAAS;EACjC,MAAM,UAAU,MAAM,IAAI;EAC1B,IAAI,MAAM,cAAc,OAAO;GAAE,SAAS;GAAM,cAAc,MAAM;EAAa;CACnF;CACA,OAAO,EAAE,SAAS,MAAM;AAC1B;AAEA,SAAgB,oBAAoB,UAAiD;CACnF,OAAO,OAAO,SAAS,SAAS,SAAS,WAAW,SAAS,SAAS,KAAK,MAAM,GAAG,EAAE,IAAI;AAC5F;AAEA,SAAS,kBAAkB,UAA+C;CACxE,OAAO,SAAS,eAAe,SAAS;AAC1C;AAEA,SAAgB,YAAY,MAA4B,SAA6D;CACnH,IAAI,SAAS,YAAY,OAAO,SAAS,UAAU;CACnD,IAAI,SAAS,UAAU,OAAO,YAAY,KAAA;CAC1C,OAAO,SAAS,UAAU;AAC5B;AAEA,SAAS,gBAAgB,UAAyC,MAA+B;CAC/F,IAAI,CAAC,MAAM,OAAO,EAAE,MAAM,QAAiB;CAC3C,MAAM,OAAO,OAAO,SAAS,SAAS,SAAS,WAAW,SAAS,SAAS,OAAO,KAAA;CACnF,MAAM,UAAU,OAAO,KAAK,SAAS,QAAQ,KAAA;CAC7C,IAAI,SACF,OAAO;EACL,MAAM;EACN,WAAW,QAAQ;EACnB,UAAU,QAAQ;CACpB;CAEF,OAAO;EACL,MAAM;EACN,YAAY,KAAK;EACjB,OAAO,cAAc,IAAI;CAC3B;AACF;AAEA,SAAgB,cAAc,MAAsC;CAElE,OADe,KAAK,gBAAgB,kBAAkB,YAAY,KAAK,eAAe,SAAS,iBAC/E,WAAW;AAC7B;AAEA,IAAa,oCAAb,MAA4E;CAC1E,OAAgB;CAChB;CAEA,YAAY,EAAE,aAA8C;EAC1D,KAAKA,aAAa;CACpB;CAEA,MAAM,OAAO,OAAiD;EAC5D,MAAM,CAAC,aAAa,cAAc,iBAAiB,MAAM,QAAQ,IAAI;GACnE,KAAKA,WAAW,iCAAiC;IAC/C,OAAO,MAAM;IACb,kBAAkB,MAAM;IACxB,UAAU,CAAC,QAAQ;GACrB,CAAC;GACD,KAAKA,WAAW,uBAAuB;IAAE,GAAG,aAAa,KAAK;IAAG,MAAM,KAAK;GAAK,CAAC;GAClF,KAAKA,WAAW,uBAAuB;IAAE,GAAG,aAAa,KAAK;IAAG,MAAM,KAAK;IAAM,OAAO;GAAW,CAAC;EACvG,CAAC;EACD,OAAO;GACL,MAAM,KAAK,IAAI,GAAG,cAAc,aAAa;GAC7C,QAAQ,KAAK,IAAI,GAAG,cAAc,YAAY;EAChD;CACF;CAEA,MAAM,OAAO,OAAwD;EAMnE,MAAM,UAAS,MALI,KAAKA,WAAW,uBAAuB;GACxD,OAAO,MAAM;GACb,kBAAkB,MAAM;GACxB,OAAO;EACT,CAAC,EAAA,CACmB,UAAU;EAC9B,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,WAAW,iCAAiC,OAAO,IAAI,OAAO,iBAAiB;EACrF,MAAM,WAAW,MAAM,KAAKA,WAAW,sBAAsB;GAC3D,GAAG,aAAa,KAAK;GACrB,YAAY,CAAC,QAAQ;EACvB,CAAC;EACD,OAAO;GACL,KAAK,oBAAoB,MAAM,kBAAkB,QAAQ;GACzD,IAAI,kBAAkB,MAAM;GAC5B,QAAQ,SAAS,WAAW;EAC9B;CACF;CAEA,MAAM,OAAuB,QAAkC;EAC7D,OAAO,YACL,OAAM,WAAU;GACd,MAAM,OAAO,MAAM,KAAKA,WAAW,uBAAuB;IACxD,OAAO,MAAM;IACb,kBAAkB,MAAM;IACxB,QAAQ;IACR,OAAA;GACF,CAAC;GACD,OAAO;IAAE,MAAM,KAAK;IAAW,SAAS,KAAK;GAAQ;EACvD,IACA,cAAa;GAAE,YAAY,kBAAkB,QAAQ;GAAG,IAAI,SAAS;EAAG,IACxE,MACF;CACF;CAEA,MAAM,KAAK,OAAuB,EAAE,MAAM,QAAQ,QAAQ,SAA0D;EAClH,OAAO,YAAY,KAAKC,MAAM,OAAO,MAAM,GAAG,OAAO,OAAM,cAAa;GACtE,MAAM,CAAC,cAAc,YAAY,MAAM,QAAQ,IAAI,CACjD,KAAKC,aAAa,OAAO,SAAS,GAClC,KAAKC,aAAa,OAAO,SAAS,CACpC,CAAC;GACD,MAAM,UAA4B,CAAC;GACnC,KAAK,MAAM,YAAY,WAAW;IAChC,MAAM,WAAW,iCAAiC,SAAS,IAAI,SAAS,iBAAiB;IACzF,MAAM,UAAU,aAAa,IAAI,oBAAoB,MAAM,kBAAkB,QAAQ,CAAC;IACtF,IAAI,CAAC,YAAY,MAAM,OAAO,GAAG;IACjC,MAAM,OAAO,SAAS,aAAa,SAAS,IAAI,SAAS,UAAU,IAAI,KAAA;IACvE,IAAI,UAAU,CAAC,qBAAqB,UAAU,MAAM,MAAM,GAAG;IAC7D,QAAQ,KAAK;KACX,YAAY,kBAAkB,QAAQ;KACtC,cAAc;MAAE,YAAY,kBAAkB,QAAQ;MAAG,IAAI,SAAS;KAAG;KACzE,MAAM,cAAc,OAAO,UAAU,MAAM,OAAO;IACpD,CAAC;GACH;GACA,OAAO;EACT,CAAC;CACH;CAEA,MAAMD,aACJ,OACA,WACqD;EACrD,MAAM,WAAW,MAAM,KAAKF,WAAW,sBAAsB;GAC3D,GAAG,aAAa,KAAK;GACrB,YAAY,UAAU,KAAI,aAAY,iCAAiC,SAAS,IAAI,SAAS,iBAAiB,CAAC;EACjH,CAAC;EACD,OAAO,IAAI,IAAI,SAAS,KAAI,YAAW,CAAC,oBAAoB,MAAM,kBAAkB,OAAO,GAAG,OAAO,CAAC,CAAC;CACzG;CAEA,MAAMG,aACJ,OACA,WACmC;EACnC,MAAM,QAAQ,MAAM,KAAKH,WAAW,UAAU;GAC5C,OAAO,MAAM;GACb,kBAAkB,MAAM;GACxB,KAAK,UAAU,SAAQ,aAAa,SAAS,aAAa,CAAC,SAAS,UAAU,IAAI,CAAC,CAAE;EACvF,CAAC;EACD,OAAO,IAAI,IAAI,MAAM,KAAI,SAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;CACnD;CAEA,MAAM,YACJ,OACA,EAAE,QAAQ,OAC6D;EACvE,OAAO,aAAa,KAAKC,MAAM,OAAO,MAAM,GAAG,OAAM,cAAa;GAChE,MAAM,KAAKD,WAAW,0BAA0B;IAC9C,GAAG,aAAa,KAAK;IACrB,YAAY,UAAU,KAAI,aACxB,iCAAiC,SAAS,IAAI,SAAS,iBAAiB,CAC1E;IACA;GACF,CAAC;EACH,CAAC;CACH;AACF;AAUA,IAAa,2BAAb,MAAmE;CACjE,OAAgB;CAChB;CACA;CAEA,YAAY,EAAE,WAAW,YAAgF;EACvG,KAAKA,aAAa;EAClB,KAAKI,YAAY;CACnB;CAEA,MAAM,OAAuB,QAAkC;EAC7D,OAAO,YACL,OAAM,WAAU;GACd,MAAM,OAAO,MAAM,KAAKA,UAAU,oBAAoB;IACpD,GAAG,aAAa,KAAK;IACrB,GAAI,SAAS,EAAE,QAAQ,OAAO,IAAI,CAAC;IACnC,OAAO;GACT,CAAC;GACD,OAAO;IAAE,MAAM,KAAK,MAAM,GAAA,EAAiB;IAAG,SAAS,KAAK,SAAA;GAAwB;EACtF,IACA,aAAY;GAAE,YAAY,QAAQ;GAAY,IAAI,QAAQ;EAAG,IAC7D,MACF;CACF;;;;;CAMA,MAAMC,SAAS,OAAuB,UAA4D;EAChG,MAAM,CAAC,UAAU,UAAU,SAAS,MAAM,QAAQ,IAAI;GACpD,KAAKL,WAAW,sBAAsB;IACpC,GAAG,aAAa,KAAK;IACrB,YAAY,SAAS,KAAI,YAAW,gCAAgC,QAAQ,SAAS,CAAC;GACxF,CAAC;GACD,KAAKI,UAAU,UAAU;IACvB,OAAO,MAAM;IACb,KAAK,CAAC,GAAG,IAAI,IAAI,SAAS,KAAI,YAAW,QAAQ,SAAS,CAAC,CAAC;GAC9D,CAAC;GACD,KAAKJ,WAAW,UAAU;IACxB,OAAO,MAAM;IACb,kBAAkB,MAAM;IACxB,KAAK,CAAC,GAAG,IAAI,IAAI,SAAS,KAAI,YAAW,QAAQ,UAAU,CAAC,CAAC;GAC/D,CAAC;EACH,CAAC;EACD,MAAM,eAAe,IAAI,IACvB,SAAS,KAAI,YAAW,CAAC,oBAAoB,MAAM,kBAAkB,OAAO,GAAG,OAAO,CAAC,CACzF;EACA,MAAM,cAAc,IAAI,IAAI,SAAS,KAAI,YAAW,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC;EAC1E,MAAM,WAAW,IAAI,IAAI,MAAM,KAAI,SAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;EAE3D,MAAM,WAA8B,CAAC;EACrC,KAAK,MAAM,WAAW,UAAU;GAC9B,MAAM,UAAU,YAAY,IAAI,QAAQ,SAAS;GACjD,MAAM,OAAO,SAAS,IAAI,QAAQ,UAAU;GAC5C,IAAI,CAAC,WAAW,QAAQ,aAAa,CAAC,MAAM;GAC5C,MAAM,WAAW,gCAAgC,QAAQ,SAAS;GAClE,SAAS,KAAK;IACZ;IACA;IACA;IACA;IACA,SAAS,aAAa,IAAI,oBAAoB,MAAM,kBAAkB,QAAQ,CAAC;GACjF,CAAC;EACH;EACA,OAAO;CACT;;;;;;;CAQA,MAAM,OAAO,OAAiD;EAC5D,IAAI,OAAO;EACX,IAAI,SAAS;EACb,WAAW,MAAM,SAAS,KAAKC,MAAM,KAAK,GACxC,KAAK,MAAM,EAAE,aAAa,MAAM,KAAKI,SAAS,OAAO,MAAM,IAAI,GAAG;GAChE,IAAI,SAAS,UAAU,YAAY,QAAQ;GAC3C,IAAI,YAAY,KAAA,GAAW,UAAU;EACvC;EAEF,OAAO;GAAE;GAAM;EAAO;CACxB;CAEA,MAAM,OAAO,OAAwD;EACnE,MAAM,WAAW,MAAM,KAAKD,UAAU,oBAAoB;GAAE,GAAG,aAAa,KAAK;GAAG,OAAO;EAAG,CAAC;EAC/F,MAAM,UAAU,MAAM,KAAKC,SAAS,OAAO,QAAQ,EAAA,CAAG;EACtD,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO;GACL,KAAK,oBAAoB,MAAM,kBAAkB,OAAO,QAAQ;GAChE,IAAI,OAAO,QAAQ;GACnB,QAAQ,OAAO,YAAY,KAAA;EAC7B;CACF;CAEA,MAAM,KAAK,OAAuB,EAAE,MAAM,QAAQ,QAAQ,SAA0D;EAClH,OAAO,YAAY,KAAKJ,MAAM,OAAO,MAAM,GAAG,OAAO,OAAM,aAAY;GACrE,MAAM,UAA4B,CAAC;GACnC,KAAK,MAAM,YAAY,MAAM,KAAKI,SAAS,OAAO,QAAQ,GAAG;IAC3D,IAAI,CAAC,YAAY,MAAM,SAAS,OAAO,GAAG;IAC1C,IAAI,UAAU,CAAC,qBAAqB,UAAU,MAAM,GAAG;IACvD,QAAQ,KAAK;KACX,YAAY,SAAS,QAAQ;KAC7B,cAAc;MAAE,YAAY,SAAS,QAAQ;MAAY,IAAI,SAAS,QAAQ;KAAG;KACjF,MAAM,cAAc,OAAO,QAAQ;IACrC,CAAC;GACH;GACA,OAAO;EACT,CAAC;CACH;CAEA,MAAM,YACJ,OACA,EAAE,QAAQ,OAC6D;EACvE,OAAO,aAAa,KAAKJ,MAAM,OAAO,MAAM,GAAG,OAAM,aAAY;GAC/D,MAAM,KAAKD,WAAW,0BAA0B;IAC9C,GAAG,aAAa,KAAK;IACrB,YAAY,SAAS,KAAI,YAAW,gCAAgC,QAAQ,SAAS,CAAC;IACtF;GACF,CAAC;EACH,CAAC;CACH;AACF;AAEA,SAAS,qBACP,UACA,MACA,QACS;CACT,OACE,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,MAAM,QAC/C,SAAS,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM,MAAM,QACvD,oBAAoB,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,MAAM;AAE/D;AAEA,SAAS,cACP,OACA,UACA,MACA,SACA;CACA,MAAM,UAAU,+BAA+B,SAAS,WAAW;CACnE,MAAM,WAAW,iCAAiC,SAAS,IAAI,SAAS,iBAAiB;CACzF,OAAO;EACL,KAAK,oBAAoB,MAAM,kBAAkB,QAAQ;EACzD,MAAM;EACN,YAAY,SAAS;EACrB,YAAY,SAAS;EACrB,YAAY,SAAS;EACrB,OAAO,MAAM,SAAS,QAAQ;EAC9B,QAAQ,SAAS,WAAW,MAAM,GAAG,GAAG,KAAK,QAAQ;EACrD,cAAc,oBAAoB,QAAQ;EAC1C,aAAa,SAAS;EACtB,UAAU,QAAQ;EAClB,YAAY,kBAAkB,QAAQ,CAAC,CAAC,YAAY;EACpD,MAAM,YAAY,KAAA;EAClB,UAAU,SAAS,UAAU;EAC7B,QAAQ,gBAAgB,UAAU,IAAI;CACxC;AACF;AAEA,SAAS,qBAAqB,EAAE,MAAM,WAA4B,QAAyB;CACzF,OACE,KAAK,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,KACxC,QAAQ,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,KAC1C,QAAQ,OAAO,aAAa,YAAY,CAAC,CAAC,SAAS,MAAM,MAAM;AAEnE;AAEA,SAAS,cAAc,OAAuB,EAAE,SAAS,SAAS,MAAM,UAAU,WAA4B;CAC5G,MAAM,aAAa,QAAQ,OAAO;CAClC,OAAO;EACL,KAAK,oBAAoB,MAAM,kBAAkB,QAAQ;EACzD,MAAM;EACN,WAAW,QAAQ;EACnB,YAAY;EACZ,YAAY,QAAQ;EACpB,OAAO,KAAK;EACZ,QAAQ,QAAQ,KAAK,MAAM,GAAG,GAAG;EACjC,UAAU,QAAQ,OAAO;EACzB,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;EACnC,YAAY,QAAQ,WAAW,YAAY;EAC3C,MAAM,YAAY,KAAA;EAClB,UAAU,SAAS,UAAU;EAC7B,QAAQ;GACN,MAAM;GACN,YAAY,QAAQ;GACpB,OAAO,cAAc,IAAI;GACzB,WAAW,QAAQ;EACrB;CACF;AACF;AAEA,SAAgB,aAAa,OAAoF;CAC/G,OAAO;EAAE,OAAO,MAAM;EAAO,kBAAkB,MAAM;EAAkB,QAAQ,MAAM;CAAO;AAC9F"}
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":";;;;;;;;AAuFA,MAAa,iBAAiB;AAE9B,MAAM,yBAAyB;;;;;;AAa/B,gBAAuB,YACrB,YACA,YACA,QAC8B;CAC9B,IAAI,SAAS;CACb,KAAK,IAAI,UAAU,GAAG,WAAW,wBAAwB,WAAW,GAAG;EACrE,MAAM,EAAE,MAAM,YAAY,MAAM,WAAW,MAAM;EACjD,MAAM,OAAO,KAAK,GAAG,EAAE;EACvB,IAAI,CAAC,MAAM;EACX,MAAM,WAAW,WAAW,IAAI;EAChC,MAAM;GAAE;GAAM,GAAI,WAAW,YAAY,yBAAyB,EAAE,cAAc,SAAS,IAAI,CAAC;EAAG;EACnG,IAAI,CAAC,SAAS;EACd,SAAS;CACX;AACF;;AAGA,eAAsB,YACpB,SACA,OACA,WAC8B;CAC9B,MAAM,UAA4B,CAAC;CACnC,WAAW,MAAM,SAAS,SAAS;EACjC,KAAK,MAAM,SAAS,MAAM,UAAU,MAAM,IAAI,GAAG;GAC/C,IAAI,QAAQ,WAAW,OAAO,OAAO;IAAE;IAAS,SAAS;GAAK;GAC9D,QAAQ,KAAK,KAAK;EACpB;EACA,IAAI,MAAM,cAAc,OAAO;GAAE;GAAS,SAAS;GAAM,cAAc,MAAM;EAAa;CAC5F;CACA,OAAO;EAAE;EAAS,SAAS;CAAM;AACnC;;AAGA,eAAsB,aACpB,SACA,WACuE;CACvE,WAAW,MAAM,SAAS,SAAS;EACjC,MAAM,UAAU,MAAM,IAAI;EAC1B,IAAI,MAAM,cAAc,OAAO;GAAE,SAAS;GAAM,cAAc,MAAM;EAAa;CACnF;CACA,OAAO,EAAE,SAAS,MAAM;AAC1B;AAEA,SAAgB,oBAAoB,UAAiD;CACnF,OAAO,OAAO,SAAS,SAAS,SAAS,WAAW,SAAS,SAAS,KAAK,MAAM,GAAG,EAAE,IAAI;AAC5F;AAEA,SAAgB,YAAY,MAA4B,SAA6D;CACnH,IAAI,SAAS,YAAY,OAAO,SAAS,UAAU;CACnD,IAAI,SAAS,UAAU,OAAO,YAAY,KAAA;CAC1C,OAAO,SAAS,UAAU;AAC5B;AAEA,SAAS,gBAAgB,UAAyC,MAA+B;CAC/F,IAAI,CAAC,MAAM,OAAO,EAAE,MAAM,QAAiB;CAC3C,MAAM,OAAO,OAAO,SAAS,SAAS,SAAS,WAAW,SAAS,SAAS,OAAO,KAAA;CACnF,MAAM,UAAU,OAAO,KAAK,SAAS,QAAQ,KAAA;CAC7C,IAAI,SACF,OAAO;EACL,MAAM;EACN,WAAW,QAAQ;EACnB,UAAU,QAAQ;CACpB;CAEF,OAAO;EACL,MAAM;EACN,YAAY,KAAK;EACjB,OAAO,cAAc,IAAI;CAC3B;AACF;AAEA,SAAgB,cAAc,MAAsC;CAElE,OADe,KAAK,gBAAgB,kBAAkB,YAAY,KAAK,eAAe,SAAS,iBAC/E,WAAW;AAC7B;AAcA,IAAa,4BAAb,MAAoE;CAClE;CACA;CACA;CAEA,YAAY,EAAE,aAA8C,MAA6B;EACvF,KAAKA,aAAa;EAClB,KAAKC,QAAQ;EACb,KAAK,OAAO,KAAK;CACnB;CAEA,MAAM,OAAO,OAAiD;EAC5D,MAAM,CAAC,OAAO,cAAc,iBAAiB,MAAM,QAAQ,IAAI;GAC7D,KAAKD,WAAW,iCAAiC;IAC/C,OAAO,MAAM;IACb,kBAAkB,MAAM;IACxB,UAAU,CAAC,KAAKC,MAAM,MAAM;GAC9B,CAAC;GACD,KAAKD,WAAW,uBAAuB;IAAE,GAAG,aAAa,KAAK;IAAG,MAAM,KAAK;GAAK,CAAC;GAClF,KAAKA,WAAW,uBAAuB;IAAE,GAAG,aAAa,KAAK;IAAG,MAAM,KAAK;IAAM,OAAO;GAAW,CAAC;EACvG,CAAC;EACD,OAAO;GACL,MAAM,KAAK,IAAI,GAAG,QAAQ,aAAa;GACvC,QAAQ,KAAK,IAAI,GAAG,QAAQ,YAAY;EAC1C;CACF;CAEA,MAAM,OAAO,OAAwD;EAOnE,MAAM,UAAS,MANI,KAAKA,WAAW,yBAAyB;GAC1D,OAAO,MAAM;GACb,kBAAkB,MAAM;GACxB,QAAQ,KAAKC,MAAM;GACnB,OAAO;EACT,CAAC,EAAA,CACmB,UAAU;EAC9B,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,WAAW,KAAKA,MAAM,SAAS,MAAM;EAC3C,MAAM,WAAW,MAAM,KAAKD,WAAW,sBAAsB;GAC3D,GAAG,aAAa,KAAK;GACrB,YAAY,CAAC,QAAQ;EACvB,CAAC;EACD,OAAO;GACL,KAAK,oBAAoB,MAAM,kBAAkB,QAAQ;GACzD,IAAI,KAAKC,MAAM,WAAW,MAAM;GAChC,QAAQ,SAAS,WAAW;EAC9B;CACF;CAEA,MAAM,OAAuB,QAAkC;EAC7D,OAAO,YACL,OAAM,WAAU;GACd,MAAM,OAAO,MAAM,KAAKD,WAAW,yBAAyB;IAC1D,OAAO,MAAM;IACb,kBAAkB,MAAM;IACxB,QAAQ,KAAKC,MAAM;IACnB,QAAQ;IACR,OAAA;GACF,CAAC;GACD,OAAO;IAAE,MAAM,KAAK;IAAW,SAAS,KAAK;GAAQ;EACvD,IACA,cAAa;GAAE,YAAY,KAAKA,MAAM,WAAW,QAAQ;GAAG,IAAI,SAAS;EAAG,IAC5E,MACF;CACF;CAEA,MAAM,KAAK,OAAuB,EAAE,MAAM,QAAQ,QAAQ,SAA0D;EAClH,OAAO,YAAY,KAAKC,MAAM,OAAO,MAAM,GAAG,OAAO,OAAM,cAAa;GACtE,MAAM,CAAC,cAAc,YAAY,MAAM,QAAQ,IAAI,CACjD,KAAKC,aAAa,OAAO,SAAS,GAClC,KAAKC,aAAa,OAAO,SAAS,CACpC,CAAC;GACD,MAAM,UAA4B,CAAC;GACnC,KAAK,MAAM,YAAY,WAAW;IAChC,MAAM,WAAW,KAAKH,MAAM,SAAS,QAAQ;IAC7C,MAAM,UAAU,aAAa,IAAI,oBAAoB,MAAM,kBAAkB,QAAQ,CAAC;IACtF,IAAI,CAAC,YAAY,MAAM,OAAO,GAAG;IACjC,MAAM,OAAO,SAAS,aAAa,SAAS,IAAI,SAAS,UAAU,IAAI,KAAA;IACvE,IAAI,UAAU,CAAC,KAAKA,MAAM,QAAQ,UAAU,MAAM,MAAM,GAAG;IAC3D,MAAM,aAAa,KAAKA,MAAM,WAAW,QAAQ;IACjD,QAAQ,KAAK;KACX;KACA,cAAc;MAAE;MAAY,IAAI,SAAS;KAAG;KAC5C,MAAM;MACJ,KAAK,oBAAoB,MAAM,kBAAkB,QAAQ;MACzD,MAAM,KAAK;MACX,YAAY,SAAS;MACrB,YAAY,SAAS;MACrB,YAAY,SAAS;MACrB,OAAO,KAAKA,MAAM,MAAM,UAAU,IAAI;MACtC,QAAQ,KAAKA,MAAM,OAAO,UAAU,IAAI;MACxC,cAAc,oBAAoB,QAAQ;MAC1C,GAAG,KAAKA,MAAM,QAAQ,QAAQ;MAC9B,YAAY,WAAW,YAAY;MACnC,MAAM,YAAY,KAAA;MAClB,UAAU,SAAS,UAAU;MAC7B,QAAQ,gBAAgB,UAAU,IAAI;KACxC;IACF,CAAC;GACH;GACA,OAAO;EACT,CAAC;CACH;CAEA,MAAME,aACJ,OACA,WACqD;EACrD,MAAM,WAAW,MAAM,KAAKH,WAAW,sBAAsB;GAC3D,GAAG,aAAa,KAAK;GACrB,YAAY,UAAU,KAAI,aAAY,KAAKC,MAAM,SAAS,QAAQ,CAAC;EACrE,CAAC;EACD,OAAO,IAAI,IAAI,SAAS,KAAI,YAAW,CAAC,oBAAoB,MAAM,kBAAkB,OAAO,GAAG,OAAO,CAAC,CAAC;CACzG;CAEA,MAAMG,aACJ,OACA,WACmC;EACnC,MAAM,QAAQ,MAAM,KAAKJ,WAAW,UAAU;GAC5C,OAAO,MAAM;GACb,kBAAkB,MAAM;GACxB,KAAK,UAAU,SAAQ,aAAa,SAAS,aAAa,CAAC,SAAS,UAAU,IAAI,CAAC,CAAE;EACvF,CAAC;EACD,OAAO,IAAI,IAAI,MAAM,KAAI,SAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;CACnD;CAEA,MAAM,YACJ,OACA,EAAE,QAAQ,OAC6D;EACvE,OAAO,aAAa,KAAKE,MAAM,OAAO,MAAM,GAAG,OAAM,cAAa;GAChE,MAAM,KAAKF,WAAW,0BAA0B;IAC9C,GAAG,aAAa,KAAK;IACrB,YAAY,UAAU,KAAI,aAAY,KAAKC,MAAM,SAAS,QAAQ,CAAC;IACnE;GACF,CAAC;EACH,CAAC;CACH;AACF;AAEA,MAAa,8BAAqD;CAChE,MAAM;CACN,QAAQ;CACR,WAAU,aAAY,iCAAiC,SAAS,IAAI,SAAS,iBAAiB;CAC9F,aAAY,aAAY,SAAS,eAAe,SAAS;CACzD,QAAQ,UAAU,SAAS,MAAM,SAAS,+BAA+B,SAAS,WAAW,CAAC,CAAC;CAC/F,SAAQ,aAAY,SAAS,WAAW,MAAM,GAAG,GAAG,KAAK,+BAA+B,SAAS,WAAW,CAAC,CAAC;CAC9G,QAAO,cAAa;EAClB,aAAa,SAAS;EACtB,UAAU,+BAA+B,SAAS,WAAW,CAAC,CAAC;CACjE;CACA,UAAU,UAAU,MAAM,WACxB,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,MAAM,QAC/C,SAAS,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM,MAAM,QACvD,oBAAoB,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,MAAM;AAC/D;AAUA,SAAS,yBAAyB,KAA2D;CAC3F,OAAO,IAAI;AACb;AAEA,IAAa,qCAAb,MAA6E;CAC3E,OAAgB;CAChB;CAEA,YAAY,EAAE,aAA8C;EAC1D,KAAKD,aAAa;CACpB;CAEA,MAAM,OAAO,OAAiD;EAC5D,IAAI,OAAO;EACX,IAAI,SAAS;EACb,MAAM,UAAU,aACd,WACE,KAAKA,WAAW,0BAA0B;GAAE,GAAG;GAAO,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAAI,OAAA;EAAsB,CAAC,IAC9G,SAAQ;GAAE,YAAY,IAAI;GAAW,IAAI,IAAI;EAAG,EAClD;EACA,WAAW,MAAM,QAAQ,SAAS;GAChC,MAAM,aAAa,KAAK,KAAK,KAAI,QAC/B,0CAA0C,IAAI,YAAY,IAAI,UAAU,CAC1E;GACA,MAAM,WAAW,MAAM,KAAKA,WAAW,sBAAsB;IAAE,GAAG;IAAO;GAAW,CAAC;GACrF,MAAM,aAAa,IAAI,IACrB,SAAS,KAAI,YAAW,CAAC,GAAG,QAAQ,SAAS,IAAI,QAAQ,cAAc,OAAO,CAAU,CAC1F;GACA,KAAK,MAAM,OAAO,KAAK,MAAM;IAC3B,MAAM,UAAU,WAAW,IAAI,GAAG,IAAI,WAAW,IAAI,IAAI,YAAY;IACrE,IAAI,SAAS,UAAU,YAAY;IACnC,QAAQ;IACR,IAAI,CAAC,SAAS,UAAU;GAC1B;EACF;EACA,OAAO;GAAE;GAAM;EAAO;CACxB;CAEA,MAAM,OAAO,OAAwD;EAEnE,MAAM,UAAS,MADI,KAAKA,WAAW,0BAA0B;GAAE,GAAG;GAAO,OAAO;EAAE,CAAC,EAAA,CAC/D,KAAK;EACzB,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,WAAW,0CAA0C,OAAO,YAAY,OAAO,UAAU;EAC/F,MAAM,WAAW,MAAM,KAAKA,WAAW,sBAAsB;GAAE,GAAG;GAAO,YAAY,CAAC,QAAQ;EAAE,CAAC;EACjG,OAAO;GACL,KAAK,oBAAoB,MAAM,kBAAkB,QAAQ;GACzD,IAAI,OAAO;GACX,QAAQ,SAAS,WAAW;EAC9B;CACF;CAEA,KAAK,OAAuB,MAAuD;EACjF,OAAO,YACL,aACE,WACE,KAAKA,WAAW,0BAA0B;GAAE,GAAG;GAAO,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAAI,OAAA;EAAsB,CAAC,IAC9G,SAAQ;GAAE,YAAY,IAAI;GAAW,IAAI,IAAI;EAAG,IAChD,KAAK,MACP,GACA,KAAK,OACL,OAAM,SAAQ;GACZ,MAAM,aAAa,KAAK,KAAI,QAAO,0CAA0C,IAAI,YAAY,IAAI,UAAU,CAAC;GAC5G,MAAM,WAAW,MAAM,KAAKA,WAAW,sBAAsB;IAAE,GAAG;IAAO;GAAW,CAAC;GACrF,MAAM,aAAa,IAAI,IACrB,SAAS,KAAI,YAAW,CAAC,GAAG,QAAQ,SAAS,IAAI,QAAQ,cAAc,OAAO,CAAU,CAC1F;GACA,OAAO,KAAK,SAAQ,QAAO;IACzB,MAAM,UAAU,yBAAyB,GAAG;IAC5C,MAAM,UAAU,WAAW,IAAI,GAAG,IAAI,WAAW,IAAI,IAAI,YAAY;IACrE,IAAI,CAAC,YAAY,KAAK,MAAM,OAAO,GAAG,OAAO,CAAC;IAC9C,MAAM,OAAO,GAAG,QAAQ,MAAM,GAAG,QAAQ,WAAW,YAAY;IAChE,IAAI,KAAK,UAAU,CAAC,KAAK,SAAS,KAAK,MAAM,GAAG,OAAO,CAAC;IACxD,MAAM,WAAW,0CAA0C,IAAI,YAAY,IAAI,UAAU;IACzF,OAAO,CACL;KACE,KAAK,oBAAoB,MAAM,kBAAkB,QAAQ;KACzD,YAAY,IAAI;KAChB,cAAc;MAAE,YAAY,IAAI;MAAW,IAAI,IAAI;KAAG;KACtD;KACA,MAAM;MACJ,KAAK,oBAAoB,MAAM,kBAAkB,QAAQ;MACzD,MAAM,KAAK;MACX,YAAY,IAAI;MAChB,YAAY,IAAI;MAChB,cAAc,QAAQ;MACtB,UAAU,QAAQ;MAClB,OAAO,QAAQ;MACf,QAAQ,QAAQ;MAChB,OAAO,QAAQ;MACf,iBAAiB,QAAQ;MACzB,YAAY,QAAQ;MACpB,YAAY,IAAI,UAAU,YAAY;MACtC,MAAM,QAAQ,OAAO;MACrB,UAAU,SAAS,UAAU;MAC7B,QAAQ,QAAQ,aACZ;OAAE,MAAM;OAAa,YAAY,QAAQ;OAAY,OAAO;MAAO,IACnE,EAAE,MAAM,QAAQ;KACtB;IACF,CACF;GACF,CAAC;EACH,CACF;CACF;CAEA,YACE,OACA,MACuE;EACvE,OAAO,aACL,aACE,WACE,KAAKA,WAAW,0BAA0B;GAAE,GAAG;GAAO,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAAI,OAAA;EAAsB,CAAC,IAC9G,SAAQ;GAAE,YAAY,IAAI;GAAW,IAAI,IAAI;EAAG,IAChD,KAAK,MACP,IACA,SACE,KAAKA,WAAW,0BAA0B;GACxC,GAAG;GACH,YAAY,KAAK,KAAI,QAAO,0CAA0C,IAAI,YAAY,IAAI,UAAU,CAAC;GACrG,KAAK,KAAK;EACZ,CAAC,CACL;CACF;AACF;AAEA,IAAa,2BAAb,MAAmE;CACjE,OAAgB;CAChB;CACA;CAEA,YAAY,EAAE,WAAW,YAAgF;EACvG,KAAKA,aAAa;EAClB,KAAKK,YAAY;CACnB;CAEA,MAAM,OAAuB,QAAkC;EAC7D,OAAO,YACL,OAAM,WAAU;GACd,MAAM,OAAO,MAAM,KAAKA,UAAU,oBAAoB;IACpD,GAAG,aAAa,KAAK;IACrB,GAAI,SAAS,EAAE,QAAQ,OAAO,IAAI,CAAC;IACnC,OAAO;GACT,CAAC;GACD,OAAO;IAAE,MAAM,KAAK,MAAM,GAAA,EAAiB;IAAG,SAAS,KAAK,SAAA;GAAwB;EACtF,IACA,aAAY;GAAE,YAAY,QAAQ;GAAY,IAAI,QAAQ;EAAG,IAC7D,MACF;CACF;;;;;CAMA,MAAMC,SAAS,OAAuB,UAA4D;EAChG,MAAM,CAAC,UAAU,UAAU,SAAS,MAAM,QAAQ,IAAI;GACpD,KAAKN,WAAW,sBAAsB;IACpC,GAAG,aAAa,KAAK;IACrB,YAAY,SAAS,KAAI,YAAW,gCAAgC,QAAQ,SAAS,CAAC;GACxF,CAAC;GACD,KAAKK,UAAU,UAAU;IACvB,OAAO,MAAM;IACb,KAAK,CAAC,GAAG,IAAI,IAAI,SAAS,KAAI,YAAW,QAAQ,SAAS,CAAC,CAAC;GAC9D,CAAC;GACD,KAAKL,WAAW,UAAU;IACxB,OAAO,MAAM;IACb,kBAAkB,MAAM;IACxB,KAAK,CAAC,GAAG,IAAI,IAAI,SAAS,KAAI,YAAW,QAAQ,UAAU,CAAC,CAAC;GAC/D,CAAC;EACH,CAAC;EACD,MAAM,eAAe,IAAI,IACvB,SAAS,KAAI,YAAW,CAAC,oBAAoB,MAAM,kBAAkB,OAAO,GAAG,OAAO,CAAC,CACzF;EACA,MAAM,cAAc,IAAI,IAAI,SAAS,KAAI,YAAW,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC;EAC1E,MAAM,WAAW,IAAI,IAAI,MAAM,KAAI,SAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;EAE3D,MAAM,WAA8B,CAAC;EACrC,KAAK,MAAM,WAAW,UAAU;GAC9B,MAAM,UAAU,YAAY,IAAI,QAAQ,SAAS;GACjD,MAAM,OAAO,SAAS,IAAI,QAAQ,UAAU;GAC5C,IAAI,CAAC,WAAW,QAAQ,aAAa,CAAC,MAAM;GAC5C,MAAM,WAAW,gCAAgC,QAAQ,SAAS;GAClE,SAAS,KAAK;IACZ;IACA;IACA;IACA;IACA,SAAS,aAAa,IAAI,oBAAoB,MAAM,kBAAkB,QAAQ,CAAC;GACjF,CAAC;EACH;EACA,OAAO;CACT;;;;;;;CAQA,MAAM,OAAO,OAAiD;EAC5D,IAAI,OAAO;EACX,IAAI,SAAS;EACb,WAAW,MAAM,SAAS,KAAKE,MAAM,KAAK,GACxC,KAAK,MAAM,EAAE,aAAa,MAAM,KAAKI,SAAS,OAAO,MAAM,IAAI,GAAG;GAChE,IAAI,SAAS,UAAU,YAAY,QAAQ;GAC3C,IAAI,YAAY,KAAA,GAAW,UAAU;EACvC;EAEF,OAAO;GAAE;GAAM;EAAO;CACxB;CAEA,MAAM,OAAO,OAAwD;EACnE,MAAM,WAAW,MAAM,KAAKD,UAAU,oBAAoB;GAAE,GAAG,aAAa,KAAK;GAAG,OAAO;EAAG,CAAC;EAC/F,MAAM,UAAU,MAAM,KAAKC,SAAS,OAAO,QAAQ,EAAA,CAAG;EACtD,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO;GACL,KAAK,oBAAoB,MAAM,kBAAkB,OAAO,QAAQ;GAChE,IAAI,OAAO,QAAQ;GACnB,QAAQ,OAAO,YAAY,KAAA;EAC7B;CACF;CAEA,MAAM,KAAK,OAAuB,EAAE,MAAM,QAAQ,QAAQ,SAA0D;EAClH,OAAO,YAAY,KAAKJ,MAAM,OAAO,MAAM,GAAG,OAAO,OAAM,aAAY;GACrE,MAAM,UAA4B,CAAC;GACnC,KAAK,MAAM,YAAY,MAAM,KAAKI,SAAS,OAAO,QAAQ,GAAG;IAC3D,IAAI,CAAC,YAAY,MAAM,SAAS,OAAO,GAAG;IAC1C,IAAI,UAAU,CAAC,qBAAqB,UAAU,MAAM,GAAG;IACvD,QAAQ,KAAK;KACX,YAAY,SAAS,QAAQ;KAC7B,cAAc;MAAE,YAAY,SAAS,QAAQ;MAAY,IAAI,SAAS,QAAQ;KAAG;KACjF,MAAM,cAAc,OAAO,QAAQ;IACrC,CAAC;GACH;GACA,OAAO;EACT,CAAC;CACH;CAEA,MAAM,YACJ,OACA,EAAE,QAAQ,OAC6D;EACvE,OAAO,aAAa,KAAKJ,MAAM,OAAO,MAAM,GAAG,OAAM,aAAY;GAC/D,MAAM,KAAKF,WAAW,0BAA0B;IAC9C,GAAG,aAAa,KAAK;IACrB,YAAY,SAAS,KAAI,YAAW,gCAAgC,QAAQ,SAAS,CAAC;IACtF;GACF,CAAC;EACH,CAAC;CACH;AACF;AAEA,SAAS,qBAAqB,EAAE,MAAM,WAA4B,QAAyB;CACzF,OACE,KAAK,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,KACxC,QAAQ,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,KAC1C,QAAQ,OAAO,aAAa,YAAY,CAAC,CAAC,SAAS,MAAM,MAAM;AAEnE;AAEA,SAAS,cAAc,OAAuB,EAAE,SAAS,SAAS,MAAM,UAAU,WAA4B;CAC5G,MAAM,aAAa,QAAQ,OAAO;CAClC,OAAO;EACL,KAAK,oBAAoB,MAAM,kBAAkB,QAAQ;EACzD,MAAM;EACN,WAAW,QAAQ;EACnB,YAAY;EACZ,YAAY,QAAQ;EACpB,OAAO,KAAK;EACZ,QAAQ,QAAQ,KAAK,MAAM,GAAG,GAAG;EACjC,UAAU,QAAQ,OAAO;EACzB,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;EACnC,YAAY,QAAQ,WAAW,YAAY;EAC3C,MAAM,YAAY,KAAA;EAClB,UAAU,SAAS,UAAU;EAC7B,QAAQ;GACN,MAAM;GACN,YAAY,QAAQ;GACpB,OAAO,cAAc,IAAI;GACzB,WAAW,QAAQ;EACrB;CACF;AACF;AAEA,SAAgB,aAAa,OAAoF;CAC/G,OAAO;EAAE,OAAO,MAAM;EAAO,kBAAkB,MAAM;EAAkB,QAAQ,MAAM;CAAO;AAC9F"}
@@ -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;AAGlC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,UAAU,0BAA0B;IAClC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,EAAE,uBAAuB,CAAC;IAClC,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,GAAG;QAAE,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CACpF;AAmMD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,0BAA0B,GAAG,QAAQ,EAAE,CA8HzF"}
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,9 +1,11 @@
1
1
  import { factoryAttentionKey } from "../storage/domains/work-items/base.js";
2
- import { AutomationFailedAttentionProvider, MentionAttentionProvider, 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;
8
+ const SUPERVISOR_FINDING_KEY_RE = /^[a-z0-9:_-]{1,256}$/i;
7
9
  const DEFAULT_PAGE_SIZE = 25;
8
10
  const MAX_PAGE_SIZE = 50;
9
11
  function parseAttentionView(raw) {
@@ -16,10 +18,15 @@ function parseAttentionLimit(raw) {
16
18
  return Math.max(1, Math.min(MAX_PAGE_SIZE, parsed));
17
19
  }
18
20
  function isAttentionKind(value) {
19
- return value === "automation-failed" || value === "mention" || value === "activity";
21
+ return value === "automation-failed" || value === "automation-proposed" || value === "mention" || value === "activity" || value === "supervisor-finding";
20
22
  }
21
23
  /** Kinds the sidebar badge and the notification sound answer to. */
22
- const BADGE_KINDS = /* @__PURE__ */ new Set(["automation-failed", "mention"]);
24
+ const BADGE_KINDS = /* @__PURE__ */ new Set([
25
+ "automation-failed",
26
+ "automation-proposed",
27
+ "mention",
28
+ "supervisor-finding"
29
+ ]);
23
30
  function parseAttentionTier(raw) {
24
31
  if (raw === void 0) return "all";
25
32
  return raw === "badge" || raw === "activity" ? raw : void 0;
@@ -138,7 +145,8 @@ function receiptRoute(dependencies, verb, action) {
138
145
  const kind = context.req.param("kind");
139
146
  const sourceId = context.req.param("sourceId");
140
147
  const occurrence = parseOccurrence(context.req.param("occurrence"));
141
- if (!kind || !isAttentionKind(kind) || !sourceId || !UUID_RE.test(sourceId) || occurrence === void 0) return context.json({ error: "invalid_attention_item" }, 422);
148
+ const validSourceId = kind === "supervisor-finding" ? SUPERVISOR_FINDING_KEY_RE.test(sourceId) : UUID_RE.test(sourceId);
149
+ if (!kind || !isAttentionKind(kind) || !sourceId || !validSourceId || occurrence === void 0) return context.json({ error: "invalid_attention_item" }, 422);
142
150
  await dependencies.workItems.ensureReady();
143
151
  const receipt = await dependencies.workItems.setAttentionReceipt({
144
152
  orgId: resolved.orgId,
@@ -165,7 +173,9 @@ function receiptRoute(dependencies, verb, action) {
165
173
  function buildAttentionRoutes(dependencies) {
166
174
  const { workItems, comments } = dependencies;
167
175
  const providers = [
168
- new AutomationFailedAttentionProvider({ workItems }),
176
+ new DecisionAttentionProvider({ workItems }, failedDecisionAttentionSpec),
177
+ new DecisionAttentionProvider({ workItems }, proposedDecisionAttentionSpec),
178
+ new SupervisorFindingAttentionProvider({ workItems }),
169
179
  new MentionAttentionProvider({
170
180
  workItems,
171
181
  comments
@@ -194,32 +204,24 @@ function buildAttentionRoutes(dependencies) {
194
204
  const search = context.req.query("search")?.trim().toLowerCase().slice(0, 200);
195
205
  const limit = parseAttentionLimit(context.req.query("limit"));
196
206
  const active = providers.filter((provider) => kindInTier(tier, provider.kind) && (!before || before.has(provider.kind)));
197
- const [approvalCount, summaries, pages] = await Promise.all([
198
- workItems.countDeferredDecisionsByStatuses({
199
- orgId: resolved.orgId,
200
- factoryProjectId: resolved.factoryProjectId,
201
- statuses: ["proposed"]
202
- }),
203
- Promise.all(providers.map(async (provider) => ({
204
- kind: provider.kind,
205
- counts: await provider.counts(resolved),
206
- latest: await provider.latest(resolved)
207
- }))),
208
- Promise.all(active.map(async (provider) => ({
209
- kind: provider.kind,
210
- incoming: before?.get(provider.kind),
211
- result: await provider.page(resolved, {
212
- view,
213
- ...search ? { search } : {},
214
- before: before?.get(provider.kind),
215
- limit
216
- })
217
- })))
218
- ]);
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
+ })))]);
219
221
  const badge = summaries.filter((summary) => BADGE_KINDS.has(summary.kind));
220
222
  const activity = summaries.filter((summary) => !BADGE_KINDS.has(summary.kind));
221
223
  const sum = (rows, field) => rows.reduce((total, row) => total + row.counts[field], 0);
222
- const openCount = sum(badge, "open") + approvalCount;
224
+ const openCount = sum(badge, "open");
223
225
  const unreadCount = sum(badge, "unread");
224
226
  const latests = badge.map((summary) => summary.latest);
225
227
  const unreadLatests = latests.filter((latest) => latest?.unread ?? false);
@@ -228,8 +230,7 @@ function buildAttentionRoutes(dependencies) {
228
230
  return context.json({
229
231
  items: merged.items,
230
232
  openCount,
231
- approvalCount,
232
- badgeCount: unreadCount + approvalCount,
233
+ badgeCount: unreadCount,
233
234
  unreadCount,
234
235
  activityUnreadCount: sum(activity, "unread"),
235
236
  latestOccurrenceKey: latest?.key ?? null,