@mastra/factory 0.10.0-alpha.2 → 0.10.0-alpha.8

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 (116) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/dist/factory.d.ts +8 -0
  3. package/dist/factory.d.ts.map +1 -1
  4. package/dist/factory.js +24 -8
  5. package/dist/factory.js.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -1
  9. package/dist/integrations/github/integration.d.ts.map +1 -1
  10. package/dist/integrations/github/integration.js +1 -0
  11. package/dist/integrations/github/integration.js.map +1 -1
  12. package/dist/integrations/github/provenance.d.ts +7 -2
  13. package/dist/integrations/github/provenance.d.ts.map +1 -1
  14. package/dist/integrations/github/provenance.js +16 -2
  15. package/dist/integrations/github/provenance.js.map +1 -1
  16. package/dist/integrations/github/routes.d.ts +3 -0
  17. package/dist/integrations/github/routes.d.ts.map +1 -1
  18. package/dist/integrations/github/routes.js +75 -6
  19. package/dist/integrations/github/routes.js.map +1 -1
  20. package/dist/integrations/github/rules.d.ts.map +1 -1
  21. package/dist/integrations/github/rules.js +193 -127
  22. package/dist/integrations/github/rules.js.map +1 -1
  23. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  24. package/dist/integrations/platform/github/integration.js +1 -0
  25. package/dist/integrations/platform/github/integration.js.map +1 -1
  26. package/dist/routes/attention.d.ts +17 -0
  27. package/dist/routes/attention.d.ts.map +1 -0
  28. package/dist/routes/attention.js +305 -0
  29. package/dist/routes/attention.js.map +1 -0
  30. package/dist/routes/surface.d.ts +2 -3
  31. package/dist/routes/surface.d.ts.map +1 -1
  32. package/dist/routes/surface.js +61 -43
  33. package/dist/routes/surface.js.map +1 -1
  34. package/dist/routes/work-items.d.ts.map +1 -1
  35. package/dist/routes/work-items.js +35 -14
  36. package/dist/routes/work-items.js.map +1 -1
  37. package/dist/rules/defaults.js +5 -5
  38. package/dist/rules/defaults.js.map +1 -1
  39. package/dist/rules/dispatch-errors.d.ts +13 -0
  40. package/dist/rules/dispatch-errors.d.ts.map +1 -0
  41. package/dist/rules/dispatch-errors.js +77 -0
  42. package/dist/rules/dispatch-errors.js.map +1 -0
  43. package/dist/rules/dispatcher.d.ts +9 -0
  44. package/dist/rules/dispatcher.d.ts.map +1 -1
  45. package/dist/rules/dispatcher.js +118 -45
  46. package/dist/rules/dispatcher.js.map +1 -1
  47. package/dist/rules/processor.d.ts.map +1 -1
  48. package/dist/rules/processor.js +30 -14
  49. package/dist/rules/processor.js.map +1 -1
  50. package/dist/rules/terminal-cleanup.d.ts +1 -1
  51. package/dist/rules/terminal-cleanup.d.ts.map +1 -1
  52. package/dist/rules/terminal-cleanup.js +2 -2
  53. package/dist/rules/terminal-cleanup.js.map +1 -1
  54. package/dist/rules/transition-service.d.ts +2 -1
  55. package/dist/rules/transition-service.d.ts.map +1 -1
  56. package/dist/rules/transition-service.js +1 -1
  57. package/dist/rules/transition-service.js.map +1 -1
  58. package/dist/rules/types.d.ts +2 -0
  59. package/dist/rules/types.d.ts.map +1 -1
  60. package/dist/rules/types.js +9 -1
  61. package/dist/rules/types.js.map +1 -1
  62. package/dist/secret-encryption.d.ts +25 -0
  63. package/dist/secret-encryption.d.ts.map +1 -0
  64. package/dist/secret-encryption.js +96 -0
  65. package/dist/secret-encryption.js.map +1 -0
  66. package/dist/session/factory-session.d.ts +13 -3
  67. package/dist/session/factory-session.d.ts.map +1 -1
  68. package/dist/session/factory-session.js +15 -6
  69. package/dist/session/factory-session.js.map +1 -1
  70. package/dist/session/first-exec-capture.d.ts +16 -10
  71. package/dist/session/first-exec-capture.d.ts.map +1 -1
  72. package/dist/session/first-exec-capture.js +74 -14
  73. package/dist/session/first-exec-capture.js.map +1 -1
  74. package/dist/session/first-message-capture.d.ts +14 -10
  75. package/dist/session/first-message-capture.d.ts.map +1 -1
  76. package/dist/session/first-message-capture.js +17 -11
  77. package/dist/session/first-message-capture.js.map +1 -1
  78. package/dist/session/session-title.d.ts +7 -0
  79. package/dist/session/session-title.d.ts.map +1 -0
  80. package/dist/session/session-title.js +14 -0
  81. package/dist/session/session-title.js.map +1 -0
  82. package/dist/session/thread-title-mirror.d.ts +35 -0
  83. package/dist/session/thread-title-mirror.d.ts.map +1 -0
  84. package/dist/session/thread-title-mirror.js +51 -0
  85. package/dist/session/thread-title-mirror.js.map +1 -0
  86. package/dist/storage/domains/credentials/base.d.ts +3 -1
  87. package/dist/storage/domains/credentials/base.d.ts.map +1 -1
  88. package/dist/storage/domains/credentials/base.js +33 -15
  89. package/dist/storage/domains/credentials/base.js.map +1 -1
  90. package/dist/storage/domains/custom-providers/base.d.ts +3 -1
  91. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -1
  92. package/dist/storage/domains/custom-providers/base.js +45 -25
  93. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  94. package/dist/storage/domains/integrations/base.d.ts +3 -1
  95. package/dist/storage/domains/integrations/base.d.ts.map +1 -1
  96. package/dist/storage/domains/integrations/base.js +28 -12
  97. package/dist/storage/domains/integrations/base.js.map +1 -1
  98. package/dist/storage/domains/source-control/base.d.ts +8 -0
  99. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  100. package/dist/storage/domains/source-control/base.js +14 -2
  101. package/dist/storage/domains/source-control/base.js.map +1 -1
  102. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  103. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  104. package/dist/storage/domains/source-control/inmemory.js +7 -0
  105. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  106. package/dist/storage/domains/work-items/base.d.ts +115 -12
  107. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  108. package/dist/storage/domains/work-items/base.js +497 -43
  109. package/dist/storage/domains/work-items/base.js.map +1 -1
  110. package/dist/work-item-branch.d.ts +23 -0
  111. package/dist/work-item-branch.d.ts.map +1 -0
  112. package/dist/work-item-branch.js +39 -0
  113. package/dist/work-item-branch.js.map +1 -0
  114. package/factory-skills/factory-rereview/SKILL.md +2 -0
  115. package/factory-skills/factory-review/SKILL.md +2 -0
  116. package/package.json +7 -7
@@ -0,0 +1,305 @@
1
+ import { factoryAttentionKey, factoryDecisionAttentionIdentity } from "../storage/domains/work-items/base.js";
2
+ import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
3
+ import { registerApiRoute } from "@mastra/core/server";
4
+ //#region src/routes/attention.ts
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
+ const DEFAULT_PAGE_SIZE = 25;
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
+ function parseAttentionView(raw) {
13
+ if (!raw || raw === "open") return "open";
14
+ if (raw === "unread" || raw === "archived") return raw;
15
+ }
16
+ function parseAttentionLimit(raw) {
17
+ const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_PAGE_SIZE;
18
+ if (!Number.isFinite(parsed)) return DEFAULT_PAGE_SIZE;
19
+ return Math.max(1, Math.min(MAX_PAGE_SIZE, parsed));
20
+ }
21
+ function attentionIdentity(decision) {
22
+ return factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence);
23
+ }
24
+ function attentionKey(factoryProjectId, decision) {
25
+ return factoryAttentionKey(factoryProjectId, attentionIdentity(decision));
26
+ }
27
+ function failureOccurredAt(decision) {
28
+ return decision.completedAt ?? decision.updatedAt;
29
+ }
30
+ function encodeAttentionCursor(decision) {
31
+ return Buffer.from(JSON.stringify([failureOccurredAt(decision).toISOString(), decision.id]), "utf8").toString("base64url");
32
+ }
33
+ function parseAttentionCursor(raw) {
34
+ if (!raw) return void 0;
35
+ try {
36
+ 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
+ };
44
+ } catch {
45
+ return;
46
+ }
47
+ }
48
+ function parseFailureOccurrence(raw) {
49
+ if (!raw || !/^(0|[1-9]\d*)$/.test(raw)) return void 0;
50
+ const occurrence = Number(raw);
51
+ return Number.isSafeInteger(occurrence) ? occurrence : void 0;
52
+ }
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";
63
+ return {
64
+ kind: "work-item",
65
+ workItemId: item.id,
66
+ board: review ? "review" : "work"
67
+ };
68
+ }
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
+ };
87
+ }
88
+ function receiptRoute(dependencies, verb, action) {
89
+ return registerApiRoute(`/web/factory/projects/:id/attention/automation-failed/:decisionId/:occurrence/${verb}`, {
90
+ method: "POST",
91
+ requiresAuth: false,
92
+ handler: async (context) => {
93
+ const resolved = await dependencies.resolveProject(context);
94
+ 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);
98
+ await dependencies.workItems.ensureReady();
99
+ const receipt = await dependencies.workItems.setAttentionReceipt({
100
+ orgId: resolved.orgId,
101
+ factoryProjectId: resolved.factoryProjectId,
102
+ userId: resolved.userId,
103
+ decisionId,
104
+ failureOccurrence,
105
+ action,
106
+ now: /* @__PURE__ */ new Date()
107
+ });
108
+ if (!receipt) return context.json({ error: "attention_item_not_current" }, 409);
109
+ return context.json({ receipt: {
110
+ key: factoryAttentionKey(resolved.factoryProjectId, receipt),
111
+ state: receipt.state,
112
+ readAt: receipt.readAt.toISOString(),
113
+ archivedAt: receipt.archivedAt?.toISOString() ?? null
114
+ } });
115
+ }
116
+ });
117
+ }
118
+ function buildAttentionRoutes(dependencies) {
119
+ const { workItems } = dependencies;
120
+ return [
121
+ registerApiRoute("/web/factory/projects/:id/attention", {
122
+ method: "GET",
123
+ requiresAuth: false,
124
+ handler: async (context) => {
125
+ const resolved = await dependencies.resolveProject(context);
126
+ if ("response" in resolved) return resolved.response;
127
+ const view = parseAttentionView(context.req.query("view"));
128
+ if (view === void 0) return context.json({ error: "invalid_attention_view" }, 400);
129
+ const cursorRaw = context.req.query("before");
130
+ const before = parseAttentionCursor(cursorRaw);
131
+ if (cursorRaw && !before) return context.json({ error: "invalid_cursor" }, 400);
132
+ 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
+ }),
139
+ workItems.countDeferredDecisionsByStatuses({
140
+ orgId: resolved.orgId,
141
+ factoryProjectId: resolved.factoryProjectId,
142
+ statuses: ["proposed"]
143
+ }),
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
+ })
160
+ ]);
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
+ }
231
+ return context.json({
232
+ items: visible.map(({ decision, item, receipt }) => attentionItem(resolved.factoryProjectId, decision, item, receipt)),
233
+ openCount,
234
+ approvalCount,
235
+ badgeCount,
236
+ 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) } : {}
242
+ });
243
+ }
244
+ }),
245
+ registerApiRoute("/web/factory/projects/:id/attention/read-all", {
246
+ method: "POST",
247
+ requiresAuth: false,
248
+ handler: async (context) => {
249
+ const resolved = await dependencies.resolveProject(context);
250
+ if ("response" in resolved) return resolved.response;
251
+ const cursorRaw = context.req.query("before");
252
+ const initialBefore = parseAttentionCursor(cursorRaw);
253
+ if (cursorRaw && !initialBefore) return context.json({ error: "invalid_cursor" }, 400);
254
+ await workItems.ensureReady();
255
+ let before = initialBefore;
256
+ let pages = 0;
257
+ 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()
277
+ });
278
+ const last = page.decisions.at(-1);
279
+ if (!page.hasMore || !last) break;
280
+ if (pages === MAX_RECEIPT_SCAN_PAGES) {
281
+ hasMore = true;
282
+ nextCursor = encodeAttentionCursor(last);
283
+ break;
284
+ }
285
+ before = {
286
+ occurredAt: failureOccurredAt(last),
287
+ id: last.id
288
+ };
289
+ }
290
+ return context.json({
291
+ ok: true,
292
+ hasMore,
293
+ ...nextCursor ? { nextCursor } : {}
294
+ });
295
+ }
296
+ }),
297
+ receiptRoute(dependencies, "read", "read"),
298
+ receiptRoute(dependencies, "archive", "archive"),
299
+ receiptRoute(dependencies, "restore", "restore")
300
+ ];
301
+ }
302
+ //#endregion
303
+ export { buildAttentionRoutes, factoryDecisionType };
304
+
305
+ //# sourceMappingURL=attention.js.map
@@ -0,0 +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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"}
@@ -23,7 +23,7 @@ import type { MemorySettingsStorage } from '../storage/domains/memory-settings/b
23
23
  import type { ModelPacksStorage } from '../storage/domains/model-packs/base.js';
24
24
  import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
25
25
  import type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';
26
- import type { SourceControlStorage } from '../storage/domains/source-control/base.js';
26
+ import { type SourceControlStorage } from '../storage/domains/source-control/base.js';
27
27
  import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
28
28
  import type { RouteAuth } from './route.js';
29
29
  export interface IntegrationRegistration {
@@ -75,7 +75,6 @@ export interface FactoryApiRoutesDeps {
75
75
  prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;
76
76
  }) => void;
77
77
  }
78
- export declare function factoryRuleBranch(item: FactoryBindingPreparationInput['item']): string;
79
78
  /**
80
79
  * Start a factory run for a rule binding: ensure the source-control session the
81
80
  * coordinator requires, then hand it to `prepare` along with the factory's
@@ -83,7 +82,7 @@ export declare function factoryRuleBranch(item: FactoryBindingPreparationInput['
83
82
  * browser and no interactive user, so nothing else would catch a regression in
84
83
  * what it forwards.
85
84
  */
86
- export declare function prepareFactoryRuleBinding(github: GithubIntegration, coordinator: FactoryStartCoordinator, projects: FactoryProjectsStorage, input: FactoryBindingPreparationInput): Promise<void>;
85
+ export declare function prepareFactoryRuleBinding(github: GithubIntegration, coordinator: Pick<FactoryStartCoordinator, 'prepare'>, projects: FactoryProjectsStorage, input: FactoryBindingPreparationInput): Promise<void>;
87
86
  /**
88
87
  * Build the {@link IntegrationContext} handed to an integration when the
89
88
  * factory collects its capabilities (routes, workers). One shape everywhere:
@@ -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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"}
@@ -1,10 +1,14 @@
1
- import { isFactoryRuleStage } from "../rules/types.js";
1
+ import { MaterializeError } from "../integrations/github/sandbox.js";
2
+ import { factoryRuleStage } from "../rules/types.js";
2
3
  import { getGithubFeatureDiagnostics } from "../integrations/github/config.js";
3
4
  import { invalidateCustomProvidersSnapshots } from "./custom-provider-source.js";
4
- import { ensureFactorySourceSession, resolveFactoryDefaultModelId } from "../session/factory-session.js";
5
+ import { FactoryDispatchError } from "../rules/dispatch-errors.js";
6
+ import { FactorySourceSessionResolutionError, ensureFactorySourceSession, resolveFactoryDefaultModelId } from "../session/factory-session.js";
5
7
  import { FactoryStartCoordinator } from "../rules/start-coordinator.js";
6
8
  import { FactoryTransitionService } from "../rules/transition-service.js";
7
9
  import { LiveSessions } from "../session/live-sessions.js";
10
+ import { SourceControlConnectionNotFoundError } from "../storage/domains/source-control/base.js";
11
+ import { workItemBranch, workItemBranchSource } from "../work-item-branch.js";
8
12
  import { ConfigRoutes } from "./config.js";
9
13
  import { buildFsRoutes } from "./fs.js";
10
14
  import { IntakeRoutes } from "./intake.js";
@@ -15,6 +19,16 @@ import { invalidateTenantCredentialSnapshots } from "./tenant-credentials.js";
15
19
  import { WorkItemRoutes } from "./work-items.js";
16
20
  import { registerApiRoute } from "@mastra/core/server";
17
21
  //#region src/routes/surface.ts
22
+ const MATERIALIZE_FAILURE_CODE = {
23
+ "git-missing": "repository_git_missing",
24
+ "egress-blocked": "repository_egress_blocked",
25
+ "clone-failed": "repository_clone_failed",
26
+ "pull-failed": "repository_pull_failed",
27
+ "push-failed": "repository_push_failed",
28
+ "commit-failed": "repository_commit_failed",
29
+ "gh-missing": "repository_cli_missing",
30
+ "pr-failed": "repository_pr_failed"
31
+ };
18
32
  function guardIntegrationRoutes({ integration, ready, ensureReady, routes }) {
19
33
  if (ready) return routes;
20
34
  return routes.map((route) => {
@@ -55,15 +69,6 @@ function guardIntegrationRoutes({ integration, ready, ensureReady, routes }) {
55
69
  };
56
70
  });
57
71
  }
58
- function factoryRuleBranch(item) {
59
- const metadata = item.metadata ?? {};
60
- const issueNumber = metadata.githubIssueNumber ?? metadata.number;
61
- if (item.externalSource?.integrationId === "github" && item.externalSource.type === "issue" && typeof issueNumber === "number") return `factory/issue-${issueNumber}`;
62
- const pullRequestNumber = metadata.githubPullRequestNumber ?? metadata.number;
63
- if (item.externalSource?.integrationId === "github" && item.externalSource.type === "pull-request" && typeof pullRequestNumber === "number") return `factory/pr-${pullRequestNumber}`;
64
- if (item.externalSource?.integrationId === "linear" && typeof metadata.identifier === "string") return `factory/linear-${metadata.identifier.toLowerCase()}`;
65
- throw new Error("Factory skill invocation requires a supported issue or pull request identifier.");
66
- }
67
72
  /**
68
73
  * Start a factory run for a rule binding: ensure the source-control session the
69
74
  * coordinator requires, then hand it to `prepare` along with the factory's
@@ -72,39 +77,52 @@ function factoryRuleBranch(item) {
72
77
  * what it forwards.
73
78
  */
74
79
  async function prepareFactoryRuleBinding(github, coordinator, projects, input) {
75
- const branch = factoryRuleBranch(input.item);
76
- const repositorySlug = typeof input.item.metadata?.repository === "string" ? input.item.metadata.repository : void 0;
77
- const preparedSession = await ensureFactorySourceSession({
78
- sourceControl: github.sourceControlStorage,
79
- orgId: input.record.orgId,
80
- factoryProjectId: input.record.factoryProjectId,
81
- repositorySlug,
82
- branch
83
- });
84
- const destinationStage = input.item.stages.length === 1 ? input.item.stages[0] : void 0;
85
- if (!isFactoryRuleStage(destinationStage)) throw new Error("Factory skill invocation requires one exclusive board stage.");
86
- await coordinator.prepare({
87
- orgId: input.record.orgId,
88
- userId: preparedSession.userId,
89
- factoryProjectId: input.record.factoryProjectId,
90
- sessionId: preparedSession.sessionId,
91
- defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),
92
- threadTitle: `${input.role === "review" ? "PR" : "Issue"}: ${input.item.title}`,
93
- kickoffKey: input.record.id,
94
- destinationStage,
95
- workItem: {
80
+ try {
81
+ const branch = workItemBranch({
96
82
  id: input.item.id,
97
- role: input.role,
98
- input: {
99
- externalSource: input.item.externalSource,
100
- parentWorkItemId: input.item.parentWorkItemId,
101
- title: input.item.title,
102
- stages: ["intake"],
103
- sessions: input.item.sessions,
104
- metadata: input.item.metadata
83
+ source: workItemBranchSource(input.item.externalSource),
84
+ metadata: input.item.metadata
85
+ });
86
+ const destinationStage = factoryRuleStage(input.item.stages);
87
+ if (!destinationStage) throw new FactoryDispatchError("unsupported_provider_item", "Factory skill invocation requires one exclusive board stage.");
88
+ const repositorySlug = typeof input.item.metadata?.repository === "string" ? input.item.metadata.repository : void 0;
89
+ const preparedSession = await ensureFactorySourceSession({
90
+ sourceControl: github.sourceControlStorage,
91
+ orgId: input.record.orgId,
92
+ factoryProjectId: input.record.factoryProjectId,
93
+ repositorySlug,
94
+ branch,
95
+ attributeToUserId: input.record.approvedBy ?? void 0
96
+ });
97
+ await coordinator.prepare({
98
+ orgId: input.record.orgId,
99
+ userId: preparedSession.userId,
100
+ factoryProjectId: input.record.factoryProjectId,
101
+ sessionId: preparedSession.sessionId,
102
+ defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),
103
+ threadTitle: `${input.role === "review" ? "PR" : "Issue"}: ${input.item.title}`,
104
+ kickoffKey: input.record.id,
105
+ destinationStage,
106
+ workItem: {
107
+ id: input.item.id,
108
+ role: input.role,
109
+ input: {
110
+ externalSource: input.item.externalSource,
111
+ parentWorkItemId: input.item.parentWorkItemId,
112
+ title: input.item.title,
113
+ stages: ["intake"],
114
+ sessions: input.item.sessions,
115
+ metadata: input.item.metadata
116
+ }
105
117
  }
106
- }
107
- });
118
+ });
119
+ } catch (error) {
120
+ if (error instanceof FactoryDispatchError) throw error;
121
+ if (error instanceof FactorySourceSessionResolutionError) throw new FactoryDispatchError(error.reason === "connection" ? "source_control_missing" : "source_repository_missing", error.message, { cause: error });
122
+ if (error instanceof SourceControlConnectionNotFoundError) throw new FactoryDispatchError("source_control_missing", error.message, { cause: error });
123
+ if (error instanceof MaterializeError) throw new FactoryDispatchError(MATERIALIZE_FAILURE_CODE[error.code], error.message, { cause: error });
124
+ throw error;
125
+ }
108
126
  }
109
127
  /**
110
128
  * Build the {@link IntegrationContext} handed to an integration when the
@@ -307,6 +325,6 @@ function assembleFactoryApiRoutes(deps) {
307
325
  ];
308
326
  }
309
327
  //#endregion
310
- export { assembleFactoryApiRoutes, buildIntegrationContext, factoryRuleBranch, prepareFactoryRuleBinding };
328
+ export { assembleFactoryApiRoutes, buildIntegrationContext, prepareFactoryRuleBinding };
311
329
 
312
330
  //# sourceMappingURL=surface.js.map