@mastra/factory 0.10.2-alpha.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/factory.d.ts.map +1 -1
  3. package/dist/factory.js +18 -2
  4. package/dist/factory.js.map +1 -1
  5. package/dist/routes/attention-providers.d.ts +101 -0
  6. package/dist/routes/attention-providers.d.ts.map +1 -0
  7. package/dist/routes/attention-providers.js +384 -0
  8. package/dist/routes/attention-providers.js.map +1 -0
  9. package/dist/routes/attention.d.ts +11 -9
  10. package/dist/routes/attention.d.ts.map +1 -1
  11. package/dist/routes/attention.js +143 -193
  12. package/dist/routes/attention.js.map +1 -1
  13. package/dist/routes/surface.d.ts +2 -0
  14. package/dist/routes/surface.d.ts.map +1 -1
  15. package/dist/routes/surface.js +3 -1
  16. package/dist/routes/surface.js.map +1 -1
  17. package/dist/routes/work-items.d.ts +3 -0
  18. package/dist/routes/work-items.d.ts.map +1 -1
  19. package/dist/routes/work-items.js +3 -1
  20. package/dist/routes/work-items.js.map +1 -1
  21. package/dist/rules/dispatcher.d.ts +3 -0
  22. package/dist/rules/dispatcher.d.ts.map +1 -1
  23. package/dist/rules/dispatcher.js +9 -1
  24. package/dist/rules/dispatcher.js.map +1 -1
  25. package/dist/rules/start-coordinator.d.ts +2 -1
  26. package/dist/rules/start-coordinator.d.ts.map +1 -1
  27. package/dist/rules/start-coordinator.js +10 -2
  28. package/dist/rules/start-coordinator.js.map +1 -1
  29. package/dist/storage/domains/audit/domain.d.ts.map +1 -1
  30. package/dist/storage/domains/audit/domain.js +2 -5
  31. package/dist/storage/domains/audit/domain.js.map +1 -1
  32. package/dist/storage/domains/channel-identity/base.d.ts +4 -0
  33. package/dist/storage/domains/channel-identity/base.d.ts.map +1 -1
  34. package/dist/storage/domains/channel-identity/base.js +15 -0
  35. package/dist/storage/domains/channel-identity/base.js.map +1 -1
  36. package/dist/storage/domains/comments/actor.d.ts +51 -0
  37. package/dist/storage/domains/comments/actor.d.ts.map +1 -0
  38. package/dist/storage/domains/comments/actor.js +38 -0
  39. package/dist/storage/domains/comments/actor.js.map +1 -0
  40. package/dist/storage/domains/comments/base.d.ts +230 -0
  41. package/dist/storage/domains/comments/base.d.ts.map +1 -0
  42. package/dist/storage/domains/comments/base.js +490 -0
  43. package/dist/storage/domains/comments/base.js.map +1 -0
  44. package/dist/storage/domains/comments/domain.d.ts +125 -0
  45. package/dist/storage/domains/comments/domain.d.ts.map +1 -0
  46. package/dist/storage/domains/comments/domain.js +270 -0
  47. package/dist/storage/domains/comments/domain.js.map +1 -0
  48. package/dist/storage/domains/comments/feed-context.d.ts +20 -0
  49. package/dist/storage/domains/comments/feed-context.d.ts.map +1 -0
  50. package/dist/storage/domains/comments/feed-context.js +74 -0
  51. package/dist/storage/domains/comments/feed-context.js.map +1 -0
  52. package/dist/storage/domains/comments/feed-sync.d.ts +38 -0
  53. package/dist/storage/domains/comments/feed-sync.d.ts.map +1 -0
  54. package/dist/storage/domains/comments/feed-sync.js +1 -0
  55. package/dist/storage/domains/comments/parse.d.ts +22 -0
  56. package/dist/storage/domains/comments/parse.d.ts.map +1 -0
  57. package/dist/storage/domains/comments/parse.js +90 -0
  58. package/dist/storage/domains/comments/parse.js.map +1 -0
  59. package/dist/storage/domains/comments/routes.d.ts +22 -0
  60. package/dist/storage/domains/comments/routes.d.ts.map +1 -0
  61. package/dist/storage/domains/comments/routes.js +248 -0
  62. package/dist/storage/domains/comments/routes.js.map +1 -0
  63. package/dist/storage/domains/comments/schema.d.ts +4 -0
  64. package/dist/storage/domains/comments/schema.d.ts.map +1 -0
  65. package/dist/storage/domains/comments/schema.js +135 -0
  66. package/dist/storage/domains/comments/schema.js.map +1 -0
  67. package/dist/storage/domains/comments/wire.d.ts +40 -0
  68. package/dist/storage/domains/comments/wire.d.ts.map +1 -0
  69. package/dist/storage/domains/comments/wire.js +37 -0
  70. package/dist/storage/domains/comments/wire.js.map +1 -0
  71. package/dist/storage/domains/work-items/base.d.ts +14 -10
  72. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  73. package/dist/storage/domains/work-items/base.js +109 -22
  74. package/dist/storage/domains/work-items/base.js.map +1 -1
  75. package/factory-skills/factory-rereview/SKILL.md +20 -12
  76. package/factory-skills/factory-review/SKILL.md +17 -12
  77. package/package.json +7 -7
@@ -1,14 +1,10 @@
1
- import { factoryAttentionKey, factoryDecisionAttentionIdentity } from "../storage/domains/work-items/base.js";
2
- import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
1
+ import { factoryAttentionKey } from "../storage/domains/work-items/base.js";
2
+ import { AutomationFailedAttentionProvider, MentionAttentionProvider, factoryDecisionType } from "./attention-providers.js";
3
3
  import { registerApiRoute } from "@mastra/core/server";
4
4
  //#region src/routes/attention.ts
5
5
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
6
  const DEFAULT_PAGE_SIZE = 25;
7
7
  const MAX_PAGE_SIZE = 50;
8
- const MAX_RECEIPT_SCAN_PAGES = 4;
9
- function factoryDecisionType(decision) {
10
- return typeof decision.decision.type === "string" ? decision.decision.type.slice(0, 64) : "unknown";
11
- }
12
8
  function parseAttentionView(raw) {
13
9
  if (!raw || raw === "open") return "open";
14
10
  if (raw === "unread" || raw === "archived") return raw;
@@ -18,90 +14,130 @@ function parseAttentionLimit(raw) {
18
14
  if (!Number.isFinite(parsed)) return DEFAULT_PAGE_SIZE;
19
15
  return Math.max(1, Math.min(MAX_PAGE_SIZE, parsed));
20
16
  }
21
- function attentionIdentity(decision) {
22
- return factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);
23
- }
24
- function attentionKey(factoryProjectId, decision) {
25
- return factoryAttentionKey(factoryProjectId, attentionIdentity(decision));
17
+ function isAttentionKind(value) {
18
+ return value === "automation-failed" || value === "mention";
26
19
  }
27
- function failureOccurredAt(decision) {
28
- return decision.completedAt ?? decision.updatedAt;
20
+ function encodeAttentionCursor(cursors) {
21
+ const wire = {};
22
+ for (const [kind, position] of cursors) wire[kind] = position ? [position.occurredAt.toISOString(), position.id] : null;
23
+ return Buffer.from(JSON.stringify(wire), "utf8").toString("base64url");
29
24
  }
30
- function encodeAttentionCursor(decision) {
31
- return Buffer.from(JSON.stringify([failureOccurredAt(decision).toISOString(), decision.id]), "utf8").toString("base64url");
25
+ function parseStreamPosition(value) {
26
+ if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "string" || typeof value[1] !== "string") return;
27
+ const occurredAt = new Date(value[0]);
28
+ if (Number.isNaN(occurredAt.getTime()) || !UUID_RE.test(value[1])) return void 0;
29
+ return {
30
+ occurredAt,
31
+ id: value[1]
32
+ };
32
33
  }
33
34
  function parseAttentionCursor(raw) {
34
35
  if (!raw) return void 0;
35
36
  try {
36
37
  const decoded = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
37
- if (!Array.isArray(decoded) || decoded.length !== 2 || typeof decoded[0] !== "string" || typeof decoded[1] !== "string") return;
38
- const occurredAt = new Date(decoded[0]);
39
- if (Number.isNaN(occurredAt.getTime()) || !UUID_RE.test(decoded[1])) return void 0;
40
- return {
41
- occurredAt,
42
- id: decoded[1]
43
- };
38
+ if (Array.isArray(decoded)) {
39
+ const legacy = parseStreamPosition(decoded);
40
+ return legacy ? /* @__PURE__ */ new Map([["automation-failed", legacy]]) : void 0;
41
+ }
42
+ if (!decoded || typeof decoded !== "object") return void 0;
43
+ const cursors = /* @__PURE__ */ new Map();
44
+ for (const [kind, value] of Object.entries(decoded)) {
45
+ if (!isAttentionKind(kind)) return void 0;
46
+ if (value === null) {
47
+ cursors.set(kind, void 0);
48
+ continue;
49
+ }
50
+ const position = parseStreamPosition(value);
51
+ if (!position) return void 0;
52
+ cursors.set(kind, position);
53
+ }
54
+ return cursors.size > 0 ? cursors : void 0;
44
55
  } catch {
45
56
  return;
46
57
  }
47
58
  }
48
- function parseFailureOccurrence(raw) {
59
+ function parseOccurrence(raw) {
49
60
  if (!raw || !/^(0|[1-9]\d*)$/.test(raw)) return void 0;
50
61
  const occurrence = Number(raw);
51
62
  return Number.isSafeInteger(occurrence) ? occurrence : void 0;
52
63
  }
53
- function attentionTarget(decision, item) {
54
- if (!item) return { kind: "rules" };
55
- const role = typeof decision.decision.role === "string" ? decision.decision.role : void 0;
56
- const session = role ? item.sessions[role] : void 0;
57
- if (session) return {
58
- kind: "thread",
59
- sessionId: session.sessionId,
60
- threadId: session.threadId
61
- };
62
- const review = item.externalSource?.integrationId === "github" && item.externalSource.type === "pull-request";
64
+ /**
65
+ * Take the newest `limit` entries across provider pages. Each kind's next
66
+ * cursor is the resume position of its last consumed entry; a kind consumed to
67
+ * the end inherits the provider's own continuation.
68
+ */
69
+ function mergeAttentionPages(pages, limit) {
70
+ const consumed = new Map(pages.map((page) => [page.kind, 0]));
71
+ const items = [];
72
+ while (items.length < limit) {
73
+ let best;
74
+ for (const page of pages) {
75
+ const next = page.result.entries[consumed.get(page.kind) ?? 0];
76
+ if (!next) continue;
77
+ const at = next.occurredAt.getTime();
78
+ if (!best || at > best.at) best = {
79
+ kind: page.kind,
80
+ at
81
+ };
82
+ }
83
+ if (!best) break;
84
+ const index = consumed.get(best.kind) ?? 0;
85
+ const entry = pages.find((page) => page.kind === best.kind)?.result.entries[index];
86
+ if (!entry) break;
87
+ items.push(entry.item);
88
+ consumed.set(best.kind, index + 1);
89
+ }
90
+ const nextCursors = /* @__PURE__ */ new Map();
91
+ let hasMore = false;
92
+ for (const page of pages) {
93
+ const used = consumed.get(page.kind) ?? 0;
94
+ const entries = page.result.entries;
95
+ if (used < entries.length) {
96
+ hasMore = true;
97
+ const lastConsumed = used > 0 ? entries[used - 1] : void 0;
98
+ nextCursors.set(page.kind, lastConsumed ? lastConsumed.resumeCursor : page.incoming);
99
+ continue;
100
+ }
101
+ if (page.result.hasMore) {
102
+ hasMore = true;
103
+ nextCursors.set(page.kind, page.result.continuation ?? entries.at(-1)?.resumeCursor ?? page.incoming);
104
+ }
105
+ }
63
106
  return {
64
- kind: "work-item",
65
- workItemId: item.id,
66
- board: review ? "review" : "work"
107
+ items,
108
+ hasMore,
109
+ ...hasMore && nextCursors.size > 0 ? { nextCursor: encodeAttentionCursor(nextCursors) } : {}
67
110
  };
68
111
  }
69
- function attentionItem(factoryProjectId, decision, item, receipt) {
70
- const failure = factoryDispatchFailureMetadata(decision.failureCode);
71
- return {
72
- key: attentionKey(factoryProjectId, decision),
73
- kind: "automation-failed",
74
- decisionId: decision.id,
75
- occurrence: decision.failureOccurrence,
76
- workItemId: decision.workItemId,
77
- title: item?.title ?? failure.label,
78
- detail: decision.lastError?.slice(0, 512) ?? failure.label,
79
- decisionType: factoryDecisionType(decision),
80
- failureCode: decision.failureCode,
81
- canRetry: failure.canRetry,
82
- occurredAt: failureOccurredAt(decision).toISOString(),
83
- read: receipt !== void 0,
84
- archived: receipt?.state === "archived",
85
- target: attentionTarget(decision, item)
86
- };
112
+ function newestLatest(latests) {
113
+ let newest = null;
114
+ for (const latest of latests) {
115
+ if (!latest) continue;
116
+ if (!newest || latest.at.getTime() > newest.at.getTime()) newest = latest;
117
+ }
118
+ return newest;
87
119
  }
88
120
  function receiptRoute(dependencies, verb, action) {
89
- return registerApiRoute(`/web/factory/projects/:id/attention/automation-failed/:decisionId/:occurrence/${verb}`, {
121
+ return registerApiRoute(`/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/${verb}`, {
90
122
  method: "POST",
91
123
  requiresAuth: false,
92
124
  handler: async (context) => {
93
125
  const resolved = await dependencies.resolveProject(context);
94
126
  if ("response" in resolved) return resolved.response;
95
- const decisionId = context.req.param("decisionId");
96
- const failureOccurrence = parseFailureOccurrence(context.req.param("occurrence"));
97
- if (!decisionId || !UUID_RE.test(decisionId) || failureOccurrence === void 0) return context.json({ error: "invalid_attention_item" }, 422);
127
+ const kind = context.req.param("kind");
128
+ const sourceId = context.req.param("sourceId");
129
+ const occurrence = parseOccurrence(context.req.param("occurrence"));
130
+ if (!kind || !isAttentionKind(kind) || !sourceId || !UUID_RE.test(sourceId) || occurrence === void 0) return context.json({ error: "invalid_attention_item" }, 422);
98
131
  await dependencies.workItems.ensureReady();
99
132
  const receipt = await dependencies.workItems.setAttentionReceipt({
100
133
  orgId: resolved.orgId,
101
134
  factoryProjectId: resolved.factoryProjectId,
102
135
  userId: resolved.userId,
103
- decisionId,
104
- failureOccurrence,
136
+ identity: {
137
+ kind,
138
+ sourceId,
139
+ occurrence
140
+ },
105
141
  action,
106
142
  now: /* @__PURE__ */ new Date()
107
143
  });
@@ -116,7 +152,11 @@ function receiptRoute(dependencies, verb, action) {
116
152
  });
117
153
  }
118
154
  function buildAttentionRoutes(dependencies) {
119
- const { workItems } = dependencies;
155
+ const { workItems, comments } = dependencies;
156
+ const providers = [new AutomationFailedAttentionProvider({ workItems }), new MentionAttentionProvider({
157
+ workItems,
158
+ comments
159
+ })];
120
160
  return [
121
161
  registerApiRoute("/web/factory/projects/:id/attention", {
122
162
  method: "GET",
@@ -130,115 +170,45 @@ function buildAttentionRoutes(dependencies) {
130
170
  const before = parseAttentionCursor(cursorRaw);
131
171
  if (cursorRaw && !before) return context.json({ error: "invalid_cursor" }, 400);
132
172
  await workItems.ensureReady();
133
- const [failedCount, approvalCount, receiptCount, archivedCount, newestPage] = await Promise.all([
134
- workItems.countDeferredDecisionsByStatuses({
135
- orgId: resolved.orgId,
136
- factoryProjectId: resolved.factoryProjectId,
137
- statuses: ["failed"]
138
- }),
173
+ await comments.ensureReady();
174
+ const search = context.req.query("search")?.trim().toLowerCase().slice(0, 200);
175
+ const limit = parseAttentionLimit(context.req.query("limit"));
176
+ const active = providers.filter((provider) => !before || before.has(provider.kind));
177
+ const [approvalCount, counts, latests, pages] = await Promise.all([
139
178
  workItems.countDeferredDecisionsByStatuses({
140
179
  orgId: resolved.orgId,
141
180
  factoryProjectId: resolved.factoryProjectId,
142
181
  statuses: ["proposed"]
143
182
  }),
144
- workItems.countAttentionReceipts({
145
- orgId: resolved.orgId,
146
- factoryProjectId: resolved.factoryProjectId,
147
- userId: resolved.userId
148
- }),
149
- workItems.countAttentionReceipts({
150
- orgId: resolved.orgId,
151
- factoryProjectId: resolved.factoryProjectId,
152
- userId: resolved.userId,
153
- state: "archived"
154
- }),
155
- workItems.listFailedDecisionPage({
156
- orgId: resolved.orgId,
157
- factoryProjectId: resolved.factoryProjectId,
158
- limit: 1
159
- })
183
+ Promise.all(providers.map((provider) => provider.counts(resolved))),
184
+ Promise.all(providers.map((provider) => provider.latest(resolved))),
185
+ Promise.all(active.map(async (provider) => ({
186
+ kind: provider.kind,
187
+ incoming: before?.get(provider.kind),
188
+ result: await provider.page(resolved, {
189
+ view,
190
+ ...search ? { search } : {},
191
+ before: before?.get(provider.kind),
192
+ limit
193
+ })
194
+ })))
160
195
  ]);
161
- const failureOpenCount = Math.max(0, failedCount - archivedCount);
162
- const openCount = failureOpenCount + approvalCount;
163
- const unreadCount = Math.max(0, failedCount - receiptCount);
164
- const badgeCount = unreadCount + approvalCount;
165
- const newestFailure = newestPage.decisions[0];
166
- const newestReceipt = newestFailure ? (await workItems.listAttentionReceipts({
167
- orgId: resolved.orgId,
168
- factoryProjectId: resolved.factoryProjectId,
169
- userId: resolved.userId,
170
- identities: [attentionIdentity(newestFailure)]
171
- }))[0] : void 0;
172
- const search = context.req.query("search")?.trim().toLowerCase().slice(0, 200);
173
- const requestedLimit = parseAttentionLimit(context.req.query("limit"));
174
- const visible = [];
175
- let scanBefore = before;
176
- let cursorDecision;
177
- let continuationDecision;
178
- let scannedPages = 0;
179
- let hasMore = false;
180
- scan: while (view === "open" && failureOpenCount > 0 || view === "unread" && unreadCount > 0 || view === "archived" && archivedCount > 0) {
181
- const page = await workItems.listFailedDecisionPage({
182
- orgId: resolved.orgId,
183
- factoryProjectId: resolved.factoryProjectId,
184
- before: scanBefore,
185
- limit: MAX_PAGE_SIZE
186
- });
187
- scannedPages += 1;
188
- if (page.decisions.length === 0) break;
189
- const receipts = await workItems.listAttentionReceipts({
190
- orgId: resolved.orgId,
191
- factoryProjectId: resolved.factoryProjectId,
192
- userId: resolved.userId,
193
- identities: page.decisions.map(attentionIdentity)
194
- });
195
- const receiptByKey = new Map(receipts.map((receipt) => [factoryAttentionKey(resolved.factoryProjectId, receipt), receipt]));
196
- const linkedItems = await workItems.listByIds({
197
- orgId: resolved.orgId,
198
- factoryProjectId: resolved.factoryProjectId,
199
- ids: page.decisions.flatMap((decision) => decision.workItemId ? [decision.workItemId] : [])
200
- });
201
- const itemById = new Map(linkedItems.map((item) => [item.id, item]));
202
- for (const decision of page.decisions) {
203
- const receipt = receiptByKey.get(attentionKey(resolved.factoryProjectId, decision));
204
- if (view === "archived" ? receipt?.state !== "archived" : view === "unread" ? receipt : receipt?.state === "archived") continue;
205
- const item = decision.workItemId ? itemById.get(decision.workItemId) : void 0;
206
- if (search && item?.title.toLowerCase().includes(search) !== true && decision.lastError?.toLowerCase().includes(search) !== true && !factoryDecisionType(decision).toLowerCase().includes(search)) continue;
207
- if (visible.length === requestedLimit) {
208
- hasMore = true;
209
- continuationDecision = cursorDecision;
210
- break scan;
211
- }
212
- visible.push({
213
- decision,
214
- item,
215
- receipt
216
- });
217
- if (visible.length === requestedLimit) cursorDecision = decision;
218
- }
219
- const lastScanned = page.decisions.at(-1);
220
- if (!page.hasMore || !lastScanned) break;
221
- if (scannedPages === MAX_RECEIPT_SCAN_PAGES) {
222
- hasMore = true;
223
- continuationDecision = lastScanned;
224
- break;
225
- }
226
- scanBefore = {
227
- occurredAt: failureOccurredAt(lastScanned),
228
- id: lastScanned.id
229
- };
230
- }
196
+ const openCount = counts.reduce((sum, count) => sum + count.open, 0) + approvalCount;
197
+ const unreadCount = counts.reduce((sum, count) => sum + count.unread, 0);
198
+ const unreadLatests = latests.filter((latest) => latest?.unread ?? false);
199
+ const latest = unreadLatests.length > 0 ? newestLatest(unreadLatests) : newestLatest(latests);
200
+ const merged = mergeAttentionPages(pages, limit);
231
201
  return context.json({
232
- items: visible.map(({ decision, item, receipt }) => attentionItem(resolved.factoryProjectId, decision, item, receipt)),
202
+ items: merged.items,
233
203
  openCount,
234
204
  approvalCount,
235
- badgeCount,
205
+ badgeCount: unreadCount + approvalCount,
236
206
  unreadCount,
237
- latestOccurrenceKey: newestFailure ? attentionKey(resolved.factoryProjectId, newestFailure) : null,
238
- latestOccurrenceAt: newestFailure ? failureOccurredAt(newestFailure).toISOString() : null,
239
- latestOccurrenceUnread: newestFailure !== void 0 && newestReceipt === void 0,
240
- hasMore,
241
- ...hasMore && continuationDecision ? { nextCursor: encodeAttentionCursor(continuationDecision) } : {}
207
+ latestOccurrenceKey: latest?.key ?? null,
208
+ latestOccurrenceAt: latest?.at.toISOString() ?? null,
209
+ latestOccurrenceUnread: latest?.unread ?? false,
210
+ hasMore: merged.hasMore,
211
+ ...merged.nextCursor ? { nextCursor: merged.nextCursor } : {}
242
212
  });
243
213
  }
244
214
  }),
@@ -249,48 +219,28 @@ function buildAttentionRoutes(dependencies) {
249
219
  const resolved = await dependencies.resolveProject(context);
250
220
  if ("response" in resolved) return resolved.response;
251
221
  const cursorRaw = context.req.query("before");
252
- const initialBefore = parseAttentionCursor(cursorRaw);
253
- if (cursorRaw && !initialBefore) return context.json({ error: "invalid_cursor" }, 400);
222
+ const before = parseAttentionCursor(cursorRaw);
223
+ if (cursorRaw && !before) return context.json({ error: "invalid_cursor" }, 400);
254
224
  await workItems.ensureReady();
255
- let before = initialBefore;
256
- let pages = 0;
225
+ await comments.ensureReady();
226
+ const now = /* @__PURE__ */ new Date();
227
+ const active = providers.filter((provider) => !before || before.has(provider.kind));
228
+ const nextCursors = /* @__PURE__ */ new Map();
257
229
  let hasMore = false;
258
- let nextCursor;
259
- while (pages < MAX_RECEIPT_SCAN_PAGES) {
260
- const page = await workItems.listFailedDecisionPage({
261
- orgId: resolved.orgId,
262
- factoryProjectId: resolved.factoryProjectId,
263
- before,
264
- limit: MAX_PAGE_SIZE
265
- });
266
- pages += 1;
267
- if (page.decisions.length === 0) break;
268
- await workItems.markAttentionReceiptsRead({
269
- orgId: resolved.orgId,
270
- factoryProjectId: resolved.factoryProjectId,
271
- userId: resolved.userId,
272
- occurrences: page.decisions.map((decision) => ({
273
- decisionId: decision.id,
274
- failureOccurrence: decision.failureOccurrence
275
- })),
276
- now: /* @__PURE__ */ new Date()
230
+ for (const provider of active) {
231
+ const result = await provider.markAllRead(resolved, {
232
+ before: before?.get(provider.kind),
233
+ now
277
234
  });
278
- const last = page.decisions.at(-1);
279
- if (!page.hasMore || !last) break;
280
- if (pages === MAX_RECEIPT_SCAN_PAGES) {
235
+ if (result.hasMore) {
281
236
  hasMore = true;
282
- nextCursor = encodeAttentionCursor(last);
283
- break;
237
+ if (result.continuation) nextCursors.set(provider.kind, result.continuation);
284
238
  }
285
- before = {
286
- occurredAt: failureOccurredAt(last),
287
- id: last.id
288
- };
289
239
  }
290
240
  return context.json({
291
241
  ok: true,
292
242
  hasMore,
293
- ...nextCursor ? { nextCursor } : {}
243
+ ...hasMore && nextCursors.size > 0 ? { nextCursor: encodeAttentionCursor(nextCursors) } : {}
294
244
  });
295
245
  }
296
246
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"attention.js","names":[],"sources":["../../src/routes/attention.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\n\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport type {\n FactoryAttentionReceiptAction,\n FactoryAttentionReceiptRecord,\n FactoryDeferredDecisionRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { factoryAttentionKey, factoryDecisionAttentionIdentity } from '../storage/domains/work-items/base.js';\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst DEFAULT_PAGE_SIZE = 25;\nconst MAX_PAGE_SIZE = 50;\n// Receipt filtering is bounded to 200 failed decisions per request; the response cursor resumes after the last scan.\nconst MAX_RECEIPT_SCAN_PAGES = 4;\n\ntype FactoryAttentionView = 'open' | 'unread' | 'archived';\n\ninterface ResolvedAttentionProject {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n}\n\ninterface AttentionRouteDependencies {\n workItems: WorkItemsStorage;\n resolveProject(context: unknown): Promise<ResolvedAttentionProject | { response: Response }>;\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 parseAttentionView(raw: string | undefined): FactoryAttentionView | undefined {\n if (!raw || raw === 'open') return 'open';\n if (raw === 'unread' || raw === 'archived') return raw;\n return undefined;\n}\n\nfunction parseAttentionLimit(raw: string | undefined): number {\n const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_PAGE_SIZE;\n if (!Number.isFinite(parsed)) return DEFAULT_PAGE_SIZE;\n return Math.max(1, Math.min(MAX_PAGE_SIZE, parsed));\n}\n\nfunction attentionIdentity(decision: FactoryDeferredDecisionRecord) {\n return factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);\n}\n\nfunction attentionKey(factoryProjectId: string, decision: FactoryDeferredDecisionRecord): string {\n return factoryAttentionKey(factoryProjectId, attentionIdentity(decision));\n}\n\nfunction failureOccurredAt(decision: FactoryDeferredDecisionRecord): Date {\n return decision.completedAt ?? decision.updatedAt;\n}\n\nfunction encodeAttentionCursor(decision: FactoryDeferredDecisionRecord): string {\n return Buffer.from(JSON.stringify([failureOccurredAt(decision).toISOString(), decision.id]), 'utf8').toString(\n 'base64url',\n );\n}\n\nfunction parseAttentionCursor(raw: string | undefined): { occurredAt: Date; id: string } | undefined {\n if (!raw) return undefined;\n try {\n const decoded: unknown = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8'));\n if (\n !Array.isArray(decoded) ||\n decoded.length !== 2 ||\n typeof decoded[0] !== 'string' ||\n typeof decoded[1] !== 'string'\n ) {\n return undefined;\n }\n const occurredAt = new Date(decoded[0]);\n if (Number.isNaN(occurredAt.getTime()) || !UUID_RE.test(decoded[1])) return undefined;\n return { occurredAt, id: decoded[1] };\n } catch {\n return undefined;\n }\n}\n\nfunction parseFailureOccurrence(raw: string | undefined): number | undefined {\n if (!raw || !/^(0|[1-9]\\d*)$/.test(raw)) return undefined;\n const occurrence = Number(raw);\n return Number.isSafeInteger(occurrence) ? occurrence : undefined;\n}\n\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 const review = item.externalSource?.integrationId === 'github' && item.externalSource.type === 'pull-request';\n return {\n kind: 'work-item' as const,\n workItemId: item.id,\n board: review ? ('review' as const) : ('work' as const),\n };\n}\n\nfunction attentionItem(\n factoryProjectId: string,\n decision: FactoryDeferredDecisionRecord,\n item: WorkItemRow | undefined,\n receipt: FactoryAttentionReceiptRecord | undefined,\n) {\n const failure = factoryDispatchFailureMetadata(decision.failureCode);\n return {\n key: attentionKey(factoryProjectId, decision),\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 receiptRoute(\n dependencies: AttentionRouteDependencies,\n verb: 'read' | 'archive' | 'restore',\n action: FactoryAttentionReceiptAction,\n): ApiRoute {\n return registerApiRoute(`/web/factory/projects/:id/attention/automation-failed/:decisionId/:occurrence/${verb}`, {\n method: 'POST',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const decisionId = context.req.param('decisionId');\n const failureOccurrence = parseFailureOccurrence(context.req.param('occurrence'));\n if (!decisionId || !UUID_RE.test(decisionId) || failureOccurrence === undefined) {\n return context.json({ error: 'invalid_attention_item' }, 422);\n }\n await dependencies.workItems.ensureReady();\n const receipt = await dependencies.workItems.setAttentionReceipt({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n decisionId,\n failureOccurrence,\n action,\n now: new Date(),\n });\n if (!receipt) return context.json({ error: 'attention_item_not_current' }, 409);\n return context.json({\n receipt: {\n key: factoryAttentionKey(resolved.factoryProjectId, receipt),\n state: receipt.state,\n readAt: receipt.readAt.toISOString(),\n archivedAt: receipt.archivedAt?.toISOString() ?? null,\n },\n });\n },\n });\n}\n\nexport function buildAttentionRoutes(dependencies: AttentionRouteDependencies): ApiRoute[] {\n const { workItems } = dependencies;\n return [\n registerApiRoute('/web/factory/projects/:id/attention', {\n method: 'GET',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const view = parseAttentionView(context.req.query('view'));\n if (view === undefined) return context.json({ error: 'invalid_attention_view' }, 400);\n const cursorRaw = context.req.query('before');\n const before = parseAttentionCursor(cursorRaw);\n if (cursorRaw && !before) return context.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n const [failedCount, approvalCount, receiptCount, archivedCount, newestPage] = await Promise.all([\n workItems.countDeferredDecisionsByStatuses({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n statuses: ['failed'],\n }),\n workItems.countDeferredDecisionsByStatuses({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n statuses: ['proposed'],\n }),\n workItems.countAttentionReceipts({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n }),\n workItems.countAttentionReceipts({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n state: 'archived',\n }),\n workItems.listFailedDecisionPage({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n limit: 1,\n }),\n ]);\n const failureOpenCount = Math.max(0, failedCount - archivedCount);\n const openCount = failureOpenCount + approvalCount;\n const unreadCount = Math.max(0, failedCount - receiptCount);\n const badgeCount = unreadCount + approvalCount;\n const newestFailure = newestPage.decisions[0];\n const newestReceipt = newestFailure\n ? (\n await workItems.listAttentionReceipts({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n identities: [attentionIdentity(newestFailure)],\n })\n )[0]\n : undefined;\n const search = context.req.query('search')?.trim().toLowerCase().slice(0, 200);\n const requestedLimit = parseAttentionLimit(context.req.query('limit'));\n const visible: Array<{\n decision: FactoryDeferredDecisionRecord;\n item: WorkItemRow | undefined;\n receipt: FactoryAttentionReceiptRecord | undefined;\n }> = [];\n let scanBefore = before;\n let cursorDecision: FactoryDeferredDecisionRecord | undefined;\n let continuationDecision: FactoryDeferredDecisionRecord | undefined;\n let scannedPages = 0;\n let hasMore = false;\n\n scan: while (\n (view === 'open' && failureOpenCount > 0) ||\n (view === 'unread' && unreadCount > 0) ||\n (view === 'archived' && archivedCount > 0)\n ) {\n const page = await workItems.listFailedDecisionPage({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n before: scanBefore,\n limit: MAX_PAGE_SIZE,\n });\n scannedPages += 1;\n if (page.decisions.length === 0) break;\n const receipts = await workItems.listAttentionReceipts({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n identities: page.decisions.map(attentionIdentity),\n });\n const receiptByKey = new Map(\n receipts.map(receipt => [factoryAttentionKey(resolved.factoryProjectId, receipt), receipt]),\n );\n const linkedItems = await workItems.listByIds({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n ids: page.decisions.flatMap(decision => (decision.workItemId ? [decision.workItemId] : [])),\n });\n const itemById = new Map(linkedItems.map(item => [item.id, item]));\n for (const decision of page.decisions) {\n const receipt = receiptByKey.get(attentionKey(resolved.factoryProjectId, decision));\n if (\n view === 'archived'\n ? receipt?.state !== 'archived'\n : view === 'unread'\n ? receipt\n : receipt?.state === 'archived'\n ) {\n continue;\n }\n const item = decision.workItemId ? itemById.get(decision.workItemId) : undefined;\n if (\n search &&\n item?.title.toLowerCase().includes(search) !== true &&\n decision.lastError?.toLowerCase().includes(search) !== true &&\n !factoryDecisionType(decision).toLowerCase().includes(search)\n ) {\n continue;\n }\n if (visible.length === requestedLimit) {\n hasMore = true;\n continuationDecision = cursorDecision;\n break scan;\n }\n visible.push({ decision, item, receipt });\n if (visible.length === requestedLimit) cursorDecision = decision;\n }\n const lastScanned = page.decisions.at(-1);\n if (!page.hasMore || !lastScanned) break;\n if (scannedPages === MAX_RECEIPT_SCAN_PAGES) {\n hasMore = true;\n continuationDecision = lastScanned;\n break;\n }\n scanBefore = { occurredAt: failureOccurredAt(lastScanned), id: lastScanned.id };\n }\n\n return context.json({\n items: visible.map(({ decision, item, receipt }) =>\n attentionItem(resolved.factoryProjectId, decision, item, receipt),\n ),\n openCount,\n approvalCount,\n badgeCount,\n unreadCount,\n latestOccurrenceKey: newestFailure ? attentionKey(resolved.factoryProjectId, newestFailure) : null,\n latestOccurrenceAt: newestFailure ? failureOccurredAt(newestFailure).toISOString() : null,\n latestOccurrenceUnread: newestFailure !== undefined && newestReceipt === undefined,\n hasMore,\n ...(hasMore && continuationDecision ? { nextCursor: encodeAttentionCursor(continuationDecision) } : {}),\n });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/attention/read-all', {\n method: 'POST',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const cursorRaw = context.req.query('before');\n const initialBefore = parseAttentionCursor(cursorRaw);\n if (cursorRaw && !initialBefore) return context.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n let before = initialBefore;\n let pages = 0;\n let hasMore = false;\n let nextCursor: string | undefined;\n while (pages < MAX_RECEIPT_SCAN_PAGES) {\n const page = await workItems.listFailedDecisionPage({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n before,\n limit: MAX_PAGE_SIZE,\n });\n pages += 1;\n if (page.decisions.length === 0) break;\n await workItems.markAttentionReceiptsRead({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n occurrences: page.decisions.map(decision => ({\n decisionId: decision.id,\n failureOccurrence: decision.failureOccurrence,\n })),\n now: new Date(),\n });\n const last = page.decisions.at(-1);\n if (!page.hasMore || !last) break;\n if (pages === MAX_RECEIPT_SCAN_PAGES) {\n hasMore = true;\n nextCursor = encodeAttentionCursor(last);\n break;\n }\n before = { occurredAt: failureOccurredAt(last), id: last.id };\n }\n return context.json({ ok: true, hasMore, ...(nextCursor ? { nextCursor } : {}) });\n },\n }),\n receiptRoute(dependencies, 'read', 'read'),\n receiptRoute(dependencies, 'archive', 'archive'),\n receiptRoute(dependencies, 'restore', 'restore'),\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"attention.js","names":[],"sources":["../../src/routes/attention.ts"],"sourcesContent":["/**\n * Attention routes: k-way merge of the per-kind providers on `occurredAt desc`.\n * The wire cursor is a per-kind map — each kind's stream resumes independently,\n * `null` meaning \"not started yet\", an absent kind meaning \"exhausted\".\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\n\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type {\n FactoryAttentionKind,\n FactoryAttentionReceiptAction,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { factoryAttentionKey } from '../storage/domains/work-items/base.js';\nimport type {\n AttentionLatest,\n AttentionPageResult,\n AttentionProvider,\n AttentionScope,\n AttentionStreamPosition,\n FactoryAttentionView,\n} from './attention-providers.js';\nimport { AutomationFailedAttentionProvider, MentionAttentionProvider } from './attention-providers.js';\n\nexport { factoryDecisionType } from './attention-providers.js';\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst DEFAULT_PAGE_SIZE = 25;\nconst MAX_PAGE_SIZE = 50;\n\ninterface AttentionRouteDependencies {\n workItems: WorkItemsStorage;\n comments: WorkItemCommentsStorage;\n resolveProject(context: unknown): Promise<AttentionScope | { response: Response }>;\n}\n\ntype AttentionCursorMap = Map<FactoryAttentionKind, AttentionStreamPosition | undefined>;\n\nfunction parseAttentionView(raw: string | undefined): FactoryAttentionView | undefined {\n if (!raw || raw === 'open') return 'open';\n if (raw === 'unread' || raw === 'archived') return raw;\n return undefined;\n}\n\nfunction parseAttentionLimit(raw: string | undefined): number {\n const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_PAGE_SIZE;\n if (!Number.isFinite(parsed)) return DEFAULT_PAGE_SIZE;\n return Math.max(1, Math.min(MAX_PAGE_SIZE, parsed));\n}\n\nfunction isAttentionKind(value: string): value is FactoryAttentionKind {\n return value === 'automation-failed' || value === 'mention';\n}\n\nfunction encodeAttentionCursor(cursors: AttentionCursorMap): string {\n const wire: Record<string, [string, string] | null> = {};\n for (const [kind, position] of cursors) {\n wire[kind] = position ? [position.occurredAt.toISOString(), position.id] : null;\n }\n return Buffer.from(JSON.stringify(wire), 'utf8').toString('base64url');\n}\n\nfunction parseStreamPosition(value: unknown): AttentionStreamPosition | undefined {\n if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== 'string' || typeof value[1] !== 'string') {\n return undefined;\n }\n const occurredAt = new Date(value[0]);\n if (Number.isNaN(occurredAt.getTime()) || !UUID_RE.test(value[1])) return undefined;\n return { occurredAt, id: value[1] };\n}\n\nfunction parseAttentionCursor(raw: string | undefined): AttentionCursorMap | undefined {\n if (!raw) return undefined;\n try {\n const decoded: unknown = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8'));\n // Cursors minted before the inbox merged kinds are a bare position over the\n // only stream there was. Held by anyone mid-list when this deploys, so they\n // resume that stream rather than 400: mentions arrive on their next load.\n if (Array.isArray(decoded)) {\n const legacy = parseStreamPosition(decoded);\n return legacy ? new Map([['automation-failed', legacy]]) : undefined;\n }\n if (!decoded || typeof decoded !== 'object') return undefined;\n const cursors: AttentionCursorMap = new Map();\n for (const [kind, value] of Object.entries(decoded)) {\n if (!isAttentionKind(kind)) return undefined;\n if (value === null) {\n cursors.set(kind, undefined);\n continue;\n }\n const position = parseStreamPosition(value);\n if (!position) return undefined;\n cursors.set(kind, position);\n }\n return cursors.size > 0 ? cursors : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction parseOccurrence(raw: string | undefined): number | undefined {\n if (!raw || !/^(0|[1-9]\\d*)$/.test(raw)) return undefined;\n const occurrence = Number(raw);\n return Number.isSafeInteger(occurrence) ? occurrence : undefined;\n}\n\ninterface MergedAttentionPage {\n items: Array<Record<string, unknown>>;\n hasMore: boolean;\n nextCursor?: string;\n}\n\n/**\n * Take the newest `limit` entries across provider pages. Each kind's next\n * cursor is the resume position of its last consumed entry; a kind consumed to\n * the end inherits the provider's own continuation.\n */\nfunction mergeAttentionPages(\n pages: Array<{\n kind: FactoryAttentionKind;\n incoming: AttentionStreamPosition | undefined;\n result: AttentionPageResult;\n }>,\n limit: number,\n): MergedAttentionPage {\n const consumed = new Map(pages.map(page => [page.kind, 0]));\n const items: Array<Record<string, unknown>> = [];\n while (items.length < limit) {\n let best: { kind: FactoryAttentionKind; at: number } | undefined;\n for (const page of pages) {\n const next = page.result.entries[consumed.get(page.kind) ?? 0];\n if (!next) continue;\n const at = next.occurredAt.getTime();\n if (!best || at > best.at) best = { kind: page.kind, at };\n }\n if (!best) break;\n const index = consumed.get(best.kind) ?? 0;\n const entry = pages.find(page => page.kind === best.kind)?.result.entries[index];\n if (!entry) break;\n items.push(entry.item);\n consumed.set(best.kind, index + 1);\n }\n\n const nextCursors: AttentionCursorMap = new Map();\n let hasMore = false;\n for (const page of pages) {\n const used = consumed.get(page.kind) ?? 0;\n const entries = page.result.entries;\n if (used < entries.length) {\n hasMore = true;\n const lastConsumed = used > 0 ? entries[used - 1] : undefined;\n nextCursors.set(page.kind, lastConsumed ? lastConsumed.resumeCursor : page.incoming);\n continue;\n }\n if (page.result.hasMore) {\n hasMore = true;\n nextCursors.set(page.kind, page.result.continuation ?? entries.at(-1)?.resumeCursor ?? page.incoming);\n }\n }\n return {\n items,\n hasMore,\n ...(hasMore && nextCursors.size > 0 ? { nextCursor: encodeAttentionCursor(nextCursors) } : {}),\n };\n}\n\nfunction newestLatest(latests: Array<AttentionLatest | null>): AttentionLatest | null {\n let newest: AttentionLatest | null = null;\n for (const latest of latests) {\n if (!latest) continue;\n if (!newest || latest.at.getTime() > newest.at.getTime()) newest = latest;\n }\n return newest;\n}\n\nfunction receiptRoute(\n dependencies: AttentionRouteDependencies,\n verb: 'read' | 'archive' | 'restore',\n action: FactoryAttentionReceiptAction,\n): ApiRoute {\n return registerApiRoute(`/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/${verb}`, {\n method: 'POST',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const kind = context.req.param('kind');\n const sourceId = context.req.param('sourceId');\n const occurrence = parseOccurrence(context.req.param('occurrence'));\n if (!kind || !isAttentionKind(kind) || !sourceId || !UUID_RE.test(sourceId) || occurrence === undefined) {\n return context.json({ error: 'invalid_attention_item' }, 422);\n }\n await dependencies.workItems.ensureReady();\n const receipt = await dependencies.workItems.setAttentionReceipt({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n userId: resolved.userId,\n identity: { kind, sourceId, occurrence },\n action,\n now: new Date(),\n });\n if (!receipt) return context.json({ error: 'attention_item_not_current' }, 409);\n return context.json({\n receipt: {\n key: factoryAttentionKey(resolved.factoryProjectId, receipt),\n state: receipt.state,\n readAt: receipt.readAt.toISOString(),\n archivedAt: receipt.archivedAt?.toISOString() ?? null,\n },\n });\n },\n });\n}\n\nexport function buildAttentionRoutes(dependencies: AttentionRouteDependencies): ApiRoute[] {\n const { workItems, comments } = dependencies;\n const providers: AttentionProvider[] = [\n new AutomationFailedAttentionProvider({ workItems }),\n new MentionAttentionProvider({ workItems, comments }),\n ];\n\n return [\n registerApiRoute('/web/factory/projects/:id/attention', {\n method: 'GET',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const view = parseAttentionView(context.req.query('view'));\n if (view === undefined) return context.json({ error: 'invalid_attention_view' }, 400);\n const cursorRaw = context.req.query('before');\n const before = parseAttentionCursor(cursorRaw);\n if (cursorRaw && !before) return context.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n await comments.ensureReady();\n\n const search = context.req.query('search')?.trim().toLowerCase().slice(0, 200);\n const limit = parseAttentionLimit(context.req.query('limit'));\n const active = providers.filter(provider => !before || before.has(provider.kind));\n\n const [approvalCount, counts, latests, pages] = await Promise.all([\n workItems.countDeferredDecisionsByStatuses({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n statuses: ['proposed'],\n }),\n Promise.all(providers.map(provider => provider.counts(resolved))),\n Promise.all(providers.map(provider => provider.latest(resolved))),\n Promise.all(\n active.map(async provider => ({\n kind: provider.kind,\n incoming: before?.get(provider.kind),\n result: await provider.page(resolved, {\n view,\n ...(search ? { search } : {}),\n before: before?.get(provider.kind),\n limit,\n }),\n })),\n ),\n ]);\n\n const openCount = counts.reduce((sum, count) => sum + count.open, 0) + approvalCount;\n const unreadCount = counts.reduce((sum, count) => sum + count.unread, 0);\n // An unread item must never be masked by a newer already-read one of\n // another kind — the streams are independent.\n const unreadLatests = latests.filter(latest => latest?.unread ?? false);\n const latest = unreadLatests.length > 0 ? newestLatest(unreadLatests) : newestLatest(latests);\n const merged = mergeAttentionPages(pages, limit);\n\n return context.json({\n items: merged.items,\n openCount,\n approvalCount,\n badgeCount: unreadCount + approvalCount,\n unreadCount,\n latestOccurrenceKey: latest?.key ?? null,\n latestOccurrenceAt: latest?.at.toISOString() ?? null,\n latestOccurrenceUnread: latest?.unread ?? false,\n hasMore: merged.hasMore,\n ...(merged.nextCursor ? { nextCursor: merged.nextCursor } : {}),\n });\n },\n }),\n registerApiRoute('/web/factory/projects/:id/attention/read-all', {\n method: 'POST',\n requiresAuth: false,\n handler: async context => {\n const resolved = await dependencies.resolveProject(context);\n if ('response' in resolved) return resolved.response;\n const cursorRaw = context.req.query('before');\n const before = parseAttentionCursor(cursorRaw);\n if (cursorRaw && !before) return context.json({ error: 'invalid_cursor' }, 400);\n await workItems.ensureReady();\n await comments.ensureReady();\n\n const now = new Date();\n const active = providers.filter(provider => !before || before.has(provider.kind));\n const nextCursors: AttentionCursorMap = new Map();\n let hasMore = false;\n for (const provider of active) {\n const result = await provider.markAllRead(resolved, { before: before?.get(provider.kind), now });\n if (result.hasMore) {\n hasMore = true;\n if (result.continuation) nextCursors.set(provider.kind, result.continuation);\n }\n }\n return context.json({\n ok: true,\n hasMore,\n ...(hasMore && nextCursors.size > 0 ? { nextCursor: encodeAttentionCursor(nextCursors) } : {}),\n });\n },\n }),\n receiptRoute(dependencies, 'read', 'read'),\n receiptRoute(dependencies, 'archive', 'archive'),\n receiptRoute(dependencies, 'restore', 'restore'),\n ];\n}\n"],"mappings":"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"}
@@ -14,6 +14,7 @@ import type { SandboxFleet } from '../sandbox/fleet.js';
14
14
  import type { StateSigner } from '../state-signing.js';
15
15
  import type { AuditEmitter } from '../storage/domains/audit/domain.js';
16
16
  import type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';
17
+ import type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';
17
18
  import type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';
18
19
  import type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';
19
20
  import type { FilesystemStorage } from '../storage/domains/filesystem/base.js';
@@ -63,6 +64,7 @@ export interface FactoryApiRoutesDeps {
63
64
  queueHealth: QueueHealthStorage;
64
65
  workItems: WorkItemsStorage;
65
66
  channelIdentity: ChannelIdentityStorage;
67
+ comments: WorkItemCommentsStorage;
66
68
  };
67
69
  integrations?: IntegrationRegistration[];
68
70
  intakeReady: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { getGithubFeatureDiagnostics } from "../integrations/github/config.js";
4
4
  import { invalidateCustomProvidersSnapshots } from "./custom-provider-source.js";
5
5
  import { FactoryDispatchError } from "../rules/dispatch-errors.js";
6
6
  import { FactorySourceSessionResolutionError, ensureFactorySourceSession, resolveFactoryDefaultModelId } from "../session/factory-session.js";
7
+ import { FactoryFeedReader } from "../storage/domains/comments/feed-context.js";
7
8
  import { FactoryStartCoordinator } from "../rules/start-coordinator.js";
8
9
  import { FactoryTransitionService } from "../rules/transition-service.js";
9
10
  import { LiveSessions } from "../session/live-sessions.js";
@@ -254,7 +255,7 @@ function assembleFactoryApiRoutes(deps) {
254
255
  rules: deps.rules,
255
256
  storage: deps.domains.workItems
256
257
  }) : void 0;
257
- const startCoordinator = transitionService ? new FactoryStartCoordinator(deps.controller, deps.domains.workItems, transitionService, githubIntegration?.sourceControlStorage, deps.domains.memorySettings) : void 0;
258
+ const startCoordinator = transitionService ? new FactoryStartCoordinator(deps.controller, deps.domains.workItems, transitionService, githubIntegration?.sourceControlStorage, deps.domains.memorySettings, new FactoryFeedReader(deps.domains.comments)) : void 0;
258
259
  if (transitionService && startCoordinator) deps.onFactoryRuntime?.({
259
260
  transitionService,
260
261
  ...githubIntegration ? { prepareBinding: (input) => prepareFactoryRuleBinding(githubIntegration, startCoordinator, deps.domains.projects, input) } : {}
@@ -319,6 +320,7 @@ function assembleFactoryApiRoutes(deps) {
319
320
  audit: deps.audit,
320
321
  projects: deps.domains.projects,
321
322
  workItems: deps.domains.workItems,
323
+ comments: deps.domains.comments,
322
324
  queueHealth: deps.domains.queueHealth,
323
325
  transitionService,
324
326
  startCoordinator,