@mastra/factory 0.24.0-alpha.11 → 0.24.0-alpha.13

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 (165) hide show
  1. package/README.md +4 -4
  2. package/dist/environment/build-runner.js +2 -1
  3. package/dist/environment/build-runner.js.map +1 -1
  4. package/dist/environment/build-workflow.js +1 -1
  5. package/dist/environment/build-workflow.js.map +1 -1
  6. package/dist/environment/build.js +16 -14
  7. package/dist/environment/build.js.map +1 -1
  8. package/dist/factory.d.ts.map +1 -1
  9. package/dist/factory.js +25 -1
  10. package/dist/factory.js.map +1 -1
  11. package/dist/integrations/github/app-identity.js.map +1 -1
  12. package/dist/integrations/github/integration.js +1 -1
  13. package/dist/integrations/github/integration.js.map +1 -1
  14. package/dist/integrations/github/reconcile-worker.js +1 -1
  15. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  16. package/dist/integrations/github/routes.d.ts.map +1 -1
  17. package/dist/integrations/github/routes.js +6 -3
  18. package/dist/integrations/github/routes.js.map +1 -1
  19. package/dist/integrations/github/rules.js +3 -2
  20. package/dist/integrations/github/rules.js.map +1 -1
  21. package/dist/integrations/github/sandbox.js +2 -2
  22. package/dist/integrations/github/sandbox.js.map +1 -1
  23. package/dist/integrations/gitlab/api.js.map +1 -1
  24. package/dist/integrations/gitlab/integration.js.map +1 -1
  25. package/dist/integrations/gitlab/reconciliation-config.js +2 -1
  26. package/dist/integrations/gitlab/reconciliation-config.js.map +1 -1
  27. package/dist/integrations/gitlab/rules.js.map +1 -1
  28. package/dist/integrations/incidentio/api.js.map +1 -1
  29. package/dist/integrations/incidentio/integration.js.map +1 -1
  30. package/dist/integrations/incidentio/rules.js.map +1 -1
  31. package/dist/integrations/issue-reconcile-worker.js +1 -1
  32. package/dist/integrations/issue-reconcile-worker.js.map +1 -1
  33. package/dist/integrations/issue-reconciler.js +1 -1
  34. package/dist/integrations/issue-reconciler.js.map +1 -1
  35. package/dist/integrations/jira/api.js.map +1 -1
  36. package/dist/integrations/jira/integration.js.map +1 -1
  37. package/dist/integrations/jira/rules.js.map +1 -1
  38. package/dist/integrations/linear/integration.js.map +1 -1
  39. package/dist/integrations/linear/rules.js.map +1 -1
  40. package/dist/integrations/platform/api-client.js.map +1 -1
  41. package/dist/integrations/platform/github/event-worker.js +1 -1
  42. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  43. package/dist/integrations/platform/github/integration.js +2 -2
  44. package/dist/integrations/platform/github/integration.js.map +1 -1
  45. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
  46. package/dist/integrations/platform/gitlab/integration.js.map +1 -1
  47. package/dist/integrations/platform/incidentio/integration.js +2 -1
  48. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  49. package/dist/integrations/platform/jira/integration.js.map +1 -1
  50. package/dist/integrations/platform/linear/event-worker.js +1 -1
  51. package/dist/integrations/platform/linear/event-worker.js.map +1 -1
  52. package/dist/integrations/platform/linear/integration.js.map +1 -1
  53. package/dist/integrations/slack/connect-route.js +1 -1
  54. package/dist/integrations/slack/connect-route.js.map +1 -1
  55. package/dist/integrations/slack/feed-publisher.js +1 -1
  56. package/dist/integrations/slack/feed-publisher.js.map +1 -1
  57. package/dist/integrations/slack/integration.js +1 -1
  58. package/dist/integrations/slack/integration.js.map +1 -1
  59. package/dist/integrations/slack/slack.js +2 -1
  60. package/dist/integrations/slack/slack.js.map +1 -1
  61. package/dist/integrations/workos/integration.js.map +1 -1
  62. package/dist/packages/_internals/workspace/dist/index.js +18 -1
  63. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  64. package/dist/routes/attention-activity.js.map +1 -1
  65. package/dist/routes/attention-parked.js.map +1 -1
  66. package/dist/routes/attention-providers.js.map +1 -1
  67. package/dist/routes/automation-runs.js.map +1 -1
  68. package/dist/routes/contracts.js +1 -1
  69. package/dist/routes/contracts.js.map +1 -1
  70. package/dist/routes/custom-provider-source.js.map +1 -1
  71. package/dist/routes/fs.d.ts +31 -23
  72. package/dist/routes/fs.d.ts.map +1 -1
  73. package/dist/routes/fs.js +218 -64
  74. package/dist/routes/fs.js.map +1 -1
  75. package/dist/routes/intake.js +2 -1
  76. package/dist/routes/intake.js.map +1 -1
  77. package/dist/routes/knowledge.js +1 -1
  78. package/dist/routes/knowledge.js.map +1 -1
  79. package/dist/routes/oauth.js +3 -2
  80. package/dist/routes/oauth.js.map +1 -1
  81. package/dist/routes/projects.js +32 -30
  82. package/dist/routes/projects.js.map +1 -1
  83. package/dist/routes/session-access.d.ts +55 -0
  84. package/dist/routes/session-access.d.ts.map +1 -0
  85. package/dist/routes/session-access.js +165 -0
  86. package/dist/routes/session-access.js.map +1 -0
  87. package/dist/routes/skills.js.map +1 -1
  88. package/dist/routes/source-control-sessions.d.ts +5 -0
  89. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  90. package/dist/routes/source-control-sessions.js +87 -10
  91. package/dist/routes/source-control-sessions.js.map +1 -1
  92. package/dist/routes/surface.d.ts +5 -0
  93. package/dist/routes/surface.d.ts.map +1 -1
  94. package/dist/routes/surface.js +10 -4
  95. package/dist/routes/surface.js.map +1 -1
  96. package/dist/routes/tenant-credentials.js.map +1 -1
  97. package/dist/routes/work-items.js.map +1 -1
  98. package/dist/rules/binding-context.js +4 -2
  99. package/dist/rules/binding-context.js.map +1 -1
  100. package/dist/rules/dispatcher.js +4 -4
  101. package/dist/rules/dispatcher.js.map +1 -1
  102. package/dist/rules/start-coordinator.js.map +1 -1
  103. package/dist/rules/tools.js +2 -2
  104. package/dist/rules/tools.js.map +1 -1
  105. package/dist/rules/transition-service.js.map +1 -1
  106. package/dist/sandbox/callback-factory-sandbox.js.map +1 -1
  107. package/dist/sandbox/local-factory-sandbox.js.map +1 -1
  108. package/dist/sandbox/session-retirement.js.map +1 -1
  109. package/dist/session/live-sessions.js.map +1 -1
  110. package/dist/session/memory-settings-hydration.js +2 -1
  111. package/dist/session/memory-settings-hydration.js.map +1 -1
  112. package/dist/session/resource-migration.d.ts +18 -0
  113. package/dist/session/resource-migration.d.ts.map +1 -1
  114. package/dist/session/resource-migration.js +24 -1
  115. package/dist/session/resource-migration.js.map +1 -1
  116. package/dist/session/session-threads.d.ts +9 -0
  117. package/dist/session/session-threads.d.ts.map +1 -1
  118. package/dist/session/session-threads.js +24 -4
  119. package/dist/session/session-threads.js.map +1 -1
  120. package/dist/spa-static.js.map +1 -1
  121. package/dist/state-signing.js +1 -1
  122. package/dist/state-signing.js.map +1 -1
  123. package/dist/storage/domains/audit/base.js.map +1 -1
  124. package/dist/storage/domains/audit/domain.js.map +1 -1
  125. package/dist/storage/domains/channel-identity/base.js.map +1 -1
  126. package/dist/storage/domains/comments/base.js.map +1 -1
  127. package/dist/storage/domains/comments/domain.js.map +1 -1
  128. package/dist/storage/domains/comments/feed-context.js.map +1 -1
  129. package/dist/storage/domains/credentials/base.js.map +1 -1
  130. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  131. package/dist/storage/domains/intake/base.js.map +1 -1
  132. package/dist/storage/domains/integrations/base.js.map +1 -1
  133. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  134. package/dist/storage/domains/model-defaults/base.js.map +1 -1
  135. package/dist/storage/domains/projects/base.js.map +1 -1
  136. package/dist/storage/domains/queue-health/base.js.map +1 -1
  137. package/dist/storage/domains/source-control/base.d.ts +15 -0
  138. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  139. package/dist/storage/domains/source-control/base.js +29 -0
  140. package/dist/storage/domains/source-control/base.js.map +1 -1
  141. package/dist/storage/domains/source-control/inmemory.d.ts +5 -1
  142. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  143. package/dist/storage/domains/source-control/inmemory.js +7 -0
  144. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  145. package/dist/storage/domains/work-items/base.js +2 -1
  146. package/dist/storage/domains/work-items/base.js.map +1 -1
  147. package/dist/storage/domains/work-items/board-version.js.map +1 -1
  148. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  149. package/dist/tools/messaging-tools.d.ts.map +1 -1
  150. package/dist/tools/messaging-tools.js +201 -73
  151. package/dist/tools/messaging-tools.js.map +1 -1
  152. package/dist/tools/run-watch.d.ts +2 -0
  153. package/dist/tools/run-watch.d.ts.map +1 -1
  154. package/dist/tools/run-watch.js +42 -2
  155. package/dist/tools/run-watch.js.map +1 -1
  156. package/dist/tools/session-tools.d.ts.map +1 -1
  157. package/dist/tools/session-tools.js +25 -10
  158. package/dist/tools/session-tools.js.map +1 -1
  159. package/dist/tools/sessions-state-processor.js +2 -2
  160. package/dist/tools/sessions-state-processor.js.map +1 -1
  161. package/dist/workspace.d.ts +5 -2
  162. package/dist/workspace.d.ts.map +1 -1
  163. package/dist/workspace.js +26 -13
  164. package/dist/workspace.js.map +1 -1
  165. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"attention-parked.js","names":["#workItems","#liveSessions","#parked","#withReceipts"],"sources":["../../src/routes/attention-parked.ts"],"sourcesContent":["/** Sessions parked on a tool, read live from the registry: the item exists exactly as long as an answer is owed. */\n\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport type {\n FactoryAttentionIdentity,\n FactoryAttentionReceiptRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { factoryAgentWaitingAttentionIdentity, factoryAttentionKey } from '../storage/domains/work-items/base.js';\nimport type {\n AttentionCounts,\n AttentionEntry,\n AttentionLatest,\n AttentionPageArgs,\n AttentionPageResult,\n AttentionProvider,\n AttentionScope,\n AttentionStreamPosition,\n} from './attention-providers.js';\nimport { matchesView, receiptScope } from './attention-providers.js';\n\ninterface ParkedSession {\n item: WorkItemRow;\n role: string;\n sessionId: string;\n threadId: string;\n toolName: string;\n occurredAt: Date;\n identity: FactoryAttentionIdentity;\n}\n\ninterface ReceiptedParkedSession {\n entry: ParkedSession;\n receipt: FactoryAttentionReceiptRecord | undefined;\n}\n\nfunction parkedRunLabel(toolName: string): string {\n return toolName === 'submit_plan' ? 'Plan waiting for review' : 'Agent is waiting for an answer';\n}\n\nfunction olderThan(entry: ParkedSession, before: AttentionStreamPosition | undefined): boolean {\n if (!before) return true;\n const gap = entry.occurredAt.getTime() - before.occurredAt.getTime();\n return gap < 0 || (gap === 0 && entry.sessionId.localeCompare(before.id) > 0);\n}\n\nfunction toItem(scope: AttentionScope, { entry, receipt }: ReceiptedParkedSession): Record<string, unknown> {\n return {\n key: factoryAttentionKey(scope.factoryProjectId, entry.identity),\n kind: 'agent-waiting' as const,\n sessionId: entry.sessionId,\n threadId: entry.threadId,\n role: entry.role,\n toolName: entry.toolName,\n occurrence: entry.identity.occurrence,\n workItemId: entry.item.id,\n title: entry.item.title,\n detail: parkedRunLabel(entry.toolName),\n occurredAt: entry.occurredAt.toISOString(),\n read: receipt !== undefined,\n archived: receipt?.state === 'archived',\n target: { kind: 'thread' as const, sessionId: entry.sessionId, threadId: entry.threadId },\n };\n}\n\nexport class ParkedRunAttentionProvider implements AttentionProvider {\n readonly kind = 'agent-waiting' as const;\n readonly #workItems: WorkItemsStorage;\n readonly #liveSessions: Pick<LiveSessions, 'parkedIn'>;\n\n constructor({\n workItems,\n liveSessions,\n }: {\n workItems: WorkItemsStorage;\n liveSessions: Pick<LiveSessions, 'parkedIn'>;\n }) {\n this.#workItems = workItems;\n this.#liveSessions = liveSessions;\n }\n\n /** Newest park first. A session bound to several cards is listed once, under the first card. */\n async #parked(scope: AttentionScope): Promise<ParkedSession[]> {\n const runBySession = new Map(\n this.#liveSessions.parkedIn(scope.factoryProjectId).map(({ sessionId, run }) => [sessionId, run]),\n );\n if (runBySession.size === 0) return [];\n const items = await this.#workItems.list({ orgId: scope.orgId, factoryProjectId: scope.factoryProjectId });\n const bySession = new Map<string, ParkedSession>();\n for (const item of items) {\n for (const [role, ref] of Object.entries(item.sessions)) {\n const run = runBySession.get(ref.sessionId);\n if (!run || bySession.has(ref.sessionId)) continue;\n bySession.set(ref.sessionId, {\n item,\n role,\n sessionId: ref.sessionId,\n threadId: ref.threadId,\n toolName: run.toolName,\n occurredAt: new Date(run.suspendedAt),\n identity: factoryAgentWaitingAttentionIdentity(ref.sessionId, run.suspendedAt),\n });\n }\n }\n return [...bySession.values()].sort(\n (a, b) => b.occurredAt.getTime() - a.occurredAt.getTime() || a.sessionId.localeCompare(b.sessionId),\n );\n }\n\n async #withReceipts(scope: AttentionScope, parked: ParkedSession[]): Promise<ReceiptedParkedSession[]> {\n const receipts = await this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: parked.map(entry => entry.identity),\n });\n const byKey = new Map(receipts.map(receipt => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]));\n return parked.map(entry => ({\n entry,\n receipt: byKey.get(factoryAttentionKey(scope.factoryProjectId, entry.identity)),\n }));\n }\n\n async counts(scope: AttentionScope): Promise<AttentionCounts> {\n let open = 0;\n let unread = 0;\n for (const { receipt } of await this.#withReceipts(scope, await this.#parked(scope))) {\n if (receipt?.state !== 'archived') open += 1;\n if (receipt === undefined) unread += 1;\n }\n return { open, unread };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const [newest] = await this.#withReceipts(scope, (await this.#parked(scope)).slice(0, 1));\n if (!newest) return null;\n return {\n key: factoryAttentionKey(scope.factoryProjectId, newest.entry.identity),\n at: newest.entry.occurredAt,\n unread: newest.receipt === undefined,\n };\n }\n\n async page(scope: AttentionScope, { view, search, before, limit }: AttentionPageArgs): Promise<AttentionPageResult> {\n const parked = (await this.#parked(scope)).filter(entry => olderThan(entry, before));\n const entries: AttentionEntry[] = [];\n for (const receipted of await this.#withReceipts(scope, parked)) {\n const { entry, receipt } = receipted;\n if (!matchesView(view, receipt)) continue;\n if (search && !`${entry.item.title} ${parkedRunLabel(entry.toolName)}`.toLowerCase().includes(search)) continue;\n if (entries.length === limit) return { entries, hasMore: true };\n entries.push({\n occurredAt: entry.occurredAt,\n resumeCursor: { occurredAt: entry.occurredAt, id: entry.sessionId },\n item: toItem(scope, receipted),\n });\n }\n return { entries, hasMore: false };\n }\n\n async markAllRead(\n scope: AttentionScope,\n { before, now }: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean }> {\n const parked = (await this.#parked(scope)).filter(entry => olderThan(entry, before));\n if (parked.length > 0) {\n await this.#workItems.markAttentionReceiptsRead({\n ...receiptScope(scope),\n identities: parked.map(entry => entry.identity),\n now,\n });\n }\n return { hasMore: false };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"attention-parked.js","names":[],"sources":["../../src/routes/attention-parked.ts"],"sourcesContent":["/** Sessions parked on a tool, read live from the registry: the item exists exactly as long as an answer is owed. */\n\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport type {\n FactoryAttentionIdentity,\n FactoryAttentionReceiptRecord,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { factoryAgentWaitingAttentionIdentity, factoryAttentionKey } from '../storage/domains/work-items/base.js';\nimport type {\n AttentionCounts,\n AttentionEntry,\n AttentionLatest,\n AttentionPageArgs,\n AttentionPageResult,\n AttentionProvider,\n AttentionScope,\n AttentionStreamPosition,\n} from './attention-providers.js';\nimport { matchesView, receiptScope } from './attention-providers.js';\n\ninterface ParkedSession {\n item: WorkItemRow;\n role: string;\n sessionId: string;\n threadId: string;\n toolName: string;\n occurredAt: Date;\n identity: FactoryAttentionIdentity;\n}\n\ninterface ReceiptedParkedSession {\n entry: ParkedSession;\n receipt: FactoryAttentionReceiptRecord | undefined;\n}\n\nfunction parkedRunLabel(toolName: string): string {\n return toolName === 'submit_plan' ? 'Plan waiting for review' : 'Agent is waiting for an answer';\n}\n\nfunction olderThan(entry: ParkedSession, before: AttentionStreamPosition | undefined): boolean {\n if (!before) return true;\n const gap = entry.occurredAt.getTime() - before.occurredAt.getTime();\n return gap < 0 || (gap === 0 && entry.sessionId.localeCompare(before.id) > 0);\n}\n\nfunction toItem(scope: AttentionScope, { entry, receipt }: ReceiptedParkedSession): Record<string, unknown> {\n return {\n key: factoryAttentionKey(scope.factoryProjectId, entry.identity),\n kind: 'agent-waiting' as const,\n sessionId: entry.sessionId,\n threadId: entry.threadId,\n role: entry.role,\n toolName: entry.toolName,\n occurrence: entry.identity.occurrence,\n workItemId: entry.item.id,\n title: entry.item.title,\n detail: parkedRunLabel(entry.toolName),\n occurredAt: entry.occurredAt.toISOString(),\n read: receipt !== undefined,\n archived: receipt?.state === 'archived',\n target: { kind: 'thread' as const, sessionId: entry.sessionId, threadId: entry.threadId },\n };\n}\n\nexport class ParkedRunAttentionProvider implements AttentionProvider {\n readonly kind = 'agent-waiting' as const;\n readonly #workItems: WorkItemsStorage;\n readonly #liveSessions: Pick<LiveSessions, 'parkedIn'>;\n\n constructor({\n workItems,\n liveSessions,\n }: {\n workItems: WorkItemsStorage;\n liveSessions: Pick<LiveSessions, 'parkedIn'>;\n }) {\n this.#workItems = workItems;\n this.#liveSessions = liveSessions;\n }\n\n /** Newest park first. A session bound to several cards is listed once, under the first card. */\n async #parked(scope: AttentionScope): Promise<ParkedSession[]> {\n const runBySession = new Map(\n this.#liveSessions.parkedIn(scope.factoryProjectId).map(({ sessionId, run }) => [sessionId, run]),\n );\n if (runBySession.size === 0) return [];\n const items = await this.#workItems.list({ orgId: scope.orgId, factoryProjectId: scope.factoryProjectId });\n const bySession = new Map<string, ParkedSession>();\n for (const item of items) {\n for (const [role, ref] of Object.entries(item.sessions)) {\n const run = runBySession.get(ref.sessionId);\n if (!run || bySession.has(ref.sessionId)) continue;\n bySession.set(ref.sessionId, {\n item,\n role,\n sessionId: ref.sessionId,\n threadId: ref.threadId,\n toolName: run.toolName,\n occurredAt: new Date(run.suspendedAt),\n identity: factoryAgentWaitingAttentionIdentity(ref.sessionId, run.suspendedAt),\n });\n }\n }\n return [...bySession.values()].sort(\n (a, b) => b.occurredAt.getTime() - a.occurredAt.getTime() || a.sessionId.localeCompare(b.sessionId),\n );\n }\n\n async #withReceipts(scope: AttentionScope, parked: ParkedSession[]): Promise<ReceiptedParkedSession[]> {\n const receipts = await this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: parked.map(entry => entry.identity),\n });\n const byKey = new Map(receipts.map(receipt => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]));\n return parked.map(entry => ({\n entry,\n receipt: byKey.get(factoryAttentionKey(scope.factoryProjectId, entry.identity)),\n }));\n }\n\n async counts(scope: AttentionScope): Promise<AttentionCounts> {\n let open = 0;\n let unread = 0;\n for (const { receipt } of await this.#withReceipts(scope, await this.#parked(scope))) {\n if (receipt?.state !== 'archived') open += 1;\n if (receipt === undefined) unread += 1;\n }\n return { open, unread };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const [newest] = await this.#withReceipts(scope, (await this.#parked(scope)).slice(0, 1));\n if (!newest) return null;\n return {\n key: factoryAttentionKey(scope.factoryProjectId, newest.entry.identity),\n at: newest.entry.occurredAt,\n unread: newest.receipt === undefined,\n };\n }\n\n async page(scope: AttentionScope, { view, search, before, limit }: AttentionPageArgs): Promise<AttentionPageResult> {\n const parked = (await this.#parked(scope)).filter(entry => olderThan(entry, before));\n const entries: AttentionEntry[] = [];\n for (const receipted of await this.#withReceipts(scope, parked)) {\n const { entry, receipt } = receipted;\n if (!matchesView(view, receipt)) continue;\n if (search && !`${entry.item.title} ${parkedRunLabel(entry.toolName)}`.toLowerCase().includes(search)) continue;\n if (entries.length === limit) return { entries, hasMore: true };\n entries.push({\n occurredAt: entry.occurredAt,\n resumeCursor: { occurredAt: entry.occurredAt, id: entry.sessionId },\n item: toItem(scope, receipted),\n });\n }\n return { entries, hasMore: false };\n }\n\n async markAllRead(\n scope: AttentionScope,\n { before, now }: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean }> {\n const parked = (await this.#parked(scope)).filter(entry => olderThan(entry, before));\n if (parked.length > 0) {\n await this.#workItems.markAttentionReceiptsRead({\n ...receiptScope(scope),\n identities: parked.map(entry => entry.identity),\n now,\n });\n }\n return { hasMore: false };\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"attention-providers.js","names":["#workItems","#spec","#boards","#scan","#receiptsFor","#linkedItems","#comments","#resolve"],"sources":["../../src/routes/attention-providers.ts"],"sourcesContent":["import { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { decisionRetryable, overtakenDecisionIds } from '../rules/decision-applicability.js';\n/**\n * Per-kind attention providers. Each kind owns its counts, its bounded page\n * scan, and its wire item shape; the route layer k-way merges provider pages\n * on `occurredAt desc` with one resumable cursor per kind.\n */\n\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport type {\n WorkItemCommentRow,\n WorkItemCommentsStorage,\n WorkItemMentionRow,\n} from '../storage/domains/comments/base.js';\nimport type {\n FactoryAttentionIdentity,\n FactoryAttentionKind,\n FactoryAttentionReceiptRecord,\n FactoryDeferredDecisionRecord,\n FactoryDispatchStatus,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport {\n factoryAttentionKey,\n factoryDecisionAttentionIdentity,\n factoryMentionAttentionIdentity,\n} from '../storage/domains/work-items/base.js';\n\nexport type FactoryAttentionView = 'open' | 'unread' | 'archived';\n\nexport interface AttentionScope {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n}\n\nexport interface AttentionStreamPosition {\n occurredAt: Date;\n id: string;\n}\n\nexport interface AttentionEntry {\n occurredAt: Date;\n /** Position resuming the provider's stream right after this entry. */\n resumeCursor: AttentionStreamPosition;\n item: Record<string, unknown>;\n}\n\nexport interface AttentionPageArgs {\n view: FactoryAttentionView;\n search?: string;\n before?: AttentionStreamPosition;\n limit: number;\n}\n\nexport interface AttentionPageResult {\n entries: AttentionEntry[];\n /** More rows behind the scan once every returned entry is consumed. */\n hasMore: boolean;\n /** Resume point for a scan-budget stop past the last returned entry. */\n continuation?: AttentionStreamPosition;\n}\n\nexport interface AttentionCounts {\n open: number;\n unread: number;\n}\n\nexport interface AttentionLatest {\n key: string;\n at: Date;\n unread: boolean;\n}\n\nexport interface AttentionProvider {\n kind: FactoryAttentionKind;\n counts(scope: AttentionScope): Promise<AttentionCounts>;\n latest(scope: AttentionScope): Promise<AttentionLatest | null>;\n page(scope: AttentionScope, args: AttentionPageArgs): Promise<AttentionPageResult>;\n markAllRead(\n scope: AttentionScope,\n args: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }>;\n}\n\nexport const SCAN_PAGE_SIZE = 50;\n// Receipt filtering is bounded per request; the response cursor resumes after the last scan.\nconst MAX_RECEIPT_SCAN_PAGES = 4;\n\ninterface ScanBatch<T> {\n rows: T[];\n /** Set only when the page budget stopped a scan that still had rows behind it. */\n continuation?: AttentionStreamPosition;\n}\n\n/**\n * One bounded keyset scan, shared by every provider read. Walks newest-first\n * from `before` until the stream runs dry or the page budget is spent; the\n * final batch of a budget stop carries the position to resume from.\n */\nexport async function* scanBatches<T>(\n fetchBatch: (before?: AttentionStreamPosition) => Promise<{ rows: T[]; hasMore: boolean }>,\n positionOf: (row: T) => AttentionStreamPosition,\n before?: AttentionStreamPosition,\n): AsyncGenerator<ScanBatch<T>> {\n let cursor = before;\n for (let scanned = 1; scanned <= MAX_RECEIPT_SCAN_PAGES; scanned += 1) {\n const { rows, hasMore } = await fetchBatch(cursor);\n const last = rows.at(-1);\n if (!last) return;\n const position = positionOf(last);\n yield { rows, ...(hasMore && scanned === MAX_RECEIPT_SCAN_PAGES ? { continuation: position } : {}) };\n if (!hasMore) return;\n cursor = position;\n }\n}\n\n/** Fills a page from the scan, stopping at `limit` entries or at the scan budget. */\nexport async function collectPage<T>(\n batches: AsyncGenerator<ScanBatch<T>>,\n limit: number,\n toEntries: (rows: T[]) => Promise<AttentionEntry[]>,\n): Promise<AttentionPageResult> {\n const entries: AttentionEntry[] = [];\n for await (const batch of batches) {\n for (const entry of await toEntries(batch.rows)) {\n if (entries.length === limit) return { entries, hasMore: true };\n entries.push(entry);\n }\n if (batch.continuation) return { entries, hasMore: true, continuation: batch.continuation };\n }\n return { entries, hasMore: false };\n}\n\n/** Marks every scanned batch read, reporting where a budget stop left off. */\nexport async function markScanRead<T>(\n batches: AsyncGenerator<ScanBatch<T>>,\n markBatch: (rows: T[]) => Promise<void>,\n): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n for await (const batch of batches) {\n await markBatch(batch.rows);\n if (batch.continuation) return { hasMore: true, continuation: batch.continuation };\n }\n return { hasMore: false };\n}\n\nexport function factoryDecisionType(decision: FactoryDeferredDecisionRecord): string {\n return typeof decision.decision.type === 'string' ? decision.decision.type.slice(0, 64) : 'unknown';\n}\n\nexport function matchesView(view: FactoryAttentionView, receipt: FactoryAttentionReceiptRecord | undefined): boolean {\n if (view === 'archived') return receipt?.state === 'archived';\n if (view === 'unread') return receipt === undefined;\n return receipt?.state !== 'archived';\n}\n\nfunction attentionTarget(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined) {\n if (!item) return { kind: 'rules' as const };\n const role = typeof decision.decision.role === 'string' ? decision.decision.role : undefined;\n const session = role ? item.sessions[role] : undefined;\n if (session) {\n return {\n kind: 'thread' as const,\n sessionId: session.sessionId,\n threadId: session.threadId,\n };\n }\n return {\n kind: 'work-item' as const,\n workItemId: item.id,\n board: boardForWorkItem(item),\n };\n}\n\n/** One deferred-decision status seen as attention: failures and parked runs differ only by this. */\nexport interface DecisionAttentionSpec {\n kind: FactoryAttentionKind;\n status: FactoryDispatchStatus;\n identity(decision: FactoryDeferredDecisionRecord): FactoryAttentionIdentity;\n occurredAt(decision: FactoryDeferredDecisionRecord): Date;\n title(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined): string;\n detail(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined): string;\n extra?(decision: FactoryDeferredDecisionRecord, context: { overtaken: boolean }): Record<string, unknown>;\n matches(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined, search: string): boolean;\n}\n\nexport class DecisionAttentionProvider implements AttentionProvider {\n readonly kind: FactoryAttentionKind;\n readonly #workItems: WorkItemsStorage;\n readonly #spec: DecisionAttentionSpec;\n readonly #boards: BoardRegistry;\n\n constructor(\n { workItems, boards }: { workItems: WorkItemsStorage; boards: BoardRegistry },\n spec: DecisionAttentionSpec,\n ) {\n this.#workItems = workItems;\n this.#boards = boards;\n this.#spec = spec;\n this.kind = spec.kind;\n }\n\n async counts(scope: AttentionScope): Promise<AttentionCounts> {\n const [total, receiptCount, archivedCount] = await Promise.all([\n this.#workItems.countDeferredDecisionsByStatuses({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n statuses: [this.#spec.status],\n }),\n this.#workItems.countAttentionReceipts({ ...receiptScope(scope), kind: this.kind }),\n this.#workItems.countAttentionReceipts({ ...receiptScope(scope), kind: this.kind, state: 'archived' }),\n ]);\n return {\n open: Math.max(0, total - archivedCount),\n unread: Math.max(0, total - receiptCount),\n };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const page = await this.#workItems.listDecisionPageByStatus({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n status: this.#spec.status,\n limit: 1,\n });\n const newest = page.decisions[0];\n if (!newest) return null;\n const identity = this.#spec.identity(newest);\n const receipts = await this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: [identity],\n });\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n at: this.#spec.occurredAt(newest),\n unread: receipts.length === 0,\n };\n }\n\n #scan(scope: AttentionScope, before?: AttentionStreamPosition) {\n return scanBatches<FactoryDeferredDecisionRecord>(\n async cursor => {\n const page = await this.#workItems.listDecisionPageByStatus({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n status: this.#spec.status,\n before: cursor,\n limit: SCAN_PAGE_SIZE,\n });\n return { rows: page.decisions, hasMore: page.hasMore };\n },\n decision => ({ occurredAt: this.#spec.occurredAt(decision), id: decision.id }),\n before,\n );\n }\n\n async page(scope: AttentionScope, { view, search, before, limit }: AttentionPageArgs): Promise<AttentionPageResult> {\n return collectPage(this.#scan(scope, before), limit, async decisions => {\n const [receiptByKey, itemById, overtaken] = await Promise.all([\n this.#receiptsFor(scope, decisions),\n this.#linkedItems(scope, decisions),\n overtakenDecisionIds(this.#workItems, this.#boards, scope, decisions),\n ]);\n const entries: AttentionEntry[] = [];\n for (const decision of decisions) {\n const identity = this.#spec.identity(decision);\n const receipt = receiptByKey.get(factoryAttentionKey(scope.factoryProjectId, identity));\n if (!matchesView(view, receipt)) continue;\n const item = decision.workItemId ? itemById.get(decision.workItemId) : undefined;\n if (search && !this.#spec.matches(decision, item, search)) continue;\n const occurredAt = this.#spec.occurredAt(decision);\n entries.push({\n occurredAt,\n resumeCursor: { occurredAt, id: decision.id },\n item: {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n kind: this.kind,\n decisionId: decision.id,\n occurrence: identity.occurrence,\n workItemId: decision.workItemId,\n title: this.#spec.title(decision, item),\n detail: this.#spec.detail(decision, item),\n decisionType: factoryDecisionType(decision),\n ...this.#spec.extra?.(decision, { overtaken: overtaken.has(decision.id) }),\n occurredAt: occurredAt.toISOString(),\n read: receipt !== undefined,\n archived: receipt?.state === 'archived',\n target: attentionTarget(decision, item),\n },\n });\n }\n return entries;\n });\n }\n\n async #receiptsFor(\n scope: AttentionScope,\n decisions: FactoryDeferredDecisionRecord[],\n ): Promise<Map<string, FactoryAttentionReceiptRecord>> {\n const receipts = await this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: decisions.map(decision => this.#spec.identity(decision)),\n });\n return new Map(receipts.map(receipt => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]));\n }\n\n async #linkedItems(\n scope: AttentionScope,\n decisions: FactoryDeferredDecisionRecord[],\n ): Promise<Map<string, WorkItemRow>> {\n const items = await this.#workItems.listByIds({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n ids: decisions.flatMap(decision => (decision.workItemId ? [decision.workItemId] : [])),\n });\n return new Map(items.map(item => [item.id, item]));\n }\n\n async markAllRead(\n scope: AttentionScope,\n { before, now }: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n return markScanRead(this.#scan(scope, before), async decisions => {\n await this.#workItems.markAttentionReceiptsRead({\n ...receiptScope(scope),\n identities: decisions.map(decision => this.#spec.identity(decision)),\n now,\n });\n });\n }\n}\n\nexport const failedDecisionAttentionSpec: DecisionAttentionSpec = {\n kind: 'automation-failed',\n status: 'failed',\n identity: decision => factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence),\n occurredAt: decision => decision.completedAt ?? decision.updatedAt,\n title: (decision, item) => item?.title ?? factoryDispatchFailureMetadata(decision.failureCode).label,\n detail: decision => decision.lastError?.slice(0, 512) ?? factoryDispatchFailureMetadata(decision.failureCode).label,\n extra: (decision, { overtaken }) => ({\n failureCode: decision.failureCode,\n canRetry: decisionRetryable(decision, overtaken),\n }),\n matches: (decision, item, search) =>\n item?.title.toLowerCase().includes(search) === true ||\n decision.lastError?.toLowerCase().includes(search) === true ||\n factoryDecisionType(decision).toLowerCase().includes(search),\n};\n\ninterface ResolvedMention {\n mention: WorkItemMentionRow;\n comment: WorkItemCommentRow;\n item: WorkItemRow;\n identity: FactoryAttentionIdentity;\n receipt: FactoryAttentionReceiptRecord | undefined;\n}\n\nexport class MentionAttentionProvider implements AttentionProvider {\n readonly kind = 'mention' as const;\n readonly #workItems: WorkItemsStorage;\n readonly #comments: WorkItemCommentsStorage;\n\n constructor({ workItems, comments }: { workItems: WorkItemsStorage; comments: WorkItemCommentsStorage }) {\n this.#workItems = workItems;\n this.#comments = comments;\n }\n\n #scan(scope: AttentionScope, before?: AttentionStreamPosition) {\n return scanBatches<WorkItemMentionRow>(\n async cursor => {\n const rows = await this.#comments.listMentionsForUser({\n ...receiptScope(scope),\n ...(cursor ? { before: cursor } : {}),\n limit: SCAN_PAGE_SIZE + 1,\n });\n return { rows: rows.slice(0, SCAN_PAGE_SIZE), hasMore: rows.length > SCAN_PAGE_SIZE };\n },\n mention => ({ occurredAt: mention.occurredAt, id: mention.id }),\n before,\n );\n }\n\n /**\n * Mention rows joined to what the inbox needs to show one — dropping the\n * ones it cannot: a deleted comment, or a work item gone from the project.\n */\n async #resolve(scope: AttentionScope, mentions: WorkItemMentionRow[]): Promise<ResolvedMention[]> {\n const [receipts, comments, items] = await Promise.all([\n this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: mentions.map(mention => factoryMentionAttentionIdentity(mention.commentId)),\n }),\n this.#comments.listByIds({\n orgId: scope.orgId,\n ids: [...new Set(mentions.map(mention => mention.commentId))],\n }),\n this.#workItems.listByIds({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n ids: [...new Set(mentions.map(mention => mention.workItemId))],\n }),\n ]);\n const receiptByKey = new Map(\n receipts.map(receipt => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]),\n );\n const commentById = new Map(comments.map(comment => [comment.id, comment]));\n const itemById = new Map(items.map(item => [item.id, item]));\n\n const resolved: ResolvedMention[] = [];\n for (const mention of mentions) {\n const comment = commentById.get(mention.commentId);\n const item = itemById.get(mention.workItemId);\n if (!comment || comment.deletedAt || !item) continue;\n const identity = factoryMentionAttentionIdentity(mention.commentId);\n resolved.push({\n mention,\n comment,\n item,\n identity,\n receipt: receiptByKey.get(factoryAttentionKey(scope.factoryProjectId, identity)),\n });\n }\n return resolved;\n }\n\n /**\n * Derived from the same bounded scan as `page`, never from raw aggregates:\n * the badge must count exactly what the views can show, down to stopping at\n * the same scan budget. Aggregates would count orphan mention rows the list\n * can never display or clear.\n */\n async counts(scope: AttentionScope): Promise<AttentionCounts> {\n let open = 0;\n let unread = 0;\n for await (const batch of this.#scan(scope)) {\n for (const { receipt } of await this.#resolve(scope, batch.rows)) {\n if (receipt?.state !== 'archived') open += 1;\n if (receipt === undefined) unread += 1;\n }\n }\n return { open, unread };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const mentions = await this.#comments.listMentionsForUser({ ...receiptScope(scope), limit: 10 });\n const newest = (await this.#resolve(scope, mentions))[0];\n if (!newest) return null;\n return {\n key: factoryAttentionKey(scope.factoryProjectId, newest.identity),\n at: newest.mention.occurredAt,\n unread: newest.receipt === undefined,\n };\n }\n\n async page(scope: AttentionScope, { view, search, before, limit }: AttentionPageArgs): Promise<AttentionPageResult> {\n return collectPage(this.#scan(scope, before), limit, async mentions => {\n const entries: AttentionEntry[] = [];\n for (const resolved of await this.#resolve(scope, mentions)) {\n if (!matchesView(view, resolved.receipt)) continue;\n if (search && !matchesMentionSearch(resolved, search)) continue;\n entries.push({\n occurredAt: resolved.mention.occurredAt,\n resumeCursor: { occurredAt: resolved.mention.occurredAt, id: resolved.mention.id },\n item: toMentionItem(scope, resolved),\n });\n }\n return entries;\n });\n }\n\n async markAllRead(\n scope: AttentionScope,\n { before, now }: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n return markScanRead(this.#scan(scope, before), async mentions => {\n await this.#workItems.markAttentionReceiptsRead({\n ...receiptScope(scope),\n identities: mentions.map(mention => factoryMentionAttentionIdentity(mention.commentId)),\n now,\n });\n });\n }\n}\n\nfunction matchesMentionSearch({ item, comment }: ResolvedMention, search: string): boolean {\n return (\n item.title.toLowerCase().includes(search) ||\n comment.body.toLowerCase().includes(search) ||\n comment.author.displayName?.toLowerCase().includes(search) === true\n );\n}\n\nfunction toMentionItem(scope: AttentionScope, { mention, comment, item, identity, receipt }: ResolvedMention) {\n const authorName = comment.author.displayName;\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n kind: 'mention' as const,\n commentId: mention.commentId,\n occurrence: 0,\n workItemId: mention.workItemId,\n title: item.title,\n detail: comment.body.slice(0, 512),\n authorId: comment.author.id,\n ...(authorName ? { authorName } : {}),\n occurredAt: mention.occurredAt.toISOString(),\n read: receipt !== undefined,\n archived: receipt?.state === 'archived',\n target: {\n kind: 'work-item' as const,\n workItemId: mention.workItemId,\n board: boardForWorkItem(item),\n commentId: mention.commentId,\n },\n };\n}\n\nexport function receiptScope(scope: AttentionScope): { orgId: string; factoryProjectId: string; userId: string } {\n return { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId, userId: scope.userId };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"attention-providers.js","names":[],"sources":["../../src/routes/attention-providers.ts"],"sourcesContent":["import { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport { decisionRetryable, overtakenDecisionIds } from '../rules/decision-applicability.js';\n/**\n * Per-kind attention providers. Each kind owns its counts, its bounded page\n * scan, and its wire item shape; the route layer k-way merges provider pages\n * on `occurredAt desc` with one resumable cursor per kind.\n */\n\nimport { factoryDispatchFailureMetadata } from '../rules/dispatch-errors.js';\nimport type {\n WorkItemCommentRow,\n WorkItemCommentsStorage,\n WorkItemMentionRow,\n} from '../storage/domains/comments/base.js';\nimport type {\n FactoryAttentionIdentity,\n FactoryAttentionKind,\n FactoryAttentionReceiptRecord,\n FactoryDeferredDecisionRecord,\n FactoryDispatchStatus,\n WorkItemRow,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport {\n factoryAttentionKey,\n factoryDecisionAttentionIdentity,\n factoryMentionAttentionIdentity,\n} from '../storage/domains/work-items/base.js';\n\nexport type FactoryAttentionView = 'open' | 'unread' | 'archived';\n\nexport interface AttentionScope {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n}\n\nexport interface AttentionStreamPosition {\n occurredAt: Date;\n id: string;\n}\n\nexport interface AttentionEntry {\n occurredAt: Date;\n /** Position resuming the provider's stream right after this entry. */\n resumeCursor: AttentionStreamPosition;\n item: Record<string, unknown>;\n}\n\nexport interface AttentionPageArgs {\n view: FactoryAttentionView;\n search?: string;\n before?: AttentionStreamPosition;\n limit: number;\n}\n\nexport interface AttentionPageResult {\n entries: AttentionEntry[];\n /** More rows behind the scan once every returned entry is consumed. */\n hasMore: boolean;\n /** Resume point for a scan-budget stop past the last returned entry. */\n continuation?: AttentionStreamPosition;\n}\n\nexport interface AttentionCounts {\n open: number;\n unread: number;\n}\n\nexport interface AttentionLatest {\n key: string;\n at: Date;\n unread: boolean;\n}\n\nexport interface AttentionProvider {\n kind: FactoryAttentionKind;\n counts(scope: AttentionScope): Promise<AttentionCounts>;\n latest(scope: AttentionScope): Promise<AttentionLatest | null>;\n page(scope: AttentionScope, args: AttentionPageArgs): Promise<AttentionPageResult>;\n markAllRead(\n scope: AttentionScope,\n args: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }>;\n}\n\nexport const SCAN_PAGE_SIZE = 50;\n// Receipt filtering is bounded per request; the response cursor resumes after the last scan.\nconst MAX_RECEIPT_SCAN_PAGES = 4;\n\ninterface ScanBatch<T> {\n rows: T[];\n /** Set only when the page budget stopped a scan that still had rows behind it. */\n continuation?: AttentionStreamPosition;\n}\n\n/**\n * One bounded keyset scan, shared by every provider read. Walks newest-first\n * from `before` until the stream runs dry or the page budget is spent; the\n * final batch of a budget stop carries the position to resume from.\n */\nexport async function* scanBatches<T>(\n fetchBatch: (before?: AttentionStreamPosition) => Promise<{ rows: T[]; hasMore: boolean }>,\n positionOf: (row: T) => AttentionStreamPosition,\n before?: AttentionStreamPosition,\n): AsyncGenerator<ScanBatch<T>> {\n let cursor = before;\n for (let scanned = 1; scanned <= MAX_RECEIPT_SCAN_PAGES; scanned += 1) {\n const { rows, hasMore } = await fetchBatch(cursor);\n const last = rows.at(-1);\n if (!last) return;\n const position = positionOf(last);\n yield { rows, ...(hasMore && scanned === MAX_RECEIPT_SCAN_PAGES ? { continuation: position } : {}) };\n if (!hasMore) return;\n cursor = position;\n }\n}\n\n/** Fills a page from the scan, stopping at `limit` entries or at the scan budget. */\nexport async function collectPage<T>(\n batches: AsyncGenerator<ScanBatch<T>>,\n limit: number,\n toEntries: (rows: T[]) => Promise<AttentionEntry[]>,\n): Promise<AttentionPageResult> {\n const entries: AttentionEntry[] = [];\n for await (const batch of batches) {\n for (const entry of await toEntries(batch.rows)) {\n if (entries.length === limit) return { entries, hasMore: true };\n entries.push(entry);\n }\n if (batch.continuation) return { entries, hasMore: true, continuation: batch.continuation };\n }\n return { entries, hasMore: false };\n}\n\n/** Marks every scanned batch read, reporting where a budget stop left off. */\nexport async function markScanRead<T>(\n batches: AsyncGenerator<ScanBatch<T>>,\n markBatch: (rows: T[]) => Promise<void>,\n): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n for await (const batch of batches) {\n await markBatch(batch.rows);\n if (batch.continuation) return { hasMore: true, continuation: batch.continuation };\n }\n return { hasMore: false };\n}\n\nexport function factoryDecisionType(decision: FactoryDeferredDecisionRecord): string {\n return typeof decision.decision.type === 'string' ? decision.decision.type.slice(0, 64) : 'unknown';\n}\n\nexport function matchesView(view: FactoryAttentionView, receipt: FactoryAttentionReceiptRecord | undefined): boolean {\n if (view === 'archived') return receipt?.state === 'archived';\n if (view === 'unread') return receipt === undefined;\n return receipt?.state !== 'archived';\n}\n\nfunction attentionTarget(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined) {\n if (!item) return { kind: 'rules' as const };\n const role = typeof decision.decision.role === 'string' ? decision.decision.role : undefined;\n const session = role ? item.sessions[role] : undefined;\n if (session) {\n return {\n kind: 'thread' as const,\n sessionId: session.sessionId,\n threadId: session.threadId,\n };\n }\n return {\n kind: 'work-item' as const,\n workItemId: item.id,\n board: boardForWorkItem(item),\n };\n}\n\n/** One deferred-decision status seen as attention: failures and parked runs differ only by this. */\nexport interface DecisionAttentionSpec {\n kind: FactoryAttentionKind;\n status: FactoryDispatchStatus;\n identity(decision: FactoryDeferredDecisionRecord): FactoryAttentionIdentity;\n occurredAt(decision: FactoryDeferredDecisionRecord): Date;\n title(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined): string;\n detail(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined): string;\n extra?(decision: FactoryDeferredDecisionRecord, context: { overtaken: boolean }): Record<string, unknown>;\n matches(decision: FactoryDeferredDecisionRecord, item: WorkItemRow | undefined, search: string): boolean;\n}\n\nexport class DecisionAttentionProvider implements AttentionProvider {\n readonly kind: FactoryAttentionKind;\n readonly #workItems: WorkItemsStorage;\n readonly #spec: DecisionAttentionSpec;\n readonly #boards: BoardRegistry;\n\n constructor(\n { workItems, boards }: { workItems: WorkItemsStorage; boards: BoardRegistry },\n spec: DecisionAttentionSpec,\n ) {\n this.#workItems = workItems;\n this.#boards = boards;\n this.#spec = spec;\n this.kind = spec.kind;\n }\n\n async counts(scope: AttentionScope): Promise<AttentionCounts> {\n const [total, receiptCount, archivedCount] = await Promise.all([\n this.#workItems.countDeferredDecisionsByStatuses({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n statuses: [this.#spec.status],\n }),\n this.#workItems.countAttentionReceipts({ ...receiptScope(scope), kind: this.kind }),\n this.#workItems.countAttentionReceipts({ ...receiptScope(scope), kind: this.kind, state: 'archived' }),\n ]);\n return {\n open: Math.max(0, total - archivedCount),\n unread: Math.max(0, total - receiptCount),\n };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const page = await this.#workItems.listDecisionPageByStatus({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n status: this.#spec.status,\n limit: 1,\n });\n const newest = page.decisions[0];\n if (!newest) return null;\n const identity = this.#spec.identity(newest);\n const receipts = await this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: [identity],\n });\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n at: this.#spec.occurredAt(newest),\n unread: receipts.length === 0,\n };\n }\n\n #scan(scope: AttentionScope, before?: AttentionStreamPosition) {\n return scanBatches<FactoryDeferredDecisionRecord>(\n async cursor => {\n const page = await this.#workItems.listDecisionPageByStatus({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n status: this.#spec.status,\n before: cursor,\n limit: SCAN_PAGE_SIZE,\n });\n return { rows: page.decisions, hasMore: page.hasMore };\n },\n decision => ({ occurredAt: this.#spec.occurredAt(decision), id: decision.id }),\n before,\n );\n }\n\n async page(scope: AttentionScope, { view, search, before, limit }: AttentionPageArgs): Promise<AttentionPageResult> {\n return collectPage(this.#scan(scope, before), limit, async decisions => {\n const [receiptByKey, itemById, overtaken] = await Promise.all([\n this.#receiptsFor(scope, decisions),\n this.#linkedItems(scope, decisions),\n overtakenDecisionIds(this.#workItems, this.#boards, scope, decisions),\n ]);\n const entries: AttentionEntry[] = [];\n for (const decision of decisions) {\n const identity = this.#spec.identity(decision);\n const receipt = receiptByKey.get(factoryAttentionKey(scope.factoryProjectId, identity));\n if (!matchesView(view, receipt)) continue;\n const item = decision.workItemId ? itemById.get(decision.workItemId) : undefined;\n if (search && !this.#spec.matches(decision, item, search)) continue;\n const occurredAt = this.#spec.occurredAt(decision);\n entries.push({\n occurredAt,\n resumeCursor: { occurredAt, id: decision.id },\n item: {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n kind: this.kind,\n decisionId: decision.id,\n occurrence: identity.occurrence,\n workItemId: decision.workItemId,\n title: this.#spec.title(decision, item),\n detail: this.#spec.detail(decision, item),\n decisionType: factoryDecisionType(decision),\n ...this.#spec.extra?.(decision, { overtaken: overtaken.has(decision.id) }),\n occurredAt: occurredAt.toISOString(),\n read: receipt !== undefined,\n archived: receipt?.state === 'archived',\n target: attentionTarget(decision, item),\n },\n });\n }\n return entries;\n });\n }\n\n async #receiptsFor(\n scope: AttentionScope,\n decisions: FactoryDeferredDecisionRecord[],\n ): Promise<Map<string, FactoryAttentionReceiptRecord>> {\n const receipts = await this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: decisions.map(decision => this.#spec.identity(decision)),\n });\n return new Map(receipts.map(receipt => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]));\n }\n\n async #linkedItems(\n scope: AttentionScope,\n decisions: FactoryDeferredDecisionRecord[],\n ): Promise<Map<string, WorkItemRow>> {\n const items = await this.#workItems.listByIds({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n ids: decisions.flatMap(decision => (decision.workItemId ? [decision.workItemId] : [])),\n });\n return new Map(items.map(item => [item.id, item]));\n }\n\n async markAllRead(\n scope: AttentionScope,\n { before, now }: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n return markScanRead(this.#scan(scope, before), async decisions => {\n await this.#workItems.markAttentionReceiptsRead({\n ...receiptScope(scope),\n identities: decisions.map(decision => this.#spec.identity(decision)),\n now,\n });\n });\n }\n}\n\nexport const failedDecisionAttentionSpec: DecisionAttentionSpec = {\n kind: 'automation-failed',\n status: 'failed',\n identity: decision => factoryDecisionAttentionIdentity(decision.id, decision.failureOccurrence),\n occurredAt: decision => decision.completedAt ?? decision.updatedAt,\n title: (decision, item) => item?.title ?? factoryDispatchFailureMetadata(decision.failureCode).label,\n detail: decision => decision.lastError?.slice(0, 512) ?? factoryDispatchFailureMetadata(decision.failureCode).label,\n extra: (decision, { overtaken }) => ({\n failureCode: decision.failureCode,\n canRetry: decisionRetryable(decision, overtaken),\n }),\n matches: (decision, item, search) =>\n item?.title.toLowerCase().includes(search) === true ||\n decision.lastError?.toLowerCase().includes(search) === true ||\n factoryDecisionType(decision).toLowerCase().includes(search),\n};\n\ninterface ResolvedMention {\n mention: WorkItemMentionRow;\n comment: WorkItemCommentRow;\n item: WorkItemRow;\n identity: FactoryAttentionIdentity;\n receipt: FactoryAttentionReceiptRecord | undefined;\n}\n\nexport class MentionAttentionProvider implements AttentionProvider {\n readonly kind = 'mention' as const;\n readonly #workItems: WorkItemsStorage;\n readonly #comments: WorkItemCommentsStorage;\n\n constructor({ workItems, comments }: { workItems: WorkItemsStorage; comments: WorkItemCommentsStorage }) {\n this.#workItems = workItems;\n this.#comments = comments;\n }\n\n #scan(scope: AttentionScope, before?: AttentionStreamPosition) {\n return scanBatches<WorkItemMentionRow>(\n async cursor => {\n const rows = await this.#comments.listMentionsForUser({\n ...receiptScope(scope),\n ...(cursor ? { before: cursor } : {}),\n limit: SCAN_PAGE_SIZE + 1,\n });\n return { rows: rows.slice(0, SCAN_PAGE_SIZE), hasMore: rows.length > SCAN_PAGE_SIZE };\n },\n mention => ({ occurredAt: mention.occurredAt, id: mention.id }),\n before,\n );\n }\n\n /**\n * Mention rows joined to what the inbox needs to show one — dropping the\n * ones it cannot: a deleted comment, or a work item gone from the project.\n */\n async #resolve(scope: AttentionScope, mentions: WorkItemMentionRow[]): Promise<ResolvedMention[]> {\n const [receipts, comments, items] = await Promise.all([\n this.#workItems.listAttentionReceipts({\n ...receiptScope(scope),\n identities: mentions.map(mention => factoryMentionAttentionIdentity(mention.commentId)),\n }),\n this.#comments.listByIds({\n orgId: scope.orgId,\n ids: [...new Set(mentions.map(mention => mention.commentId))],\n }),\n this.#workItems.listByIds({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n ids: [...new Set(mentions.map(mention => mention.workItemId))],\n }),\n ]);\n const receiptByKey = new Map(\n receipts.map(receipt => [factoryAttentionKey(scope.factoryProjectId, receipt), receipt]),\n );\n const commentById = new Map(comments.map(comment => [comment.id, comment]));\n const itemById = new Map(items.map(item => [item.id, item]));\n\n const resolved: ResolvedMention[] = [];\n for (const mention of mentions) {\n const comment = commentById.get(mention.commentId);\n const item = itemById.get(mention.workItemId);\n if (!comment || comment.deletedAt || !item) continue;\n const identity = factoryMentionAttentionIdentity(mention.commentId);\n resolved.push({\n mention,\n comment,\n item,\n identity,\n receipt: receiptByKey.get(factoryAttentionKey(scope.factoryProjectId, identity)),\n });\n }\n return resolved;\n }\n\n /**\n * Derived from the same bounded scan as `page`, never from raw aggregates:\n * the badge must count exactly what the views can show, down to stopping at\n * the same scan budget. Aggregates would count orphan mention rows the list\n * can never display or clear.\n */\n async counts(scope: AttentionScope): Promise<AttentionCounts> {\n let open = 0;\n let unread = 0;\n for await (const batch of this.#scan(scope)) {\n for (const { receipt } of await this.#resolve(scope, batch.rows)) {\n if (receipt?.state !== 'archived') open += 1;\n if (receipt === undefined) unread += 1;\n }\n }\n return { open, unread };\n }\n\n async latest(scope: AttentionScope): Promise<AttentionLatest | null> {\n const mentions = await this.#comments.listMentionsForUser({ ...receiptScope(scope), limit: 10 });\n const newest = (await this.#resolve(scope, mentions))[0];\n if (!newest) return null;\n return {\n key: factoryAttentionKey(scope.factoryProjectId, newest.identity),\n at: newest.mention.occurredAt,\n unread: newest.receipt === undefined,\n };\n }\n\n async page(scope: AttentionScope, { view, search, before, limit }: AttentionPageArgs): Promise<AttentionPageResult> {\n return collectPage(this.#scan(scope, before), limit, async mentions => {\n const entries: AttentionEntry[] = [];\n for (const resolved of await this.#resolve(scope, mentions)) {\n if (!matchesView(view, resolved.receipt)) continue;\n if (search && !matchesMentionSearch(resolved, search)) continue;\n entries.push({\n occurredAt: resolved.mention.occurredAt,\n resumeCursor: { occurredAt: resolved.mention.occurredAt, id: resolved.mention.id },\n item: toMentionItem(scope, resolved),\n });\n }\n return entries;\n });\n }\n\n async markAllRead(\n scope: AttentionScope,\n { before, now }: { before?: AttentionStreamPosition; now: Date },\n ): Promise<{ hasMore: boolean; continuation?: AttentionStreamPosition }> {\n return markScanRead(this.#scan(scope, before), async mentions => {\n await this.#workItems.markAttentionReceiptsRead({\n ...receiptScope(scope),\n identities: mentions.map(mention => factoryMentionAttentionIdentity(mention.commentId)),\n now,\n });\n });\n }\n}\n\nfunction matchesMentionSearch({ item, comment }: ResolvedMention, search: string): boolean {\n return (\n item.title.toLowerCase().includes(search) ||\n comment.body.toLowerCase().includes(search) ||\n comment.author.displayName?.toLowerCase().includes(search) === true\n );\n}\n\nfunction toMentionItem(scope: AttentionScope, { mention, comment, item, identity, receipt }: ResolvedMention) {\n const authorName = comment.author.displayName;\n return {\n key: factoryAttentionKey(scope.factoryProjectId, identity),\n kind: 'mention' as const,\n commentId: mention.commentId,\n occurrence: 0,\n workItemId: mention.workItemId,\n title: item.title,\n detail: comment.body.slice(0, 512),\n authorId: comment.author.id,\n ...(authorName ? { authorName } : {}),\n occurredAt: mention.occurredAt.toISOString(),\n read: receipt !== undefined,\n archived: receipt?.state === 'archived',\n target: {\n kind: 'work-item' as const,\n workItemId: mention.workItemId,\n board: boardForWorkItem(item),\n commentId: mention.commentId,\n },\n };\n}\n\nexport function receiptScope(scope: AttentionScope): { orgId: string; factoryProjectId: string; userId: string } {\n return { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId, userId: scope.userId };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"automation-runs.js","names":["#resolveScope","#resolveProject","#writeAudit"],"sources":["../../src/routes/automation-runs.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` for trusted external orchestrators to enqueue an\n * idempotent, deferred skill dispatch against a Factory work item.\n *\n * The endpoint is a constrained ingress in front of\n * `WorkItemsStorage.commitRuleEvaluation()`: it commits exactly one\n * `invokeSkill` decision, stamps a non-human system actor, and leaves\n * execution (session creation, auto-run/approval policy, retry, restart\n * recovery) to the `FactoryDecisionDispatcher`.\n *\n * Idempotency: the `requestId` becomes the ingress identity, so replaying the\n * same request returns the prior result without inserting a second decision.\n * Tenant-mode callers must administer the organization; local (no-auth)\n * deployments run under the shared `local` storage scope without inventing a\n * tenant user.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { AuditAction } from '../storage/domains/audit/actions.js';\nimport type { AuditTarget } from '../storage/domains/audit/base.js';\nimport type { AuditEmitter, AuditRecorder } from '../storage/domains/audit/domain.js';\nimport { auditRequestContext } from '../storage/domains/audit/domain.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { FACTORY_ROUTE_CONTRACTS } from './contracts.js';\nimport { Route } from './route.js';\nimport type { RouteDependencies } from './route.js';\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\n/** The invokeSkill payload this route commits, matched against a prior replay. */\ninterface AutomationInvokeSkillDecision {\n type: 'invokeSkill';\n idempotencyKey: string;\n role: string;\n skillName: string;\n arguments?: string;\n}\n\n/**\n * Whether a replayed rule-evaluation record describes the SAME operation as the\n * current request. `commitRuleEvaluation` keys idempotency on `(org, project,\n * ingress identity)` alone, so a reused `requestId` replays regardless of the\n * work item or decision payload. We reconstruct the prior `invokeSkill`\n * decision from the stored result and compare the item id, role, skill, and\n * arguments; any divergence is a collision, not a legitimate retry.\n */\nfunction replayMatchesRequest(\n result: { itemId?: string | null; decisions?: unknown[] },\n itemId: string,\n request: AutomationInvokeSkillDecision,\n): boolean {\n if (result.itemId != null && result.itemId !== itemId) return false;\n const decisions = Array.isArray(result.decisions) ? result.decisions : [];\n const prior = decisions.find(\n (candidate): candidate is Record<string, unknown> =>\n typeof candidate === 'object' &&\n candidate !== null &&\n (candidate as Record<string, unknown>).type === 'invokeSkill' &&\n (candidate as Record<string, unknown>).idempotencyKey === request.idempotencyKey,\n );\n if (!prior) return false;\n return (\n prior.role === request.role &&\n prior.skillName === request.skillName &&\n (prior.arguments ?? undefined) === (request.arguments ?? undefined)\n );\n}\n\nexport interface AutomationRunRoutesDeps extends RouteDependencies {\n audit: AuditEmitter & AuditRecorder;\n /** Factory projects domain — validates the `:id` project belongs to the caller's org. */\n projects: FactoryProjectsStorage;\n /** Work-items domain backing `commitRuleEvaluation`. */\n workItems: WorkItemsStorage;\n /** Config version stamped on the committed rule evaluation. */\n configVersion: string;\n}\n\n/** Resolved ingress scope for an automation-run request. */\ninterface AutomationRunScope {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n}\n\nexport class AutomationRunRoutes extends Route<AutomationRunRoutesDeps> {\n /**\n * Resolve the `(orgId, userId)` ingress scope. Tenant mode requires a\n * signed-in organization administrator; local (no-auth) mode runs under the\n * shared `local` scope without inventing a user.\n */\n async #resolveScope(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n const { auth } = this.deps;\n if (!auth.enabled()) {\n return { orgId: 'local', userId: 'local' };\n }\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'The Factory board requires an organization.' },\n 403,\n ),\n };\n }\n if (!(await auth.isOrganizationAdmin(c, tenant.orgId))) {\n return {\n response: c.json(\n { error: 'forbidden', message: 'Organization administrator access is required for automation ingress.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n /** Resolve the ingress scope AND the org-owned project from the `:id` param. */\n async #resolveProject(c: Context): Promise<AutomationRunScope | { response: Response }> {\n const scope = await this.#resolveScope(c);\n if ('response' in scope) return scope;\n\n const parsedPath = FACTORY_ROUTE_CONTRACTS.projectGet.pathSchema.safeParse({ id: c.req.param('id') });\n if (!parsedPath.success) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n const { projects } = this.deps;\n await projects.ensureReady();\n const project = await projects.get({ orgId: scope.orgId, id: parsedPath.data.id });\n if (!project) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n return { ...scope, factoryProjectId: parsedPath.data.id };\n }\n\n /**\n * Persist an audit event under the ingress-resolved scope. This route can run\n * under the synthetic `local` no-auth scope, where the tenant-gated\n * `audit.emit()` derives no org from the request and drops the event; writing\n * through `record()` with the explicit org + system actor keeps traceability\n * in every deployment mode.\n *\n * The `idempotencyKey` (`<action>:<requestId>`) makes the audit write itself\n * idempotent: replaying the same request re-enters this path, but the trail\n * keeps a single event per requestId instead of one duplicate per retry.\n */\n async #writeAudit(\n c: Context,\n scope: AutomationRunScope,\n action: AuditAction,\n requestId: string,\n targets: AuditTarget[],\n metadata: Record<string, unknown>,\n ): Promise<void> {\n await this.deps.audit.record({\n idempotencyKey: `${action}:${requestId}`,\n orgId: scope.orgId,\n actorId: 'factory-external-orchestrator',\n actorType: 'system',\n action,\n targets,\n metadata,\n factoryProjectId: scope.factoryProjectId,\n context: auditRequestContext(c),\n });\n }\n\n routes(): ApiRoute[] {\n const { workItems, configVersion } = this.deps;\n const contract = FACTORY_ROUTE_CONTRACTS.workItemAutomationRun;\n\n return [\n registerApiRoute(contract.path, {\n method: contract.method,\n handler: async c => {\n const context = loose(c);\n const resolved = await this.#resolveProject(context);\n if ('response' in resolved) return resolved.response;\n\n const parsedPath = contract.pathSchema.safeParse({\n id: c.req.param('id'),\n workItemId: c.req.param('workItemId'),\n });\n if (!parsedPath.success) return c.json({ error: 'Work item not found' }, 404);\n\n const body = await c.req.json().catch(() => undefined);\n if (body === undefined) return c.json({ error: 'Invalid JSON body' }, 400);\n const parsed = contract.bodySchema.safeParse(body);\n if (!parsed.success) return c.json({ error: 'invalid_automation_run_request' }, 400);\n const request = parsed.data as {\n requestId: string;\n expectedRevision: number;\n role: string;\n skillName: string;\n arguments?: string;\n };\n\n const item = await workItems.getForProject(\n resolved.orgId,\n resolved.factoryProjectId,\n (parsedPath.data as { workItemId: string }).workItemId,\n );\n if (!item) return c.json({ error: 'Work item not found' }, 404);\n\n const idempotencyKey = `external-orchestrator:${request.requestId}`;\n const decision = {\n type: 'invokeSkill' as const,\n idempotencyKey,\n role: request.role,\n skillName: request.skillName,\n ...(request.arguments !== undefined ? { arguments: request.arguments } : {}),\n };\n\n const now = new Date();\n const commit = await workItems.commitRuleEvaluation({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n workItemId: item.id,\n ingress: { identity: idempotencyKey, triggerType: 'external-orchestrator.invoke-skill' },\n configVersion,\n expectedRevision: request.expectedRevision,\n actor: { type: 'system', id: 'factory-external-orchestrator' },\n outcome: { status: 'accepted' },\n decisions: [decision],\n causalChain: [],\n now,\n });\n\n if (commit.status === 'missing') return c.json({ error: 'Work item not found' }, 404);\n\n // A stale commit persists nothing, so the caller can retry the same\n // requestId once it has re-read the work item's revision.\n const result = (commit.status === 'stale' ? { status: 'rejected', code: 'stale' } : commit.result) as {\n status?: string;\n code?: string;\n itemId?: string | null;\n decisions?: unknown[];\n };\n const resultStatus = result.status ?? 'accepted';\n const code = typeof result.code === 'string' ? result.code : undefined;\n const targets = [{ type: 'work_item' as const, id: item.id, name: item.title }];\n\n const auditMetadata = {\n requestId: request.requestId,\n role: request.role,\n skillName: request.skillName,\n commitStatus: commit.status,\n resultStatus,\n ...(code ? { code } : {}),\n };\n\n // A replay only means \"this request was already committed\" — it must\n // be the SAME operation. If the caller reused a requestId against a\n // different work item, role, skill, or arguments, returning\n // `replayed` would report durable success for work that was never\n // enqueued. Reject the collision instead of silently swallowing it.\n if (commit.status === 'replayed' && !replayMatchesRequest(result, item.id, decision)) {\n await this.#writeAudit(context, resolved, 'factory.run.rejected', request.requestId, targets, {\n ...auditMetadata,\n resultStatus: 'rejected',\n code: 'request_id_conflict',\n });\n return c.json(\n {\n status: 'rejected',\n code: 'request_id_conflict',\n requestId: request.requestId,\n },\n 409,\n );\n }\n\n if (resultStatus === 'accepted') {\n await this.#writeAudit(context, resolved, 'factory.run.queued', request.requestId, targets, auditMetadata);\n return c.json(\n { status: commit.status === 'replayed' ? 'replayed' : 'committed', requestId: request.requestId },\n commit.status === 'replayed' ? 200 : 202,\n );\n }\n\n await this.#writeAudit(context, resolved, 'factory.run.rejected', request.requestId, targets, auditMetadata);\n return c.json(\n { status: 'rejected', ...(code ? { code } : {}), requestId: request.requestId },\n code === 'stale' ? 409 : 422,\n );\n },\n }),\n ];\n }\n}\n\nexport function buildAutomationRunRoutes(deps: AutomationRunRoutesDeps): ApiRoute[] {\n return new AutomationRunRoutes(deps).routes();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"automation-runs.js","names":[],"sources":["../../src/routes/automation-runs.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` for trusted external orchestrators to enqueue an\n * idempotent, deferred skill dispatch against a Factory work item.\n *\n * The endpoint is a constrained ingress in front of\n * `WorkItemsStorage.commitRuleEvaluation()`: it commits exactly one\n * `invokeSkill` decision, stamps a non-human system actor, and leaves\n * execution (session creation, auto-run/approval policy, retry, restart\n * recovery) to the `FactoryDecisionDispatcher`.\n *\n * Idempotency: the `requestId` becomes the ingress identity, so replaying the\n * same request returns the prior result without inserting a second decision.\n * Tenant-mode callers must administer the organization; local (no-auth)\n * deployments run under the shared `local` storage scope without inventing a\n * tenant user.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { AuditAction } from '../storage/domains/audit/actions.js';\nimport type { AuditTarget } from '../storage/domains/audit/base.js';\nimport type { AuditEmitter, AuditRecorder } from '../storage/domains/audit/domain.js';\nimport { auditRequestContext } from '../storage/domains/audit/domain.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { FACTORY_ROUTE_CONTRACTS } from './contracts.js';\nimport { Route } from './route.js';\nimport type { RouteDependencies } from './route.js';\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\n/** The invokeSkill payload this route commits, matched against a prior replay. */\ninterface AutomationInvokeSkillDecision {\n type: 'invokeSkill';\n idempotencyKey: string;\n role: string;\n skillName: string;\n arguments?: string;\n}\n\n/**\n * Whether a replayed rule-evaluation record describes the SAME operation as the\n * current request. `commitRuleEvaluation` keys idempotency on `(org, project,\n * ingress identity)` alone, so a reused `requestId` replays regardless of the\n * work item or decision payload. We reconstruct the prior `invokeSkill`\n * decision from the stored result and compare the item id, role, skill, and\n * arguments; any divergence is a collision, not a legitimate retry.\n */\nfunction replayMatchesRequest(\n result: { itemId?: string | null; decisions?: unknown[] },\n itemId: string,\n request: AutomationInvokeSkillDecision,\n): boolean {\n if (result.itemId != null && result.itemId !== itemId) return false;\n const decisions = Array.isArray(result.decisions) ? result.decisions : [];\n const prior = decisions.find(\n (candidate): candidate is Record<string, unknown> =>\n typeof candidate === 'object' &&\n candidate !== null &&\n (candidate as Record<string, unknown>).type === 'invokeSkill' &&\n (candidate as Record<string, unknown>).idempotencyKey === request.idempotencyKey,\n );\n if (!prior) return false;\n return (\n prior.role === request.role &&\n prior.skillName === request.skillName &&\n (prior.arguments ?? undefined) === (request.arguments ?? undefined)\n );\n}\n\nexport interface AutomationRunRoutesDeps extends RouteDependencies {\n audit: AuditEmitter & AuditRecorder;\n /** Factory projects domain — validates the `:id` project belongs to the caller's org. */\n projects: FactoryProjectsStorage;\n /** Work-items domain backing `commitRuleEvaluation`. */\n workItems: WorkItemsStorage;\n /** Config version stamped on the committed rule evaluation. */\n configVersion: string;\n}\n\n/** Resolved ingress scope for an automation-run request. */\ninterface AutomationRunScope {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n}\n\nexport class AutomationRunRoutes extends Route<AutomationRunRoutesDeps> {\n /**\n * Resolve the `(orgId, userId)` ingress scope. Tenant mode requires a\n * signed-in organization administrator; local (no-auth) mode runs under the\n * shared `local` scope without inventing a user.\n */\n async #resolveScope(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n const { auth } = this.deps;\n if (!auth.enabled()) {\n return { orgId: 'local', userId: 'local' };\n }\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'The Factory board requires an organization.' },\n 403,\n ),\n };\n }\n if (!(await auth.isOrganizationAdmin(c, tenant.orgId))) {\n return {\n response: c.json(\n { error: 'forbidden', message: 'Organization administrator access is required for automation ingress.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n /** Resolve the ingress scope AND the org-owned project from the `:id` param. */\n async #resolveProject(c: Context): Promise<AutomationRunScope | { response: Response }> {\n const scope = await this.#resolveScope(c);\n if ('response' in scope) return scope;\n\n const parsedPath = FACTORY_ROUTE_CONTRACTS.projectGet.pathSchema.safeParse({ id: c.req.param('id') });\n if (!parsedPath.success) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n const { projects } = this.deps;\n await projects.ensureReady();\n const project = await projects.get({ orgId: scope.orgId, id: parsedPath.data.id });\n if (!project) {\n return { response: c.json({ error: 'Project not found' }, 404) };\n }\n return { ...scope, factoryProjectId: parsedPath.data.id };\n }\n\n /**\n * Persist an audit event under the ingress-resolved scope. This route can run\n * under the synthetic `local` no-auth scope, where the tenant-gated\n * `audit.emit()` derives no org from the request and drops the event; writing\n * through `record()` with the explicit org + system actor keeps traceability\n * in every deployment mode.\n *\n * The `idempotencyKey` (`<action>:<requestId>`) makes the audit write itself\n * idempotent: replaying the same request re-enters this path, but the trail\n * keeps a single event per requestId instead of one duplicate per retry.\n */\n async #writeAudit(\n c: Context,\n scope: AutomationRunScope,\n action: AuditAction,\n requestId: string,\n targets: AuditTarget[],\n metadata: Record<string, unknown>,\n ): Promise<void> {\n await this.deps.audit.record({\n idempotencyKey: `${action}:${requestId}`,\n orgId: scope.orgId,\n actorId: 'factory-external-orchestrator',\n actorType: 'system',\n action,\n targets,\n metadata,\n factoryProjectId: scope.factoryProjectId,\n context: auditRequestContext(c),\n });\n }\n\n routes(): ApiRoute[] {\n const { workItems, configVersion } = this.deps;\n const contract = FACTORY_ROUTE_CONTRACTS.workItemAutomationRun;\n\n return [\n registerApiRoute(contract.path, {\n method: contract.method,\n handler: async c => {\n const context = loose(c);\n const resolved = await this.#resolveProject(context);\n if ('response' in resolved) return resolved.response;\n\n const parsedPath = contract.pathSchema.safeParse({\n id: c.req.param('id'),\n workItemId: c.req.param('workItemId'),\n });\n if (!parsedPath.success) return c.json({ error: 'Work item not found' }, 404);\n\n const body = await c.req.json().catch(() => undefined);\n if (body === undefined) return c.json({ error: 'Invalid JSON body' }, 400);\n const parsed = contract.bodySchema.safeParse(body);\n if (!parsed.success) return c.json({ error: 'invalid_automation_run_request' }, 400);\n const request = parsed.data as {\n requestId: string;\n expectedRevision: number;\n role: string;\n skillName: string;\n arguments?: string;\n };\n\n const item = await workItems.getForProject(\n resolved.orgId,\n resolved.factoryProjectId,\n (parsedPath.data as { workItemId: string }).workItemId,\n );\n if (!item) return c.json({ error: 'Work item not found' }, 404);\n\n const idempotencyKey = `external-orchestrator:${request.requestId}`;\n const decision = {\n type: 'invokeSkill' as const,\n idempotencyKey,\n role: request.role,\n skillName: request.skillName,\n ...(request.arguments !== undefined ? { arguments: request.arguments } : {}),\n };\n\n const now = new Date();\n const commit = await workItems.commitRuleEvaluation({\n orgId: resolved.orgId,\n factoryProjectId: resolved.factoryProjectId,\n workItemId: item.id,\n ingress: { identity: idempotencyKey, triggerType: 'external-orchestrator.invoke-skill' },\n configVersion,\n expectedRevision: request.expectedRevision,\n actor: { type: 'system', id: 'factory-external-orchestrator' },\n outcome: { status: 'accepted' },\n decisions: [decision],\n causalChain: [],\n now,\n });\n\n if (commit.status === 'missing') return c.json({ error: 'Work item not found' }, 404);\n\n // A stale commit persists nothing, so the caller can retry the same\n // requestId once it has re-read the work item's revision.\n const result = (commit.status === 'stale' ? { status: 'rejected', code: 'stale' } : commit.result) as {\n status?: string;\n code?: string;\n itemId?: string | null;\n decisions?: unknown[];\n };\n const resultStatus = result.status ?? 'accepted';\n const code = typeof result.code === 'string' ? result.code : undefined;\n const targets = [{ type: 'work_item' as const, id: item.id, name: item.title }];\n\n const auditMetadata = {\n requestId: request.requestId,\n role: request.role,\n skillName: request.skillName,\n commitStatus: commit.status,\n resultStatus,\n ...(code ? { code } : {}),\n };\n\n // A replay only means \"this request was already committed\" — it must\n // be the SAME operation. If the caller reused a requestId against a\n // different work item, role, skill, or arguments, returning\n // `replayed` would report durable success for work that was never\n // enqueued. Reject the collision instead of silently swallowing it.\n if (commit.status === 'replayed' && !replayMatchesRequest(result, item.id, decision)) {\n await this.#writeAudit(context, resolved, 'factory.run.rejected', request.requestId, targets, {\n ...auditMetadata,\n resultStatus: 'rejected',\n code: 'request_id_conflict',\n });\n return c.json(\n {\n status: 'rejected',\n code: 'request_id_conflict',\n requestId: request.requestId,\n },\n 409,\n );\n }\n\n if (resultStatus === 'accepted') {\n await this.#writeAudit(context, resolved, 'factory.run.queued', request.requestId, targets, auditMetadata);\n return c.json(\n { status: commit.status === 'replayed' ? 'replayed' : 'committed', requestId: request.requestId },\n commit.status === 'replayed' ? 200 : 202,\n );\n }\n\n await this.#writeAudit(context, resolved, 'factory.run.rejected', request.requestId, targets, auditMetadata);\n return c.json(\n { status: 'rejected', ...(code ? { code } : {}), requestId: request.requestId },\n code === 'stale' ? 409 : 422,\n );\n },\n }),\n ];\n }\n}\n\nexport function buildAutomationRunRoutes(deps: AutomationRunRoutesDeps): ApiRoute[] {\n return new AutomationRunRoutes(deps).routes();\n}\n"],"mappings":"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"}
@@ -187,7 +187,7 @@ const sessionSchema = z.object({
187
187
  const sessionsSchema = z.record(z.string().min(1).max(64), sessionSchema);
188
188
  const metadataSchema = z.union([z.record(z.string(), z.unknown()), z.null()]).refine((value) => {
189
189
  try {
190
- return JSON.stringify(value).length <= 16 * 1024;
190
+ return JSON.stringify(value).length <= 16384;
191
191
  } catch {
192
192
  return false;
193
193
  }
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.js","names":[],"sources":["../../src/routes/contracts.ts"],"sourcesContent":["import { THINKING_LEVEL_VALUES } from '@mastra/code-sdk/thinking';\nimport type { ThinkingLevelSetting } from '@mastra/code-sdk/thinking';\nimport { z } from 'zod';\n\nimport { FACTORY_ROLE_STAGES } from '../rules/types.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from '../rules/validation.js';\nimport { FACTORY_MODEL_SCOPES } from '../storage/domains/projects/base.js';\n\nconst FACTORY_ROLE_VALUES = Object.keys(FACTORY_ROLE_STAGES) as [string, ...string[]];\nconst THINKING_LEVELS = THINKING_LEVEL_VALUES as [ThinkingLevelSetting, ...ThinkingLevelSetting[]];\n\nexport type FactoryRouteContract = {\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE';\n path: string;\n description: string;\n pathSchema?: z.ZodType;\n querySchema?: z.ZodType;\n bodySchema?: z.ZodType;\n responseSchema: z.ZodType;\n};\n\nexport const UUID_PATTERN = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$';\nconst uuidSchema = z.string().regex(new RegExp(UUID_PATTERN));\nconst entitySchema = z.record(z.string(), z.unknown());\nconst nonEmptyTrimmed = (max: number) => z.string().trim().min(1).max(max);\nconst rawBoundedTrimmed = (max: number) =>\n z\n .string()\n .max(max)\n .transform(value => value.trim())\n .pipe(z.string().min(1));\nconst trimmedUuidSchema = z.string().trim().regex(new RegExp(UUID_PATTERN));\nconst nullableTrimmed = (max: number) =>\n z.union([\n z\n .string()\n .trim()\n .max(max)\n .transform(value => value || null),\n z.null(),\n ]);\nconst projectPathSchema = z.object({ id: uuidSchema });\nconst projectDecisionPathSchema = z.object({ id: uuidSchema, decisionId: uuidSchema });\nconst workItemPathSchema = z.object({ id: uuidSchema });\nconst transitionPathSchema = z.object({ id: uuidSchema, workItemId: uuidSchema });\n\nconst projectSchema = entitySchema;\nconst projectResponseSchema = z.object({ project: projectSchema });\nconst workItemResponseSchema = z.object({ workItem: entitySchema });\nconst decisionResponseSchema = z.object({ decision: entitySchema });\n\nexport const createProjectBodySchema = z.object({\n name: nonEmptyTrimmed(200),\n description: nullableTrimmed(2_000).optional().default(null),\n modelScope: z.enum(FACTORY_MODEL_SCOPES).optional(),\n});\n\nexport const updateProjectBodySchema = z\n .object({\n name: nonEmptyTrimmed(200).optional(),\n description: nullableTrimmed(2_000).optional(),\n defaultModelId: nullableTrimmed(200).optional(),\n modelScope: z.enum(FACTORY_MODEL_SCOPES).optional(),\n defaultThinkingLevel: z.enum(THINKING_LEVELS).optional(),\n autoRunEnabled: z.boolean().optional(),\n autoApprovePlans: z.boolean().optional(),\n reviewTestChecksEnabled: z.boolean().optional(),\n selfReviewEnabled: z.boolean().optional(),\n })\n .refine(input => Object.keys(input).length > 0, { message: 'At least one project field is required' });\n\nconst environmentRepositorySchema = z.object({\n projectRepositoryId: z.string(),\n connectionId: z.string(),\n repositoryId: z.string(),\n // Null when the repository row behind the link is missing.\n slug: z.string().nullable(),\n defaultBranch: z.string().nullable(),\n position: z.number().int(),\n inEnvironment: z.boolean(),\n setupCommand: z.string().nullable(),\n teardownCommand: z.string().nullable(),\n lastBuildStatus: z.enum(['unbuilt', 'configured', 'failed']),\n lastBuildError: z.string().nullable(),\n lastBuiltAt: z.string().nullable(),\n});\n\n/** The configured FactorySandbox as factory describes it: provider id, JSON Schema of its settings, capabilities. */\nexport const environmentSandboxSchema = z.object({\n provider: z.string(),\n settingsSchema: z.record(z.string(), z.unknown()),\n capabilities: z.object({\n template: z.boolean(),\n builds: z.object({ available: z.boolean(), history: z.boolean() }),\n }),\n});\n\n/** When the environment builds on its own; present only when the sandbox can build. */\nexport const environmentBuildTriggersSchema = z.object({\n schedule: z.object({\n enabled: z.boolean(),\n cron: z.string().nullable(),\n timezone: z.string().nullable(),\n /** False when the host's storage has no schedules domain; the cron trigger cannot be enabled then. */\n scheduleAvailable: z.boolean(),\n }),\n push: z.object({ enabled: z.boolean(), debounceMinutes: z.number().int() }),\n});\n\n/** The last build factory asked the provider for, from the project row only; status is read live. */\nexport const environmentLastBuildSchema = z.object({ buildId: z.string(), attemptedAt: z.string().nullable() });\n\nexport const projectEnvironmentResponseSchema = z.object({\n environment: z.object({\n sandbox: environmentSandboxSchema,\n /** The stored settings document; only keys the user set. */\n settings: z.record(z.string(), z.unknown()),\n sandboxWorkingDirectory: z.string().nullable(),\n workspaceSetupCommand: z.string().nullable(),\n activeTemplateId: z.string().nullable(),\n activeTemplateHeads: z.record(z.string(), z.string()).nullable(),\n repositories: z.array(environmentRepositorySchema),\n buildTriggers: environmentBuildTriggersSchema.optional(),\n build: environmentLastBuildSchema.nullable().optional(),\n }),\n});\n\nconst environmentBuildStatusSchema = z.enum(['pending', 'building', 'ready', 'failed', 'unknown']);\n\nexport const environmentBuildStartResponseSchema = z.object({\n outcome: z.enum(['started', 'skipped', 'unavailable', 'failed']),\n buildId: z.string().optional(),\n templateId: z.string().optional(),\n reason: z.string().optional(),\n});\n\nexport const environmentBuildSchema = z.object({\n buildId: z.string(),\n status: environmentBuildStatusSchema,\n templateId: z.string().optional(),\n startedAt: z.string().optional(),\n finishedAt: z.string().optional(),\n error: z.string().optional(),\n logs: z.array(z.string()).optional(),\n});\n\nexport const environmentBuildsResponseSchema = z.object({ builds: z.array(environmentBuildSchema) });\nexport const environmentBuildResponseSchema = z.object({ build: environmentBuildSchema });\n\nconst buildPathSchema = projectPathSchema.extend({ buildId: z.string().min(1) });\n\nconst environmentRepositoryPatchSchema = z.object({\n projectRepositoryId: uuidSchema,\n position: z.number().int().min(1).optional(),\n inEnvironment: z.boolean().optional(),\n setupCommand: nullableTrimmed(2_000).optional(),\n teardownCommand: nullableTrimmed(2_000).optional(),\n});\n\nexport const updateProjectEnvironmentBodySchema = z\n .object({\n sandboxWorkingDirectory: nullableTrimmed(1_000)\n .refine(value => value === null || value.startsWith('/'), {\n message: 'sandboxWorkingDirectory must be absolute',\n })\n .optional(),\n /** Partial settings merged onto the stored document; null removes a key. Validated by the FactorySandbox. */\n settings: z.record(z.string(), z.unknown().nullable()).optional(),\n workspaceSetupCommand: nullableTrimmed(2_000).optional(),\n /** Build triggers; only accepted when the sandbox can build. */\n buildTriggers: z\n .object({\n schedule: z\n .object({\n enabled: z.boolean(),\n cron: z.string().trim().min(1).max(100).optional(),\n timezone: z.string().trim().min(1).max(100).optional(),\n })\n .optional(),\n push: z\n .object({\n enabled: z.boolean().optional(),\n debounceMinutes: z.number().int().min(0).max(1_440).optional(),\n })\n .optional(),\n })\n .optional(),\n repositories: z\n .array(environmentRepositoryPatchSchema)\n .max(100)\n .superRefine((entries, ctx) => {\n const ids = new Set(entries.map(entry => entry.projectRepositoryId));\n if (ids.size !== entries.length) {\n ctx.addIssue({ code: 'custom', message: 'Each repository may be listed once' });\n }\n const positions = entries.flatMap(entry => (entry.position === undefined ? [] : [entry.position]));\n if (positions.length === 0) return;\n const expected = Array.from({ length: entries.length }, (_, index) => index + 1);\n if (\n positions.length !== entries.length ||\n [...positions].sort((a, b) => a - b).some((p, i) => p !== expected[i])\n ) {\n ctx.addIssue({\n code: 'custom',\n message: 'Positions must be a permutation of 1..n over the listed repositories',\n });\n }\n })\n .optional(),\n })\n .refine(input => Object.keys(input).length > 0, { message: 'At least one environment field is required' });\n\nconst stagesSchema = z\n .array(\n z\n .string()\n .max(64)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i),\n )\n .min(1)\n .max(16)\n .refine(stages => new Set(stages).size === stages.length, { message: 'Stages must be unique' });\n\nconst externalSourceSchema = z\n .object({\n integrationId: z.string().min(1).max(128),\n type: z.string().min(1).max(128),\n externalId: z.string().min(1).max(512),\n url: z.string().max(2_048).optional(),\n })\n .nullable();\n\nconst sessionSchema = z.object({\n sessionId: z.string().min(1).max(512),\n branch: z.string().min(1).max(512),\n threadId: z.string().min(1).max(512),\n});\n\nconst sessionsSchema = z.record(z.string().min(1).max(64), sessionSchema);\nconst metadataSchema = z\n .union([z.record(z.string(), z.unknown()), z.null()])\n .refine(value => {\n try {\n return JSON.stringify(value).length <= 16 * 1024;\n } catch {\n return false;\n }\n }, 'Metadata exceeds 16 KiB')\n .transform(value => {\n if (value === null) return null;\n const {\n factoryRuleMaterializationKey: _materialization,\n factoryPullRequestReconciliation: _reconciliation,\n externalSourceMissingAt: _externalSourceMissingAt,\n ...metadata\n } = value;\n return metadata;\n });\n\nexport const createWorkItemBodySchema = z.object({\n title: rawBoundedTrimmed(500),\n board: z.string().min(1).max(128).optional(),\n externalSource: externalSourceSchema.optional(),\n parentWorkItemId: uuidSchema.nullable().optional(),\n stages: stagesSchema.optional(),\n sessions: sessionsSchema.optional(),\n metadata: metadataSchema.optional(),\n});\n\nexport const updateWorkItemBodySchema = z\n .object({\n title: rawBoundedTrimmed(500).optional(),\n board: z.string().min(1).max(128).optional(),\n parentWorkItemId: uuidSchema.nullable().optional(),\n stages: stagesSchema.optional(),\n sessions: sessionsSchema.optional(),\n metadata: metadataSchema.optional(),\n plansPreapproved: z.literal(true).optional(),\n })\n .refine(input => Object.keys(input).length > 0, { message: 'At least one work-item field is required' });\n\nexport const transitionBodySchema = z\n .object({\n board: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n expectedRevision: z.number().int().min(1),\n requestId: trimmedUuidSchema,\n cause: nonEmptyTrimmed(256),\n reenter: z.unknown().optional(),\n })\n .transform(input => ({\n board: input.board,\n stage: input.stage,\n expectedRevision: input.expectedRevision,\n ingress: { type: 'human' as const, identity: input.requestId },\n cause: input.cause,\n ...(input.reenter === true ? { reenter: true } : {}),\n }));\n\nexport const startWorkItemBodySchema = z.object({\n sessionId: trimmedUuidSchema,\n threadTitle: nonEmptyTrimmed(512),\n kickoffKey: trimmedUuidSchema,\n workItem: z.object({\n id: trimmedUuidSchema,\n role: nonEmptyTrimmed(32),\n input: createWorkItemBodySchema,\n }),\n});\n\nexport const automationRunStartBodySchema = z\n .object({\n requestId: trimmedUuidSchema,\n expectedRevision: z.number().int().min(1),\n role: z.enum(FACTORY_ROLE_VALUES),\n skillName: nonEmptyTrimmed(128),\n arguments: nonEmptyTrimmed(4_096).optional(),\n })\n .strict();\n\nexport const metricsQuerySchema = z.object({\n from: z.string().optional(),\n to: z.string().optional(),\n});\n\nconst decisionStatusSchema = z.enum([\n 'pending',\n 'proposed',\n 'dismissed',\n 'superseded',\n 'leased',\n 'retry',\n 'succeeded',\n 'failed',\n]);\nconst boundedLimitSchema = (defaultValue: number, max: number) =>\n z\n .string()\n .optional()\n .transform(value => {\n const parsed = value ? Number.parseInt(value, 10) : defaultValue;\n return Number.isFinite(parsed) ? Math.max(1, Math.min(max, parsed)) : defaultValue;\n });\nconst decisionCursorSchema = z\n .string()\n .optional()\n .transform((value, context) => {\n if (!value) return undefined;\n try {\n const decoded = JSON.parse(Buffer.from(value, 'base64url').toString('utf8')) as unknown;\n if (\n !Array.isArray(decoded) ||\n decoded.length !== 2 ||\n typeof decoded[0] !== 'string' ||\n typeof decoded[1] !== 'string'\n ) {\n throw new Error('invalid cursor');\n }\n const createdAt = new Date(decoded[0]);\n if (Number.isNaN(createdAt.getTime()) || !new RegExp(UUID_PATTERN).test(decoded[1])) {\n throw new Error('invalid cursor');\n }\n return { createdAt, id: decoded[1] };\n } catch {\n context.addIssue({ code: 'custom', message: 'Invalid cursor' });\n return z.NEVER;\n }\n });\n\nexport const decisionQuerySchema = z.object({\n statuses: z\n .string()\n .optional()\n .transform(value => {\n if (!value) return undefined;\n const statuses = [...new Set(value.split(',').map(status => status.trim()))].filter(\n status => decisionStatusSchema.safeParse(status).success,\n ) as Array<z.infer<typeof decisionStatusSchema>>;\n return statuses.length > 0 ? statuses : undefined;\n }),\n before: decisionCursorSchema,\n limit: boundedLimitSchema(25, 50),\n});\n\nconst attentionKindSchema = z.enum([\n 'automation-failed',\n 'automation-proposed',\n 'mention',\n 'activity',\n 'agent-waiting',\n]);\ntype AttentionKind = z.infer<typeof attentionKindSchema>;\ntype AttentionStreamPosition = { occurredAt: Date; id: string };\ntype AttentionCursorMap = Map<AttentionKind, AttentionStreamPosition | undefined>;\n\nfunction parseAttentionStreamPosition(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()) || !new RegExp(UUID_PATTERN).test(value[1])) return undefined;\n return { occurredAt, id: value[1] };\n}\n\nconst attentionCursorSchema = z\n .string()\n .optional()\n .transform((value, context) => {\n if (!value) return undefined;\n try {\n const decoded: unknown = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));\n if (Array.isArray(decoded)) {\n const legacy = parseAttentionStreamPosition(decoded);\n if (!legacy) throw new Error('invalid cursor');\n return new Map<AttentionKind, AttentionStreamPosition | undefined>([['automation-failed', legacy]]);\n }\n if (!decoded || typeof decoded !== 'object') throw new Error('invalid cursor');\n const cursors: AttentionCursorMap = new Map();\n for (const [kind, positionValue] of Object.entries(decoded)) {\n const parsedKind = attentionKindSchema.safeParse(kind);\n if (!parsedKind.success) throw new Error('invalid cursor');\n if (positionValue === null) {\n cursors.set(parsedKind.data, undefined);\n continue;\n }\n const position = parseAttentionStreamPosition(positionValue);\n if (!position) throw new Error('invalid cursor');\n cursors.set(parsedKind.data, position);\n }\n if (cursors.size === 0) throw new Error('invalid cursor');\n return cursors;\n } catch {\n context.addIssue({ code: 'custom', message: 'Invalid cursor' });\n return z.NEVER;\n }\n });\n\nexport const attentionQuerySchema = z.object({\n view: z.enum(['open', 'unread', 'archived']).optional().default('open'),\n kind: z.array(attentionKindSchema).optional(),\n before: attentionCursorSchema,\n limit: boundedLimitSchema(25, 50),\n search: z\n .string()\n .optional()\n .transform(value => value?.trim().toLowerCase().slice(0, 200) || undefined),\n});\n\nconst attentionActionPathSchema = z\n .object({\n id: uuidSchema,\n kind: attentionKindSchema,\n sourceId: z.string().min(1).max(256),\n occurrence: z\n .string()\n .regex(/^(0|[1-9]\\d*)$/)\n .transform((value, context) => {\n const occurrence = Number(value);\n if (Number.isSafeInteger(occurrence)) return occurrence;\n context.addIssue({ code: 'custom', message: 'Invalid occurrence' });\n return z.NEVER;\n }),\n })\n .refine(input => new RegExp(UUID_PATTERN).test(input.sourceId), {\n message: 'Invalid attention source id',\n path: ['sourceId'],\n });\n\nexport const FACTORY_ROUTE_CONTRACTS = {\n projectList: {\n method: 'GET',\n path: '/web/factory/projects',\n description: 'List Factory projects for the current organization',\n responseSchema: z.object({ projects: z.array(projectSchema) }),\n },\n projectCreate: {\n method: 'POST',\n path: '/web/factory/projects',\n description: 'Create a Factory project',\n bodySchema: createProjectBodySchema,\n responseSchema: projectResponseSchema,\n },\n projectGet: {\n method: 'GET',\n path: '/web/factory/projects/:id',\n description: 'Get a Factory project',\n pathSchema: projectPathSchema,\n responseSchema: projectResponseSchema,\n },\n projectUpdate: {\n method: 'PATCH',\n path: '/web/factory/projects/:id',\n description: 'Update a Factory project',\n pathSchema: projectPathSchema,\n bodySchema: updateProjectBodySchema,\n responseSchema: projectResponseSchema,\n },\n projectEnvironmentGet: {\n method: 'GET',\n path: '/web/factory/projects/:id/environment',\n description: 'Get the sandbox environment of a Factory project: provider settings and its repositories in order',\n pathSchema: projectPathSchema,\n responseSchema: projectEnvironmentResponseSchema,\n },\n projectEnvironmentUpdate: {\n method: 'PATCH',\n path: '/web/factory/projects/:id/environment',\n description: 'Update the sandbox environment of a Factory project: provider settings, repository order and setup',\n pathSchema: projectPathSchema,\n bodySchema: updateProjectEnvironmentBodySchema,\n responseSchema: projectEnvironmentResponseSchema,\n },\n projectEnvironmentBuild: {\n method: 'POST',\n path: '/web/factory/projects/:id/environment/build',\n description: 'Build the sandbox environment template of a Factory project now',\n pathSchema: projectPathSchema,\n responseSchema: environmentBuildStartResponseSchema,\n },\n projectEnvironmentBuilds: {\n method: 'GET',\n path: '/web/factory/projects/:id/environment/builds',\n description:\n 'List the environment template builds of a Factory project, newest first, when the provider keeps history',\n pathSchema: projectPathSchema,\n responseSchema: environmentBuildsResponseSchema,\n },\n projectEnvironmentBuildGet: {\n method: 'GET',\n path: '/web/factory/projects/:id/environment/builds/:buildId',\n description: 'Get the live status of one environment template build of a Factory project',\n pathSchema: buildPathSchema,\n responseSchema: environmentBuildResponseSchema,\n },\n projectApplyDefaultModel: {\n method: 'POST',\n path: '/web/factory/projects/:id/apply-default-model',\n description: 'Apply the Factory default model to running work and review threads',\n pathSchema: projectPathSchema,\n responseSchema: z.object({\n modelId: z.string(),\n applied: z.array(z.string()),\n skipped: z.array(\n z.object({\n threadId: z.string(),\n reason: z.enum([\n 'not-running',\n 'work-item-missing',\n 'stage-inactive',\n 'thread-missing',\n 'mode-unknown',\n 'apply-failed',\n ]),\n }),\n ),\n }),\n },\n projectDelete: {\n method: 'DELETE',\n path: '/web/factory/projects/:id',\n description: 'Delete a Factory project',\n pathSchema: projectPathSchema,\n responseSchema: z.null(),\n },\n metricsGet: {\n method: 'GET',\n path: '/web/factory/projects/:id/metrics',\n description: 'Get Factory project metrics',\n pathSchema: projectPathSchema,\n querySchema: metricsQuerySchema,\n responseSchema: z.object({ metrics: entitySchema }),\n },\n healthThresholdsGet: {\n method: 'GET',\n path: '/web/factory/projects/:id/health/thresholds',\n description: 'Get queue-health thresholds',\n pathSchema: projectPathSchema,\n responseSchema: z.object({ thresholds: z.array(z.number().finite()) }),\n },\n boardCatalog: {\n method: 'GET',\n path: '/web/factory/projects/:id/boards',\n description: 'List installed Factory boards',\n pathSchema: projectPathSchema,\n responseSchema: z.object({\n boards: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n initialPhase: z.string(),\n phases: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n kind: z.enum(['resting', 'working', 'terminal']),\n role: z.string().optional(),\n transitions: z.array(z.object({ outcome: z.string().nullable(), to: z.string() })),\n }),\n ),\n }),\n ),\n }),\n },\n workItemList: {\n method: 'GET',\n path: '/web/factory/projects/:id/work-items',\n description: 'List Factory work items and running sessions',\n pathSchema: projectPathSchema,\n responseSchema: z.object({\n workItems: z.array(entitySchema),\n runningSessionIds: z.array(z.string()),\n parkedSessionIds: z.array(z.string()),\n }),\n },\n workItemCreate: {\n method: 'POST',\n path: '/web/factory/projects/:id/work-items',\n description: 'Create a work item in Intake',\n pathSchema: projectPathSchema,\n bodySchema: createWorkItemBodySchema,\n responseSchema: workItemResponseSchema,\n },\n workItemUpdate: {\n method: 'PATCH',\n path: '/web/factory/work-items/:id',\n description: 'Update non-stage work-item fields',\n pathSchema: workItemPathSchema,\n bodySchema: updateWorkItemBodySchema,\n responseSchema: workItemResponseSchema,\n },\n workItemDelete: {\n method: 'DELETE',\n path: '/web/factory/work-items/:id',\n description: 'Delete a Factory work item',\n pathSchema: workItemPathSchema,\n responseSchema: z.object({ ok: z.literal(true) }),\n },\n workItemTransition: {\n method: 'POST',\n path: '/web/factory/projects/:id/work-items/:workItemId/transition',\n description: 'Transition a work item with its expected revision',\n pathSchema: transitionPathSchema,\n bodySchema: transitionBodySchema,\n responseSchema: entitySchema,\n },\n workItemStart: {\n method: 'POST',\n path: '/web/factory/projects/:id/runs/start',\n description: 'Explicitly start a Factory work-item run',\n pathSchema: projectPathSchema,\n bodySchema: startWorkItemBodySchema,\n responseSchema: entitySchema,\n },\n workItemAutomationRun: {\n method: 'POST',\n path: '/web/factory/projects/:id/work-items/:workItemId/automation-runs',\n description: 'Enqueue an idempotent deferred skill dispatch for a trusted external orchestrator',\n pathSchema: transitionPathSchema,\n bodySchema: automationRunStartBodySchema,\n responseSchema: entitySchema,\n },\n decisionList: {\n method: 'GET',\n path: '/web/factory/projects/:id/decisions',\n description: 'List Factory decisions',\n pathSchema: projectPathSchema,\n querySchema: decisionQuerySchema,\n responseSchema: z.object({ decisions: z.array(entitySchema), nextCursor: z.string().optional() }),\n },\n decisionApprove: {\n method: 'POST',\n path: '/web/factory/projects/:id/decisions/:decisionId/approve',\n description: 'Approve a proposed Factory decision',\n pathSchema: projectDecisionPathSchema,\n responseSchema: decisionResponseSchema,\n },\n decisionDismiss: {\n method: 'POST',\n path: '/web/factory/projects/:id/decisions/:decisionId/dismiss',\n description: 'Dismiss a proposed Factory decision',\n pathSchema: projectDecisionPathSchema,\n responseSchema: decisionResponseSchema,\n },\n decisionRetry: {\n method: 'POST',\n path: '/web/factory/projects/:id/decisions/:decisionId/retry',\n description: 'Retry a failed retryable Factory decision',\n pathSchema: projectDecisionPathSchema,\n responseSchema: decisionResponseSchema,\n },\n attentionList: {\n method: 'GET',\n path: '/web/factory/projects/:id/attention',\n description: 'List the Factory attention inbox',\n pathSchema: projectPathSchema,\n querySchema: attentionQuerySchema,\n responseSchema: z.object({\n items: z.array(entitySchema),\n kinds: z.record(\n attentionKindSchema,\n z.object({\n open: z.number(),\n unread: z.number(),\n latest: z.object({ key: z.string(), at: z.string(), unread: z.boolean() }).nullable(),\n }),\n ),\n hasMore: z.boolean(),\n nextCursor: z.string().optional(),\n }),\n },\n attentionReadAll: {\n method: 'POST',\n path: '/web/factory/projects/:id/attention/read-all',\n description: 'Mark all Factory attention as read',\n pathSchema: projectPathSchema,\n querySchema: z.object({ before: attentionCursorSchema }),\n responseSchema: z.object({ ok: z.literal(true), hasMore: z.boolean(), nextCursor: z.string().optional() }),\n },\n attentionRead: {\n method: 'POST',\n path: '/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/read',\n description: 'Mark a Factory attention receipt as read',\n pathSchema: attentionActionPathSchema,\n responseSchema: z.object({ receipt: entitySchema }),\n },\n attentionArchive: {\n method: 'POST',\n path: '/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/archive',\n description: 'Archive a Factory attention receipt',\n pathSchema: attentionActionPathSchema,\n responseSchema: z.object({ receipt: entitySchema }),\n },\n attentionRestore: {\n method: 'POST',\n path: '/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/restore',\n description: 'Restore an archived Factory attention receipt',\n pathSchema: attentionActionPathSchema,\n responseSchema: z.object({ receipt: entitySchema }),\n },\n} as const satisfies Record<string, FactoryRouteContract>;\n\nexport type FactoryRouteContractKey = keyof typeof FACTORY_ROUTE_CONTRACTS;\n"],"mappings":";;;;;;AAQA,MAAM,sBAAsB,OAAO,KAAK,mBAAmB;AAC3D,MAAM,kBAAkB;AAYxB,MAAa,eAAe;AAC5B,MAAM,aAAa,EAAE,OAAO,CAAC,CAAC,MAAM,IAAI,OAAO,YAAY,CAAC;AAC5D,MAAM,eAAe,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AACrD,MAAM,mBAAmB,QAAgB,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;AACzE,MAAM,qBAAqB,QACzB,EACG,OAAO,CAAC,CACR,IAAI,GAAG,CAAC,CACR,WAAU,UAAS,MAAM,KAAK,CAAC,CAAC,CAChC,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;AAC3B,MAAM,oBAAoB,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,IAAI,OAAO,YAAY,CAAC;AAC1E,MAAM,mBAAmB,QACvB,EAAE,MAAM,CACN,EACG,OAAO,CAAC,CACR,KAAK,CAAC,CACN,IAAI,GAAG,CAAC,CACR,WAAU,UAAS,SAAS,IAAI,GACnC,EAAE,KAAK,CACT,CAAC;AACH,MAAM,oBAAoB,EAAE,OAAO,EAAE,IAAI,WAAW,CAAC;AACrD,MAAM,4BAA4B,EAAE,OAAO;CAAE,IAAI;CAAY,YAAY;AAAW,CAAC;AACrF,MAAM,qBAAqB,EAAE,OAAO,EAAE,IAAI,WAAW,CAAC;AACtD,MAAM,uBAAuB,EAAE,OAAO;CAAE,IAAI;CAAY,YAAY;AAAW,CAAC;AAEhF,MAAM,gBAAgB;AACtB,MAAM,wBAAwB,EAAE,OAAO,EAAE,SAAS,cAAc,CAAC;AACjE,MAAM,yBAAyB,EAAE,OAAO,EAAE,UAAU,aAAa,CAAC;AAClE,MAAM,yBAAyB,EAAE,OAAO,EAAE,UAAU,aAAa,CAAC;AAElE,MAAa,0BAA0B,EAAE,OAAO;CAC9C,MAAM,gBAAgB,GAAG;CACzB,aAAa,gBAAgB,GAAK,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,IAAI;CAC3D,YAAY,EAAE,KAAK,oBAAoB,CAAC,CAAC,SAAS;AACpD,CAAC;AAED,MAAa,0BAA0B,EACpC,OAAO;CACN,MAAM,gBAAgB,GAAG,CAAC,CAAC,SAAS;CACpC,aAAa,gBAAgB,GAAK,CAAC,CAAC,SAAS;CAC7C,gBAAgB,gBAAgB,GAAG,CAAC,CAAC,SAAS;CAC9C,YAAY,EAAE,KAAK,oBAAoB,CAAC,CAAC,SAAS;CAClD,sBAAsB,EAAE,KAAK,eAAe,CAAC,CAAC,SAAS;CACvD,gBAAgB,EAAE,QAAQ,CAAC,CAAC,SAAS;CACrC,kBAAkB,EAAE,QAAQ,CAAC,CAAC,SAAS;CACvC,yBAAyB,EAAE,QAAQ,CAAC,CAAC,SAAS;CAC9C,mBAAmB,EAAE,QAAQ,CAAC,CAAC,SAAS;AAC1C,CAAC,CAAC,CACD,QAAO,UAAS,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,GAAG,EAAE,SAAS,yCAAyC,CAAC;AAEvG,MAAM,8BAA8B,EAAE,OAAO;CAC3C,qBAAqB,EAAE,OAAO;CAC9B,cAAc,EAAE,OAAO;CACvB,cAAc,EAAE,OAAO;CAEvB,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;CACnC,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI;CACzB,eAAe,EAAE,QAAQ;CACzB,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,iBAAiB,EAAE,OAAO,CAAC,CAAC,SAAS;CACrC,iBAAiB,EAAE,KAAK;EAAC;EAAW;EAAc;CAAQ,CAAC;CAC3D,gBAAgB,EAAE,OAAO,CAAC,CAAC,SAAS;CACpC,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;AACnC,CAAC;;AAGD,MAAa,2BAA2B,EAAE,OAAO;CAC/C,UAAU,EAAE,OAAO;CACnB,gBAAgB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;CAChD,cAAc,EAAE,OAAO;EACrB,UAAU,EAAE,QAAQ;EACpB,QAAQ,EAAE,OAAO;GAAE,WAAW,EAAE,QAAQ;GAAG,SAAS,EAAE,QAAQ;EAAE,CAAC;CACnE,CAAC;AACH,CAAC;;AAGD,MAAa,iCAAiC,EAAE,OAAO;CACrD,UAAU,EAAE,OAAO;EACjB,SAAS,EAAE,QAAQ;EACnB,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;EAC1B,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;;EAE9B,mBAAmB,EAAE,QAAQ;CAC/B,CAAC;CACD,MAAM,EAAE,OAAO;EAAE,SAAS,EAAE,QAAQ;EAAG,iBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI;CAAE,CAAC;AAC5E,CAAC;;AAGD,MAAa,6BAA6B,EAAE,OAAO;CAAE,SAAS,EAAE,OAAO;CAAG,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;AAAE,CAAC;AAE9G,MAAa,mCAAmC,EAAE,OAAO,EACvD,aAAa,EAAE,OAAO;CACpB,SAAS;;CAET,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;CAC1C,yBAAyB,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7C,uBAAuB,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3C,kBAAkB,EAAE,OAAO,CAAC,CAAC,SAAS;CACtC,qBAAqB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;CAC/D,cAAc,EAAE,MAAM,2BAA2B;CACjD,eAAe,+BAA+B,SAAS;CACvD,OAAO,2BAA2B,SAAS,CAAC,CAAC,SAAS;AACxD,CAAC,EACH,CAAC;AAED,MAAM,+BAA+B,EAAE,KAAK;CAAC;CAAW;CAAY;CAAS;CAAU;AAAS,CAAC;AAEjG,MAAa,sCAAsC,EAAE,OAAO;CAC1D,SAAS,EAAE,KAAK;EAAC;EAAW;EAAW;EAAe;CAAQ,CAAC;CAC/D,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;AAC9B,CAAC;AAED,MAAa,yBAAyB,EAAE,OAAO;CAC7C,SAAS,EAAE,OAAO;CAClB,QAAQ;CACR,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;AACrC,CAAC;AAED,MAAa,kCAAkC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,EAAE,CAAC;AACnG,MAAa,iCAAiC,EAAE,OAAO,EAAE,OAAO,uBAAuB,CAAC;AAExF,MAAM,kBAAkB,kBAAkB,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AAE/E,MAAM,mCAAmC,EAAE,OAAO;CAChD,qBAAqB;CACrB,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CAC3C,eAAe,EAAE,QAAQ,CAAC,CAAC,SAAS;CACpC,cAAc,gBAAgB,GAAK,CAAC,CAAC,SAAS;CAC9C,iBAAiB,gBAAgB,GAAK,CAAC,CAAC,SAAS;AACnD,CAAC;AAED,MAAa,qCAAqC,EAC/C,OAAO;CACN,yBAAyB,gBAAgB,GAAK,CAAC,CAC5C,QAAO,UAAS,UAAU,QAAQ,MAAM,WAAW,GAAG,GAAG,EACxD,SAAS,2CACX,CAAC,CAAC,CACD,SAAS;;CAEZ,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;CAChE,uBAAuB,gBAAgB,GAAK,CAAC,CAAC,SAAS;;CAEvD,eAAe,EACZ,OAAO;EACN,UAAU,EACP,OAAO;GACN,SAAS,EAAE,QAAQ;GACnB,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,SAAS;GACjD,UAAU,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,SAAS;EACvD,CAAC,CAAC,CACD,SAAS;EACZ,MAAM,EACH,OAAO;GACN,SAAS,EAAE,QAAQ,CAAC,CAAC,SAAS;GAC9B,iBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAK,CAAC,CAAC,SAAS;EAC/D,CAAC,CAAC,CACD,SAAS;CACd,CAAC,CAAC,CACD,SAAS;CACZ,cAAc,EACX,MAAM,gCAAgC,CAAC,CACvC,IAAI,GAAG,CAAC,CACR,aAAa,SAAS,QAAQ;EAE7B,IAAI,IADY,IAAI,QAAQ,KAAI,UAAS,MAAM,mBAAmB,CAC5D,CAAC,CAAC,SAAS,QAAQ,QACvB,IAAI,SAAS;GAAE,MAAM;GAAU,SAAS;EAAqC,CAAC;EAEhF,MAAM,YAAY,QAAQ,SAAQ,UAAU,MAAM,aAAa,KAAA,IAAY,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAE;EACjG,IAAI,UAAU,WAAW,GAAG;EAC5B,MAAM,WAAW,MAAM,KAAK,EAAE,QAAQ,QAAQ,OAAO,IAAI,GAAG,UAAU,QAAQ,CAAC;EAC/E,IACE,UAAU,WAAW,QAAQ,UAC7B,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,MAAM,SAAS,EAAE,GAErE,IAAI,SAAS;GACX,MAAM;GACN,SAAS;EACX,CAAC;CAEL,CAAC,CAAC,CACD,SAAS;AACd,CAAC,CAAC,CACD,QAAO,UAAS,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,GAAG,EAAE,SAAS,6CAA6C,CAAC;AAE3G,MAAM,eAAe,EAClB,MACC,EACG,OAAO,CAAC,CACR,IAAI,EAAE,CAAC,CACP,MAAM,wBAAwB,CACnC,CAAC,CACA,IAAI,CAAC,CAAC,CACN,IAAI,EAAE,CAAC,CACP,QAAO,WAAU,IAAI,IAAI,MAAM,CAAC,CAAC,SAAS,OAAO,QAAQ,EAAE,SAAS,wBAAwB,CAAC;AAEhG,MAAM,uBAAuB,EAC1B,OAAO;CACN,eAAe,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;CACxC,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;CAC/B,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;CACrC,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,IAAK,CAAC,CAAC,SAAS;AACtC,CAAC,CAAC,CACD,SAAS;AAEZ,MAAM,gBAAgB,EAAE,OAAO;CAC7B,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;CACpC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;CACjC,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;AACrC,CAAC;AAED,MAAM,iBAAiB,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,aAAa;AACxE,MAAM,iBAAiB,EACpB,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CACpD,QAAO,UAAS;CACf,IAAI;EACF,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC,UAAU,KAAK;CAC9C,QAAQ;EACN,OAAO;CACT;AACF,GAAG,yBAAyB,CAAC,CAC5B,WAAU,UAAS;CAClB,IAAI,UAAU,MAAM,OAAO;CAC3B,MAAM,EACJ,+BAA+B,kBAC/B,kCAAkC,iBAClC,yBAAyB,0BACzB,GAAG,aACD;CACJ,OAAO;AACT,CAAC;AAEH,MAAa,2BAA2B,EAAE,OAAO;CAC/C,OAAO,kBAAkB,GAAG;CAC5B,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,SAAS;CAC3C,gBAAgB,qBAAqB,SAAS;CAC9C,kBAAkB,WAAW,SAAS,CAAC,CAAC,SAAS;CACjD,QAAQ,aAAa,SAAS;CAC9B,UAAU,eAAe,SAAS;CAClC,UAAU,eAAe,SAAS;AACpC,CAAC;AAED,MAAa,2BAA2B,EACrC,OAAO;CACN,OAAO,kBAAkB,GAAG,CAAC,CAAC,SAAS;CACvC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,SAAS;CAC3C,kBAAkB,WAAW,SAAS,CAAC,CAAC,SAAS;CACjD,QAAQ,aAAa,SAAS;CAC9B,UAAU,eAAe,SAAS;CAClC,UAAU,eAAe,SAAS;CAClC,kBAAkB,EAAE,QAAQ,IAAI,CAAC,CAAC,SAAS;AAC7C,CAAC,CAAC,CACD,QAAO,UAAS,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,GAAG,EAAE,SAAS,2CAA2C,CAAC;AAEzG,MAAa,uBAAuB,EACjC,OAAO;CACN,OAAO,EAAE,OAAO,CAAC,CAAC,IAAA,GAA+B,CAAC,CAAC,MAAM,mBAAmB;CAC5E,OAAO,EAAE,OAAO,CAAC,CAAC,IAAA,GAA+B,CAAC,CAAC,MAAM,mBAAmB;CAC5E,kBAAkB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;CACxC,WAAW;CACX,OAAO,gBAAgB,GAAG;CAC1B,SAAS,EAAE,QAAQ,CAAC,CAAC,SAAS;AAChC,CAAC,CAAC,CACD,WAAU,WAAU;CACnB,OAAO,MAAM;CACb,OAAO,MAAM;CACb,kBAAkB,MAAM;CACxB,SAAS;EAAE,MAAM;EAAkB,UAAU,MAAM;CAAU;CAC7D,OAAO,MAAM;CACb,GAAI,MAAM,YAAY,OAAO,EAAE,SAAS,KAAK,IAAI,CAAC;AACpD,EAAE;AAEJ,MAAa,0BAA0B,EAAE,OAAO;CAC9C,WAAW;CACX,aAAa,gBAAgB,GAAG;CAChC,YAAY;CACZ,UAAU,EAAE,OAAO;EACjB,IAAI;EACJ,MAAM,gBAAgB,EAAE;EACxB,OAAO;CACT,CAAC;AACH,CAAC;AAED,MAAa,+BAA+B,EACzC,OAAO;CACN,WAAW;CACX,kBAAkB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;CACxC,MAAM,EAAE,KAAK,mBAAmB;CAChC,WAAW,gBAAgB,GAAG;CAC9B,WAAW,gBAAgB,IAAK,CAAC,CAAC,SAAS;AAC7C,CAAC,CAAC,CACD,OAAO;AAEV,MAAa,qBAAqB,EAAE,OAAO;CACzC,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS;AAC1B,CAAC;AAED,MAAM,uBAAuB,EAAE,KAAK;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,sBAAsB,cAAsB,QAChD,EACG,OAAO,CAAC,CACR,SAAS,CAAC,CACV,WAAU,UAAS;CAClB,MAAM,SAAS,QAAQ,OAAO,SAAS,OAAO,EAAE,IAAI;CACpD,OAAO,OAAO,SAAS,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC,IAAI;AACxE,CAAC;AACL,MAAM,uBAAuB,EAC1B,OAAO,CAAC,CACR,SAAS,CAAC,CACV,WAAW,OAAO,YAAY;CAC7B,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,IAAI;EACF,MAAM,UAAU,KAAK,MAAM,OAAO,KAAK,OAAO,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;EAC3E,IACE,CAAC,MAAM,QAAQ,OAAO,KACtB,QAAQ,WAAW,KACnB,OAAO,QAAQ,OAAO,YACtB,OAAO,QAAQ,OAAO,UAEtB,MAAM,IAAI,MAAM,gBAAgB;EAElC,MAAM,YAAY,IAAI,KAAK,QAAQ,EAAE;EACrC,IAAI,OAAO,MAAM,UAAU,QAAQ,CAAC,KAAK,kBAAC,IAAI,OAAA,+EAAmB,EAAC,CAAC,KAAK,QAAQ,EAAE,GAChF,MAAM,IAAI,MAAM,gBAAgB;EAElC,OAAO;GAAE;GAAW,IAAI,QAAQ;EAAG;CACrC,QAAQ;EACN,QAAQ,SAAS;GAAE,MAAM;GAAU,SAAS;EAAiB,CAAC;EAC9D,OAAO,EAAE;CACX;AACF,CAAC;AAEH,MAAa,sBAAsB,EAAE,OAAO;CAC1C,UAAU,EACP,OAAO,CAAC,CACR,SAAS,CAAC,CACV,WAAU,UAAS;EAClB,IAAI,CAAC,OAAO,OAAO,KAAA;EACnB,MAAM,WAAW,CAAC,GAAG,IAAI,IAAI,MAAM,MAAM,GAAG,CAAC,CAAC,KAAI,WAAU,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAC3E,WAAU,qBAAqB,UAAU,MAAM,CAAC,CAAC,OACnD;EACA,OAAO,SAAS,SAAS,IAAI,WAAW,KAAA;CAC1C,CAAC;CACH,QAAQ;CACR,OAAO,mBAAmB,IAAI,EAAE;AAClC,CAAC;AAED,MAAM,sBAAsB,EAAE,KAAK;CACjC;CACA;CACA;CACA;CACA;AACF,CAAC;AAKD,SAAS,6BAA6B,OAAqD;CACzF,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,KAAK,OAAO,MAAM,OAAO,YAAY,OAAO,MAAM,OAAO,UACrG;CAEF,MAAM,aAAa,IAAI,KAAK,MAAM,EAAE;CACpC,IAAI,OAAO,MAAM,WAAW,QAAQ,CAAC,KAAK,kBAAC,IAAI,OAAA,+EAAmB,EAAC,CAAC,KAAK,MAAM,EAAE,GAAG,OAAO,KAAA;CAC3F,OAAO;EAAE;EAAY,IAAI,MAAM;CAAG;AACpC;AAEA,MAAM,wBAAwB,EAC3B,OAAO,CAAC,CACR,SAAS,CAAC,CACV,WAAW,OAAO,YAAY;CAC7B,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,IAAI;EACF,MAAM,UAAmB,KAAK,MAAM,OAAO,KAAK,OAAO,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;EACpF,IAAI,MAAM,QAAQ,OAAO,GAAG;GAC1B,MAAM,SAAS,6BAA6B,OAAO;GACnD,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,gBAAgB;GAC7C,uBAAO,IAAI,IAAwD,CAAC,CAAC,qBAAqB,MAAM,CAAC,CAAC;EACpG;EACA,IAAI,CAAC,WAAW,OAAO,YAAY,UAAU,MAAM,IAAI,MAAM,gBAAgB;EAC7E,MAAM,0BAA8B,IAAI,IAAI;EAC5C,KAAK,MAAM,CAAC,MAAM,kBAAkB,OAAO,QAAQ,OAAO,GAAG;GAC3D,MAAM,aAAa,oBAAoB,UAAU,IAAI;GACrD,IAAI,CAAC,WAAW,SAAS,MAAM,IAAI,MAAM,gBAAgB;GACzD,IAAI,kBAAkB,MAAM;IAC1B,QAAQ,IAAI,WAAW,MAAM,KAAA,CAAS;IACtC;GACF;GACA,MAAM,WAAW,6BAA6B,aAAa;GAC3D,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,gBAAgB;GAC/C,QAAQ,IAAI,WAAW,MAAM,QAAQ;EACvC;EACA,IAAI,QAAQ,SAAS,GAAG,MAAM,IAAI,MAAM,gBAAgB;EACxD,OAAO;CACT,QAAQ;EACN,QAAQ,SAAS;GAAE,MAAM;GAAU,SAAS;EAAiB,CAAC;EAC9D,OAAO,EAAE;CACX;AACF,CAAC;AAEH,MAAa,uBAAuB,EAAE,OAAO;CAC3C,MAAM,EAAE,KAAK;EAAC;EAAQ;EAAU;CAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,MAAM;CACtE,MAAM,EAAE,MAAM,mBAAmB,CAAC,CAAC,SAAS;CAC5C,QAAQ;CACR,OAAO,mBAAmB,IAAI,EAAE;CAChC,QAAQ,EACL,OAAO,CAAC,CACR,SAAS,CAAC,CACV,WAAU,UAAS,OAAO,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,GAAG,KAAK,KAAA,CAAS;AAC9E,CAAC;AAED,MAAM,4BAA4B,EAC/B,OAAO;CACN,IAAI;CACJ,MAAM;CACN,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;CACnC,YAAY,EACT,OAAO,CAAC,CACR,MAAM,gBAAgB,CAAC,CACvB,WAAW,OAAO,YAAY;EAC7B,MAAM,aAAa,OAAO,KAAK;EAC/B,IAAI,OAAO,cAAc,UAAU,GAAG,OAAO;EAC7C,QAAQ,SAAS;GAAE,MAAM;GAAU,SAAS;EAAqB,CAAC;EAClE,OAAO,EAAE;CACX,CAAC;AACL,CAAC,CAAC,CACD,QAAO,UAAS,IAAI,OAAO,YAAY,CAAC,CAAC,KAAK,MAAM,QAAQ,GAAG;CAC9D,SAAS;CACT,MAAM,CAAC,UAAU;AACnB,CAAC;AAEH,MAAa,0BAA0B;CACrC,aAAa;EACX,QAAQ;EACR,MAAM;EACN,aAAa;EACb,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,EAAE,CAAC;CAC/D;CACA,eAAe;EACb,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB;CAClB;CACA,YAAY;EACV,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB;CAClB;CACA,eAAe;EACb,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,YAAY;EACZ,gBAAgB;CAClB;CACA,uBAAuB;EACrB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB;CAClB;CACA,0BAA0B;EACxB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,YAAY;EACZ,gBAAgB;CAClB;CACA,yBAAyB;EACvB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB;CAClB;CACA,0BAA0B;EACxB,QAAQ;EACR,MAAM;EACN,aACE;EACF,YAAY;EACZ,gBAAgB;CAClB;CACA,4BAA4B;EAC1B,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB;CAClB;CACA,0BAA0B;EACxB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,EAAE,OAAO;GACvB,SAAS,EAAE,OAAO;GAClB,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC;GAC3B,SAAS,EAAE,MACT,EAAE,OAAO;IACP,UAAU,EAAE,OAAO;IACnB,QAAQ,EAAE,KAAK;KACb;KACA;KACA;KACA;KACA;KACA;IACF,CAAC;GACH,CAAC,CACH;EACF,CAAC;CACH;CACA,eAAe;EACb,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,EAAE,KAAK;CACzB;CACA,YAAY;EACV,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,aAAa;EACb,gBAAgB,EAAE,OAAO,EAAE,SAAS,aAAa,CAAC;CACpD;CACA,qBAAqB;EACnB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACvE;CACA,cAAc;EACZ,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,EAAE,OAAO,EACvB,QAAQ,EAAE,MACR,EAAE,OAAO;GACP,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,OAAO;GAChB,cAAc,EAAE,OAAO;GACvB,QAAQ,EAAE,MACR,EAAE,OAAO;IACP,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,KAAK;KAAC;KAAW;KAAW;IAAU,CAAC;IAC/C,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;IAC1B,aAAa,EAAE,MAAM,EAAE,OAAO;KAAE,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;KAAG,IAAI,EAAE,OAAO;IAAE,CAAC,CAAC;GACnF,CAAC,CACH;EACF,CAAC,CACH,EACF,CAAC;CACH;CACA,cAAc;EACZ,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,EAAE,OAAO;GACvB,WAAW,EAAE,MAAM,YAAY;GAC/B,mBAAmB,EAAE,MAAM,EAAE,OAAO,CAAC;GACrC,kBAAkB,EAAE,MAAM,EAAE,OAAO,CAAC;EACtC,CAAC;CACH;CACA,gBAAgB;EACd,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,YAAY;EACZ,gBAAgB;CAClB;CACA,gBAAgB;EACd,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,YAAY;EACZ,gBAAgB;CAClB;CACA,gBAAgB;EACd,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;CAClD;CACA,oBAAoB;EAClB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,YAAY;EACZ,gBAAgB;CAClB;CACA,eAAe;EACb,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,YAAY;EACZ,gBAAgB;CAClB;CACA,uBAAuB;EACrB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,YAAY;EACZ,gBAAgB;CAClB;CACA,cAAc;EACZ,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,aAAa;EACb,gBAAgB,EAAE,OAAO;GAAE,WAAW,EAAE,MAAM,YAAY;GAAG,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;EAAE,CAAC;CAClG;CACA,iBAAiB;EACf,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB;CAClB;CACA,iBAAiB;EACf,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB;CAClB;CACA,eAAe;EACb,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB;CAClB;CACA,eAAe;EACb,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,aAAa;EACb,gBAAgB,EAAE,OAAO;GACvB,OAAO,EAAE,MAAM,YAAY;GAC3B,OAAO,EAAE,OACP,qBACA,EAAE,OAAO;IACP,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,OAAO;IACjB,QAAQ,EAAE,OAAO;KAAE,KAAK,EAAE,OAAO;KAAG,IAAI,EAAE,OAAO;KAAG,QAAQ,EAAE,QAAQ;IAAE,CAAC,CAAC,CAAC,SAAS;GACtF,CAAC,CACH;GACA,SAAS,EAAE,QAAQ;GACnB,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;EAClC,CAAC;CACH;CACA,kBAAkB;EAChB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,aAAa,EAAE,OAAO,EAAE,QAAQ,sBAAsB,CAAC;EACvD,gBAAgB,EAAE,OAAO;GAAE,IAAI,EAAE,QAAQ,IAAI;GAAG,SAAS,EAAE,QAAQ;GAAG,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;EAAE,CAAC;CAC3G;CACA,eAAe;EACb,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,EAAE,OAAO,EAAE,SAAS,aAAa,CAAC;CACpD;CACA,kBAAkB;EAChB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,EAAE,OAAO,EAAE,SAAS,aAAa,CAAC;CACpD;CACA,kBAAkB;EAChB,QAAQ;EACR,MAAM;EACN,aAAa;EACb,YAAY;EACZ,gBAAgB,EAAE,OAAO,EAAE,SAAS,aAAa,CAAC;CACpD;AACF"}
1
+ {"version":3,"file":"contracts.js","names":[],"sources":["../../src/routes/contracts.ts"],"sourcesContent":["import { THINKING_LEVEL_VALUES } from '@mastra/code-sdk/thinking';\nimport type { ThinkingLevelSetting } from '@mastra/code-sdk/thinking';\nimport { z } from 'zod';\n\nimport { FACTORY_ROLE_STAGES } from '../rules/types.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from '../rules/validation.js';\nimport { FACTORY_MODEL_SCOPES } from '../storage/domains/projects/base.js';\n\nconst FACTORY_ROLE_VALUES = Object.keys(FACTORY_ROLE_STAGES) as [string, ...string[]];\nconst THINKING_LEVELS = THINKING_LEVEL_VALUES as [ThinkingLevelSetting, ...ThinkingLevelSetting[]];\n\nexport type FactoryRouteContract = {\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE';\n path: string;\n description: string;\n pathSchema?: z.ZodType;\n querySchema?: z.ZodType;\n bodySchema?: z.ZodType;\n responseSchema: z.ZodType;\n};\n\nexport const UUID_PATTERN = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$';\nconst uuidSchema = z.string().regex(new RegExp(UUID_PATTERN));\nconst entitySchema = z.record(z.string(), z.unknown());\nconst nonEmptyTrimmed = (max: number) => z.string().trim().min(1).max(max);\nconst rawBoundedTrimmed = (max: number) =>\n z\n .string()\n .max(max)\n .transform(value => value.trim())\n .pipe(z.string().min(1));\nconst trimmedUuidSchema = z.string().trim().regex(new RegExp(UUID_PATTERN));\nconst nullableTrimmed = (max: number) =>\n z.union([\n z\n .string()\n .trim()\n .max(max)\n .transform(value => value || null),\n z.null(),\n ]);\nconst projectPathSchema = z.object({ id: uuidSchema });\nconst projectDecisionPathSchema = z.object({ id: uuidSchema, decisionId: uuidSchema });\nconst workItemPathSchema = z.object({ id: uuidSchema });\nconst transitionPathSchema = z.object({ id: uuidSchema, workItemId: uuidSchema });\n\nconst projectSchema = entitySchema;\nconst projectResponseSchema = z.object({ project: projectSchema });\nconst workItemResponseSchema = z.object({ workItem: entitySchema });\nconst decisionResponseSchema = z.object({ decision: entitySchema });\n\nexport const createProjectBodySchema = z.object({\n name: nonEmptyTrimmed(200),\n description: nullableTrimmed(2_000).optional().default(null),\n modelScope: z.enum(FACTORY_MODEL_SCOPES).optional(),\n});\n\nexport const updateProjectBodySchema = z\n .object({\n name: nonEmptyTrimmed(200).optional(),\n description: nullableTrimmed(2_000).optional(),\n defaultModelId: nullableTrimmed(200).optional(),\n modelScope: z.enum(FACTORY_MODEL_SCOPES).optional(),\n defaultThinkingLevel: z.enum(THINKING_LEVELS).optional(),\n autoRunEnabled: z.boolean().optional(),\n autoApprovePlans: z.boolean().optional(),\n reviewTestChecksEnabled: z.boolean().optional(),\n selfReviewEnabled: z.boolean().optional(),\n })\n .refine(input => Object.keys(input).length > 0, { message: 'At least one project field is required' });\n\nconst environmentRepositorySchema = z.object({\n projectRepositoryId: z.string(),\n connectionId: z.string(),\n repositoryId: z.string(),\n // Null when the repository row behind the link is missing.\n slug: z.string().nullable(),\n defaultBranch: z.string().nullable(),\n position: z.number().int(),\n inEnvironment: z.boolean(),\n setupCommand: z.string().nullable(),\n teardownCommand: z.string().nullable(),\n lastBuildStatus: z.enum(['unbuilt', 'configured', 'failed']),\n lastBuildError: z.string().nullable(),\n lastBuiltAt: z.string().nullable(),\n});\n\n/** The configured FactorySandbox as factory describes it: provider id, JSON Schema of its settings, capabilities. */\nexport const environmentSandboxSchema = z.object({\n provider: z.string(),\n settingsSchema: z.record(z.string(), z.unknown()),\n capabilities: z.object({\n template: z.boolean(),\n builds: z.object({ available: z.boolean(), history: z.boolean() }),\n }),\n});\n\n/** When the environment builds on its own; present only when the sandbox can build. */\nexport const environmentBuildTriggersSchema = z.object({\n schedule: z.object({\n enabled: z.boolean(),\n cron: z.string().nullable(),\n timezone: z.string().nullable(),\n /** False when the host's storage has no schedules domain; the cron trigger cannot be enabled then. */\n scheduleAvailable: z.boolean(),\n }),\n push: z.object({ enabled: z.boolean(), debounceMinutes: z.number().int() }),\n});\n\n/** The last build factory asked the provider for, from the project row only; status is read live. */\nexport const environmentLastBuildSchema = z.object({ buildId: z.string(), attemptedAt: z.string().nullable() });\n\nexport const projectEnvironmentResponseSchema = z.object({\n environment: z.object({\n sandbox: environmentSandboxSchema,\n /** The stored settings document; only keys the user set. */\n settings: z.record(z.string(), z.unknown()),\n sandboxWorkingDirectory: z.string().nullable(),\n workspaceSetupCommand: z.string().nullable(),\n activeTemplateId: z.string().nullable(),\n activeTemplateHeads: z.record(z.string(), z.string()).nullable(),\n repositories: z.array(environmentRepositorySchema),\n buildTriggers: environmentBuildTriggersSchema.optional(),\n build: environmentLastBuildSchema.nullable().optional(),\n }),\n});\n\nconst environmentBuildStatusSchema = z.enum(['pending', 'building', 'ready', 'failed', 'unknown']);\n\nexport const environmentBuildStartResponseSchema = z.object({\n outcome: z.enum(['started', 'skipped', 'unavailable', 'failed']),\n buildId: z.string().optional(),\n templateId: z.string().optional(),\n reason: z.string().optional(),\n});\n\nexport const environmentBuildSchema = z.object({\n buildId: z.string(),\n status: environmentBuildStatusSchema,\n templateId: z.string().optional(),\n startedAt: z.string().optional(),\n finishedAt: z.string().optional(),\n error: z.string().optional(),\n logs: z.array(z.string()).optional(),\n});\n\nexport const environmentBuildsResponseSchema = z.object({ builds: z.array(environmentBuildSchema) });\nexport const environmentBuildResponseSchema = z.object({ build: environmentBuildSchema });\n\nconst buildPathSchema = projectPathSchema.extend({ buildId: z.string().min(1) });\n\nconst environmentRepositoryPatchSchema = z.object({\n projectRepositoryId: uuidSchema,\n position: z.number().int().min(1).optional(),\n inEnvironment: z.boolean().optional(),\n setupCommand: nullableTrimmed(2_000).optional(),\n teardownCommand: nullableTrimmed(2_000).optional(),\n});\n\nexport const updateProjectEnvironmentBodySchema = z\n .object({\n sandboxWorkingDirectory: nullableTrimmed(1_000)\n .refine(value => value === null || value.startsWith('/'), {\n message: 'sandboxWorkingDirectory must be absolute',\n })\n .optional(),\n /** Partial settings merged onto the stored document; null removes a key. Validated by the FactorySandbox. */\n settings: z.record(z.string(), z.unknown().nullable()).optional(),\n workspaceSetupCommand: nullableTrimmed(2_000).optional(),\n /** Build triggers; only accepted when the sandbox can build. */\n buildTriggers: z\n .object({\n schedule: z\n .object({\n enabled: z.boolean(),\n cron: z.string().trim().min(1).max(100).optional(),\n timezone: z.string().trim().min(1).max(100).optional(),\n })\n .optional(),\n push: z\n .object({\n enabled: z.boolean().optional(),\n debounceMinutes: z.number().int().min(0).max(1_440).optional(),\n })\n .optional(),\n })\n .optional(),\n repositories: z\n .array(environmentRepositoryPatchSchema)\n .max(100)\n .superRefine((entries, ctx) => {\n const ids = new Set(entries.map(entry => entry.projectRepositoryId));\n if (ids.size !== entries.length) {\n ctx.addIssue({ code: 'custom', message: 'Each repository may be listed once' });\n }\n const positions = entries.flatMap(entry => (entry.position === undefined ? [] : [entry.position]));\n if (positions.length === 0) return;\n const expected = Array.from({ length: entries.length }, (_, index) => index + 1);\n if (\n positions.length !== entries.length ||\n [...positions].sort((a, b) => a - b).some((p, i) => p !== expected[i])\n ) {\n ctx.addIssue({\n code: 'custom',\n message: 'Positions must be a permutation of 1..n over the listed repositories',\n });\n }\n })\n .optional(),\n })\n .refine(input => Object.keys(input).length > 0, { message: 'At least one environment field is required' });\n\nconst stagesSchema = z\n .array(\n z\n .string()\n .max(64)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i),\n )\n .min(1)\n .max(16)\n .refine(stages => new Set(stages).size === stages.length, { message: 'Stages must be unique' });\n\nconst externalSourceSchema = z\n .object({\n integrationId: z.string().min(1).max(128),\n type: z.string().min(1).max(128),\n externalId: z.string().min(1).max(512),\n url: z.string().max(2_048).optional(),\n })\n .nullable();\n\nconst sessionSchema = z.object({\n sessionId: z.string().min(1).max(512),\n branch: z.string().min(1).max(512),\n threadId: z.string().min(1).max(512),\n});\n\nconst sessionsSchema = z.record(z.string().min(1).max(64), sessionSchema);\nconst metadataSchema = z\n .union([z.record(z.string(), z.unknown()), z.null()])\n .refine(value => {\n try {\n return JSON.stringify(value).length <= 16 * 1024;\n } catch {\n return false;\n }\n }, 'Metadata exceeds 16 KiB')\n .transform(value => {\n if (value === null) return null;\n const {\n factoryRuleMaterializationKey: _materialization,\n factoryPullRequestReconciliation: _reconciliation,\n externalSourceMissingAt: _externalSourceMissingAt,\n ...metadata\n } = value;\n return metadata;\n });\n\nexport const createWorkItemBodySchema = z.object({\n title: rawBoundedTrimmed(500),\n board: z.string().min(1).max(128).optional(),\n externalSource: externalSourceSchema.optional(),\n parentWorkItemId: uuidSchema.nullable().optional(),\n stages: stagesSchema.optional(),\n sessions: sessionsSchema.optional(),\n metadata: metadataSchema.optional(),\n});\n\nexport const updateWorkItemBodySchema = z\n .object({\n title: rawBoundedTrimmed(500).optional(),\n board: z.string().min(1).max(128).optional(),\n parentWorkItemId: uuidSchema.nullable().optional(),\n stages: stagesSchema.optional(),\n sessions: sessionsSchema.optional(),\n metadata: metadataSchema.optional(),\n plansPreapproved: z.literal(true).optional(),\n })\n .refine(input => Object.keys(input).length > 0, { message: 'At least one work-item field is required' });\n\nexport const transitionBodySchema = z\n .object({\n board: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n expectedRevision: z.number().int().min(1),\n requestId: trimmedUuidSchema,\n cause: nonEmptyTrimmed(256),\n reenter: z.unknown().optional(),\n })\n .transform(input => ({\n board: input.board,\n stage: input.stage,\n expectedRevision: input.expectedRevision,\n ingress: { type: 'human' as const, identity: input.requestId },\n cause: input.cause,\n ...(input.reenter === true ? { reenter: true } : {}),\n }));\n\nexport const startWorkItemBodySchema = z.object({\n sessionId: trimmedUuidSchema,\n threadTitle: nonEmptyTrimmed(512),\n kickoffKey: trimmedUuidSchema,\n workItem: z.object({\n id: trimmedUuidSchema,\n role: nonEmptyTrimmed(32),\n input: createWorkItemBodySchema,\n }),\n});\n\nexport const automationRunStartBodySchema = z\n .object({\n requestId: trimmedUuidSchema,\n expectedRevision: z.number().int().min(1),\n role: z.enum(FACTORY_ROLE_VALUES),\n skillName: nonEmptyTrimmed(128),\n arguments: nonEmptyTrimmed(4_096).optional(),\n })\n .strict();\n\nexport const metricsQuerySchema = z.object({\n from: z.string().optional(),\n to: z.string().optional(),\n});\n\nconst decisionStatusSchema = z.enum([\n 'pending',\n 'proposed',\n 'dismissed',\n 'superseded',\n 'leased',\n 'retry',\n 'succeeded',\n 'failed',\n]);\nconst boundedLimitSchema = (defaultValue: number, max: number) =>\n z\n .string()\n .optional()\n .transform(value => {\n const parsed = value ? Number.parseInt(value, 10) : defaultValue;\n return Number.isFinite(parsed) ? Math.max(1, Math.min(max, parsed)) : defaultValue;\n });\nconst decisionCursorSchema = z\n .string()\n .optional()\n .transform((value, context) => {\n if (!value) return undefined;\n try {\n const decoded = JSON.parse(Buffer.from(value, 'base64url').toString('utf8')) as unknown;\n if (\n !Array.isArray(decoded) ||\n decoded.length !== 2 ||\n typeof decoded[0] !== 'string' ||\n typeof decoded[1] !== 'string'\n ) {\n throw new Error('invalid cursor');\n }\n const createdAt = new Date(decoded[0]);\n if (Number.isNaN(createdAt.getTime()) || !new RegExp(UUID_PATTERN).test(decoded[1])) {\n throw new Error('invalid cursor');\n }\n return { createdAt, id: decoded[1] };\n } catch {\n context.addIssue({ code: 'custom', message: 'Invalid cursor' });\n return z.NEVER;\n }\n });\n\nexport const decisionQuerySchema = z.object({\n statuses: z\n .string()\n .optional()\n .transform(value => {\n if (!value) return undefined;\n const statuses = [...new Set(value.split(',').map(status => status.trim()))].filter(\n status => decisionStatusSchema.safeParse(status).success,\n ) as Array<z.infer<typeof decisionStatusSchema>>;\n return statuses.length > 0 ? statuses : undefined;\n }),\n before: decisionCursorSchema,\n limit: boundedLimitSchema(25, 50),\n});\n\nconst attentionKindSchema = z.enum([\n 'automation-failed',\n 'automation-proposed',\n 'mention',\n 'activity',\n 'agent-waiting',\n]);\ntype AttentionKind = z.infer<typeof attentionKindSchema>;\ntype AttentionStreamPosition = { occurredAt: Date; id: string };\ntype AttentionCursorMap = Map<AttentionKind, AttentionStreamPosition | undefined>;\n\nfunction parseAttentionStreamPosition(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()) || !new RegExp(UUID_PATTERN).test(value[1])) return undefined;\n return { occurredAt, id: value[1] };\n}\n\nconst attentionCursorSchema = z\n .string()\n .optional()\n .transform((value, context) => {\n if (!value) return undefined;\n try {\n const decoded: unknown = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));\n if (Array.isArray(decoded)) {\n const legacy = parseAttentionStreamPosition(decoded);\n if (!legacy) throw new Error('invalid cursor');\n return new Map<AttentionKind, AttentionStreamPosition | undefined>([['automation-failed', legacy]]);\n }\n if (!decoded || typeof decoded !== 'object') throw new Error('invalid cursor');\n const cursors: AttentionCursorMap = new Map();\n for (const [kind, positionValue] of Object.entries(decoded)) {\n const parsedKind = attentionKindSchema.safeParse(kind);\n if (!parsedKind.success) throw new Error('invalid cursor');\n if (positionValue === null) {\n cursors.set(parsedKind.data, undefined);\n continue;\n }\n const position = parseAttentionStreamPosition(positionValue);\n if (!position) throw new Error('invalid cursor');\n cursors.set(parsedKind.data, position);\n }\n if (cursors.size === 0) throw new Error('invalid cursor');\n return cursors;\n } catch {\n context.addIssue({ code: 'custom', message: 'Invalid cursor' });\n return z.NEVER;\n }\n });\n\nexport const attentionQuerySchema = z.object({\n view: z.enum(['open', 'unread', 'archived']).optional().default('open'),\n kind: z.array(attentionKindSchema).optional(),\n before: attentionCursorSchema,\n limit: boundedLimitSchema(25, 50),\n search: z\n .string()\n .optional()\n .transform(value => value?.trim().toLowerCase().slice(0, 200) || undefined),\n});\n\nconst attentionActionPathSchema = z\n .object({\n id: uuidSchema,\n kind: attentionKindSchema,\n sourceId: z.string().min(1).max(256),\n occurrence: z\n .string()\n .regex(/^(0|[1-9]\\d*)$/)\n .transform((value, context) => {\n const occurrence = Number(value);\n if (Number.isSafeInteger(occurrence)) return occurrence;\n context.addIssue({ code: 'custom', message: 'Invalid occurrence' });\n return z.NEVER;\n }),\n })\n .refine(input => new RegExp(UUID_PATTERN).test(input.sourceId), {\n message: 'Invalid attention source id',\n path: ['sourceId'],\n });\n\nexport const FACTORY_ROUTE_CONTRACTS = {\n projectList: {\n method: 'GET',\n path: '/web/factory/projects',\n description: 'List Factory projects for the current organization',\n responseSchema: z.object({ projects: z.array(projectSchema) }),\n },\n projectCreate: {\n method: 'POST',\n path: '/web/factory/projects',\n description: 'Create a Factory project',\n bodySchema: createProjectBodySchema,\n responseSchema: projectResponseSchema,\n },\n projectGet: {\n method: 'GET',\n path: '/web/factory/projects/:id',\n description: 'Get a Factory project',\n pathSchema: projectPathSchema,\n responseSchema: projectResponseSchema,\n },\n projectUpdate: {\n method: 'PATCH',\n path: '/web/factory/projects/:id',\n description: 'Update a Factory project',\n pathSchema: projectPathSchema,\n bodySchema: updateProjectBodySchema,\n responseSchema: projectResponseSchema,\n },\n projectEnvironmentGet: {\n method: 'GET',\n path: '/web/factory/projects/:id/environment',\n description: 'Get the sandbox environment of a Factory project: provider settings and its repositories in order',\n pathSchema: projectPathSchema,\n responseSchema: projectEnvironmentResponseSchema,\n },\n projectEnvironmentUpdate: {\n method: 'PATCH',\n path: '/web/factory/projects/:id/environment',\n description: 'Update the sandbox environment of a Factory project: provider settings, repository order and setup',\n pathSchema: projectPathSchema,\n bodySchema: updateProjectEnvironmentBodySchema,\n responseSchema: projectEnvironmentResponseSchema,\n },\n projectEnvironmentBuild: {\n method: 'POST',\n path: '/web/factory/projects/:id/environment/build',\n description: 'Build the sandbox environment template of a Factory project now',\n pathSchema: projectPathSchema,\n responseSchema: environmentBuildStartResponseSchema,\n },\n projectEnvironmentBuilds: {\n method: 'GET',\n path: '/web/factory/projects/:id/environment/builds',\n description:\n 'List the environment template builds of a Factory project, newest first, when the provider keeps history',\n pathSchema: projectPathSchema,\n responseSchema: environmentBuildsResponseSchema,\n },\n projectEnvironmentBuildGet: {\n method: 'GET',\n path: '/web/factory/projects/:id/environment/builds/:buildId',\n description: 'Get the live status of one environment template build of a Factory project',\n pathSchema: buildPathSchema,\n responseSchema: environmentBuildResponseSchema,\n },\n projectApplyDefaultModel: {\n method: 'POST',\n path: '/web/factory/projects/:id/apply-default-model',\n description: 'Apply the Factory default model to running work and review threads',\n pathSchema: projectPathSchema,\n responseSchema: z.object({\n modelId: z.string(),\n applied: z.array(z.string()),\n skipped: z.array(\n z.object({\n threadId: z.string(),\n reason: z.enum([\n 'not-running',\n 'work-item-missing',\n 'stage-inactive',\n 'thread-missing',\n 'mode-unknown',\n 'apply-failed',\n ]),\n }),\n ),\n }),\n },\n projectDelete: {\n method: 'DELETE',\n path: '/web/factory/projects/:id',\n description: 'Delete a Factory project',\n pathSchema: projectPathSchema,\n responseSchema: z.null(),\n },\n metricsGet: {\n method: 'GET',\n path: '/web/factory/projects/:id/metrics',\n description: 'Get Factory project metrics',\n pathSchema: projectPathSchema,\n querySchema: metricsQuerySchema,\n responseSchema: z.object({ metrics: entitySchema }),\n },\n healthThresholdsGet: {\n method: 'GET',\n path: '/web/factory/projects/:id/health/thresholds',\n description: 'Get queue-health thresholds',\n pathSchema: projectPathSchema,\n responseSchema: z.object({ thresholds: z.array(z.number().finite()) }),\n },\n boardCatalog: {\n method: 'GET',\n path: '/web/factory/projects/:id/boards',\n description: 'List installed Factory boards',\n pathSchema: projectPathSchema,\n responseSchema: z.object({\n boards: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n initialPhase: z.string(),\n phases: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n kind: z.enum(['resting', 'working', 'terminal']),\n role: z.string().optional(),\n transitions: z.array(z.object({ outcome: z.string().nullable(), to: z.string() })),\n }),\n ),\n }),\n ),\n }),\n },\n workItemList: {\n method: 'GET',\n path: '/web/factory/projects/:id/work-items',\n description: 'List Factory work items and running sessions',\n pathSchema: projectPathSchema,\n responseSchema: z.object({\n workItems: z.array(entitySchema),\n runningSessionIds: z.array(z.string()),\n parkedSessionIds: z.array(z.string()),\n }),\n },\n workItemCreate: {\n method: 'POST',\n path: '/web/factory/projects/:id/work-items',\n description: 'Create a work item in Intake',\n pathSchema: projectPathSchema,\n bodySchema: createWorkItemBodySchema,\n responseSchema: workItemResponseSchema,\n },\n workItemUpdate: {\n method: 'PATCH',\n path: '/web/factory/work-items/:id',\n description: 'Update non-stage work-item fields',\n pathSchema: workItemPathSchema,\n bodySchema: updateWorkItemBodySchema,\n responseSchema: workItemResponseSchema,\n },\n workItemDelete: {\n method: 'DELETE',\n path: '/web/factory/work-items/:id',\n description: 'Delete a Factory work item',\n pathSchema: workItemPathSchema,\n responseSchema: z.object({ ok: z.literal(true) }),\n },\n workItemTransition: {\n method: 'POST',\n path: '/web/factory/projects/:id/work-items/:workItemId/transition',\n description: 'Transition a work item with its expected revision',\n pathSchema: transitionPathSchema,\n bodySchema: transitionBodySchema,\n responseSchema: entitySchema,\n },\n workItemStart: {\n method: 'POST',\n path: '/web/factory/projects/:id/runs/start',\n description: 'Explicitly start a Factory work-item run',\n pathSchema: projectPathSchema,\n bodySchema: startWorkItemBodySchema,\n responseSchema: entitySchema,\n },\n workItemAutomationRun: {\n method: 'POST',\n path: '/web/factory/projects/:id/work-items/:workItemId/automation-runs',\n description: 'Enqueue an idempotent deferred skill dispatch for a trusted external orchestrator',\n pathSchema: transitionPathSchema,\n bodySchema: automationRunStartBodySchema,\n responseSchema: entitySchema,\n },\n decisionList: {\n method: 'GET',\n path: '/web/factory/projects/:id/decisions',\n description: 'List Factory decisions',\n pathSchema: projectPathSchema,\n querySchema: decisionQuerySchema,\n responseSchema: z.object({ decisions: z.array(entitySchema), nextCursor: z.string().optional() }),\n },\n decisionApprove: {\n method: 'POST',\n path: '/web/factory/projects/:id/decisions/:decisionId/approve',\n description: 'Approve a proposed Factory decision',\n pathSchema: projectDecisionPathSchema,\n responseSchema: decisionResponseSchema,\n },\n decisionDismiss: {\n method: 'POST',\n path: '/web/factory/projects/:id/decisions/:decisionId/dismiss',\n description: 'Dismiss a proposed Factory decision',\n pathSchema: projectDecisionPathSchema,\n responseSchema: decisionResponseSchema,\n },\n decisionRetry: {\n method: 'POST',\n path: '/web/factory/projects/:id/decisions/:decisionId/retry',\n description: 'Retry a failed retryable Factory decision',\n pathSchema: projectDecisionPathSchema,\n responseSchema: decisionResponseSchema,\n },\n attentionList: {\n method: 'GET',\n path: '/web/factory/projects/:id/attention',\n description: 'List the Factory attention inbox',\n pathSchema: projectPathSchema,\n querySchema: attentionQuerySchema,\n responseSchema: z.object({\n items: z.array(entitySchema),\n kinds: z.record(\n attentionKindSchema,\n z.object({\n open: z.number(),\n unread: z.number(),\n latest: z.object({ key: z.string(), at: z.string(), unread: z.boolean() }).nullable(),\n }),\n ),\n hasMore: z.boolean(),\n nextCursor: z.string().optional(),\n }),\n },\n attentionReadAll: {\n method: 'POST',\n path: '/web/factory/projects/:id/attention/read-all',\n description: 'Mark all Factory attention as read',\n pathSchema: projectPathSchema,\n querySchema: z.object({ before: attentionCursorSchema }),\n responseSchema: z.object({ ok: z.literal(true), hasMore: z.boolean(), nextCursor: z.string().optional() }),\n },\n attentionRead: {\n method: 'POST',\n path: '/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/read',\n description: 'Mark a Factory attention receipt as read',\n pathSchema: attentionActionPathSchema,\n responseSchema: z.object({ receipt: entitySchema }),\n },\n attentionArchive: {\n method: 'POST',\n path: '/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/archive',\n description: 'Archive a Factory attention receipt',\n pathSchema: attentionActionPathSchema,\n responseSchema: z.object({ receipt: entitySchema }),\n },\n attentionRestore: {\n method: 'POST',\n path: '/web/factory/projects/:id/attention/:kind/:sourceId/:occurrence/restore',\n description: 'Restore an archived Factory attention receipt',\n pathSchema: attentionActionPathSchema,\n responseSchema: z.object({ receipt: entitySchema }),\n },\n} as const satisfies Record<string, FactoryRouteContract>;\n\nexport type FactoryRouteContractKey = keyof typeof FACTORY_ROUTE_CONTRACTS;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"custom-provider-source.js","names":["#orgId","#storage","#fetchedAt","#hydrating","#hydrate","#snapshot"],"sources":["../../src/routes/custom-provider-source.ts"],"sourcesContent":["/**\n * DB-backed custom providers source for model resolution (factory mode).\n *\n * The SDK's `resolveModel` and gateway catalog ask the registered\n * `CustomProvidersSource` synchronously, so this module keeps a small per-org\n * **snapshot** of custom provider rows hydrated from the `custom-providers`\n * domain. Once registered the source is authoritative — the factory never\n * reads `settings.json` custom providers, in either auth mode:\n *\n * - tenant mode: rows are scoped per org (`tenantOrgId`); calls without an\n * authenticated tenant (boot-time catalog) resolve to an empty list.\n * - no-auth/local mode: rows live under the sentinel `local` org, matching the\n * custom provider routes.\n *\n * Snapshots are primed per request by `createCustomProvidersPrimer` (mounted\n * after the web auth gate) and invalidated on writes via\n * `invalidateCustomProvidersSnapshots` so changes are visible immediately.\n */\n\nimport { resolveTenantFromRequestContext } from '@mastra/code-sdk/agents/credential-resolver';\nimport { setCustomProvidersSource } from '@mastra/code-sdk/agents/custom-provider-source';\nimport type { MastraCodeCustomProvider } from '@mastra/code-sdk/agents/mastracode-gateway';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { MiddlewareHandler } from 'hono';\n\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport { tenantOrgId } from './provider-credentials.js';\nimport type { RouteAuth } from './route.js';\n\n/** How long a hydrated snapshot is considered fresh. */\nconst SNAPSHOT_TTL_MS = 15_000;\n\n/** Cap on cached org snapshots; oldest-inserted evicted beyond this. */\nconst MAX_CACHED_ORGS = 1000;\n\n/** Sentinel org for no-auth mode — must match the custom provider routes. */\nconst LOCAL_ORG = 'local';\n\nclass OrgCustomProvidersSnapshot {\n readonly #orgId: string;\n readonly #storage: CustomProvidersStorage;\n #snapshot: MastraCodeCustomProvider[] = [];\n #fetchedAt = 0;\n #hydrating: Promise<void> | undefined;\n\n constructor(orgId: string, storage: CustomProvidersStorage) {\n this.#orgId = orgId;\n this.#storage = storage;\n }\n\n /** Hydrate the snapshot when stale; coalesces concurrent callers. */\n async ensureFresh(now = Date.now()): Promise<void> {\n if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;\n this.#hydrating ??= this.#hydrate().finally(() => {\n this.#hydrating = undefined;\n });\n await this.#hydrating;\n }\n\n async #hydrate(): Promise<void> {\n await this.#storage.ensureReady();\n const records = await this.#storage.list({ orgId: this.#orgId });\n this.#snapshot = records.map(record => ({\n name: record.name,\n url: record.url,\n apiKey: record.apiKey ?? undefined,\n models: record.models,\n }));\n this.#fetchedAt = Date.now();\n }\n\n /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */\n get(): MastraCodeCustomProvider[] {\n if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {\n void this.ensureFresh().catch(() => {});\n }\n return this.#snapshot;\n }\n}\n\nconst orgSnapshots = new Map<string, OrgCustomProvidersSnapshot>();\n\nlet registered: { storage: CustomProvidersStorage; authEnabled: boolean } | undefined;\n\nfunction snapshotFor(orgId: string, storage: CustomProvidersStorage): OrgCustomProvidersSnapshot {\n let snapshot = orgSnapshots.get(orgId);\n if (!snapshot) {\n if (orgSnapshots.size >= MAX_CACHED_ORGS) {\n const oldest = orgSnapshots.keys().next().value;\n if (oldest !== undefined) orgSnapshots.delete(oldest);\n }\n snapshot = new OrgCustomProvidersSnapshot(orgId, storage);\n orgSnapshots.set(orgId, snapshot);\n }\n return snapshot;\n}\n\n/** The org a call resolves to, or `undefined` to fail closed (tenant mode without identity). */\nfunction orgForTenant(\n tenant: { orgId?: string; userId: string } | undefined,\n authEnabled: boolean,\n): string | undefined {\n if (!authEnabled) return LOCAL_ORG;\n return tenant ? tenantOrgId(tenant) : undefined;\n}\n\n/**\n * Register the DB-backed custom providers source with the SDK. Called by the\n * factory after storage init with the `custom-providers` domain handle; from\n * then on model resolution and the gateway catalog never read settings.json\n * custom providers.\n */\nexport function registerCustomProvidersSource({\n storage,\n authEnabled,\n}: {\n storage: CustomProvidersStorage;\n authEnabled: boolean;\n}): void {\n registered = { storage, authEnabled };\n setCustomProvidersSource(tenant => {\n const orgId = orgForTenant(tenant, authEnabled);\n if (!orgId) return [];\n return snapshotFor(orgId, storage).get();\n });\n}\n\n/**\n * Drop the org's cached snapshot after a custom provider write so the change\n * is visible to the next model call immediately instead of after the TTL.\n */\nexport function invalidateCustomProvidersSnapshots(tenant: { orgId: string }): void {\n orgSnapshots.delete(tenant.orgId);\n}\n\n/** Test hook: clear registration and cached org snapshots. */\nexport function resetCustomProvidersSourceForTests(): void {\n setCustomProvidersSource(undefined);\n registered = undefined;\n orgSnapshots.clear();\n}\n\n/**\n * Middleware mounted after the web auth gate: primes the caller's org snapshot\n * so the request's first model call sees their custom providers without an\n * async seam in model resolution. Cheap when fresh (TTL check), best-effort\n * when not — a failed hydrate serves the last snapshot, never blocks a request.\n */\nexport function createCustomProvidersPrimer({\n auth,\n storage,\n authEnabled,\n}: {\n auth: RouteAuth;\n storage: CustomProvidersStorage;\n authEnabled: boolean;\n}): MiddlewareHandler {\n return async (c, next) => {\n const orgId = orgForTenant(auth.tenant(c), authEnabled);\n if (orgId) {\n try {\n await snapshotFor(orgId, storage).ensureFresh();\n } catch {\n // Fail open: model calls serve the last snapshot (or none).\n }\n }\n await next();\n };\n}\n\n/**\n * Prime the org snapshot for a run that has no inbound request (a notification\n * or peer-signal wake), so its first model call can resolve a custom-provider\n * model. Best-effort like the request primer: a failed hydrate serves the last\n * snapshot.\n */\nexport async function primeCustomProvidersForRequestContext(requestContext: RequestContext): Promise<void> {\n if (!registered) return;\n const orgId = orgForTenant(resolveTenantFromRequestContext(requestContext), registered.authEnabled);\n if (!orgId) return;\n try {\n await snapshotFor(orgId, registered.storage).ensureFresh();\n } catch {\n // Fail open: model calls serve the last snapshot (or none).\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAM,kBAAkB;;AAGxB,MAAM,kBAAkB;;AAGxB,MAAM,YAAY;AAElB,IAAM,6BAAN,MAAiC;CAC/B;CACA;CACA,YAAwC,CAAC;CACzC,aAAa;CACb;CAEA,YAAY,OAAe,SAAiC;EAC1D,KAAKA,SAAS;EACd,KAAKC,WAAW;CAClB;;CAGA,MAAM,YAAY,MAAM,KAAK,IAAI,GAAkB;EACjD,IAAI,MAAM,KAAKC,aAAa,iBAAiB;EAC7C,KAAKC,eAAe,KAAKC,SAAS,CAAC,CAAC,cAAc;GAChD,KAAKD,aAAa,KAAA;EACpB,CAAC;EACD,MAAM,KAAKA;CACb;CAEA,MAAMC,WAA0B;EAC9B,MAAM,KAAKH,SAAS,YAAY;EAChC,MAAM,UAAU,MAAM,KAAKA,SAAS,KAAK,EAAE,OAAO,KAAKD,OAAO,CAAC;EAC/D,KAAKK,YAAY,QAAQ,KAAI,YAAW;GACtC,MAAM,OAAO;GACb,KAAK,OAAO;GACZ,QAAQ,OAAO,UAAU,KAAA;GACzB,QAAQ,OAAO;EACjB,EAAE;EACF,KAAKH,aAAa,KAAK,IAAI;CAC7B;;CAGA,MAAkC;EAChC,IAAI,KAAK,IAAI,IAAI,KAAKA,cAAc,iBAClC,KAAU,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC;EAExC,OAAO,KAAKG;CACd;AACF;AAEA,MAAM,+BAAe,IAAI,IAAwC;AAEjE,IAAI;AAEJ,SAAS,YAAY,OAAe,SAA6D;CAC/F,IAAI,WAAW,aAAa,IAAI,KAAK;CACrC,IAAI,CAAC,UAAU;EACb,IAAI,aAAa,QAAQ,iBAAiB;GACxC,MAAM,SAAS,aAAa,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GAC1C,IAAI,WAAW,KAAA,GAAW,aAAa,OAAO,MAAM;EACtD;EACA,WAAW,IAAI,2BAA2B,OAAO,OAAO;EACxD,aAAa,IAAI,OAAO,QAAQ;CAClC;CACA,OAAO;AACT;;AAGA,SAAS,aACP,QACA,aACoB;CACpB,IAAI,CAAC,aAAa,OAAO;CACzB,OAAO,SAAS,YAAY,MAAM,IAAI,KAAA;AACxC;;;;;;;AAQA,SAAgB,8BAA8B,EAC5C,SACA,eAIO;CACP,aAAa;EAAE;EAAS;CAAY;CACpC,0BAAyB,WAAU;EACjC,MAAM,QAAQ,aAAa,QAAQ,WAAW;EAC9C,IAAI,CAAC,OAAO,OAAO,CAAC;EACpB,OAAO,YAAY,OAAO,OAAO,CAAC,CAAC,IAAI;CACzC,CAAC;AACH;;;;;AAMA,SAAgB,mCAAmC,QAAiC;CAClF,aAAa,OAAO,OAAO,KAAK;AAClC;;AAGA,SAAgB,qCAA2C;CACzD,yBAAyB,KAAA,CAAS;CAClC,aAAa,KAAA;CACb,aAAa,MAAM;AACrB;;;;;;;AAQA,SAAgB,4BAA4B,EAC1C,MACA,SACA,eAKoB;CACpB,OAAO,OAAO,GAAG,SAAS;EACxB,MAAM,QAAQ,aAAa,KAAK,OAAO,CAAC,GAAG,WAAW;EACtD,IAAI,OACF,IAAI;GACF,MAAM,YAAY,OAAO,OAAO,CAAC,CAAC,YAAY;EAChD,QAAQ,CAER;EAEF,MAAM,KAAK;CACb;AACF;;;;;;;AAQA,eAAsB,sCAAsC,gBAA+C;CACzG,IAAI,CAAC,YAAY;CACjB,MAAM,QAAQ,aAAa,gCAAgC,cAAc,GAAG,WAAW,WAAW;CAClG,IAAI,CAAC,OAAO;CACZ,IAAI;EACF,MAAM,YAAY,OAAO,WAAW,OAAO,CAAC,CAAC,YAAY;CAC3D,QAAQ,CAER;AACF"}
1
+ {"version":3,"file":"custom-provider-source.js","names":[],"sources":["../../src/routes/custom-provider-source.ts"],"sourcesContent":["/**\n * DB-backed custom providers source for model resolution (factory mode).\n *\n * The SDK's `resolveModel` and gateway catalog ask the registered\n * `CustomProvidersSource` synchronously, so this module keeps a small per-org\n * **snapshot** of custom provider rows hydrated from the `custom-providers`\n * domain. Once registered the source is authoritative — the factory never\n * reads `settings.json` custom providers, in either auth mode:\n *\n * - tenant mode: rows are scoped per org (`tenantOrgId`); calls without an\n * authenticated tenant (boot-time catalog) resolve to an empty list.\n * - no-auth/local mode: rows live under the sentinel `local` org, matching the\n * custom provider routes.\n *\n * Snapshots are primed per request by `createCustomProvidersPrimer` (mounted\n * after the web auth gate) and invalidated on writes via\n * `invalidateCustomProvidersSnapshots` so changes are visible immediately.\n */\n\nimport { resolveTenantFromRequestContext } from '@mastra/code-sdk/agents/credential-resolver';\nimport { setCustomProvidersSource } from '@mastra/code-sdk/agents/custom-provider-source';\nimport type { MastraCodeCustomProvider } from '@mastra/code-sdk/agents/mastracode-gateway';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { MiddlewareHandler } from 'hono';\n\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport { tenantOrgId } from './provider-credentials.js';\nimport type { RouteAuth } from './route.js';\n\n/** How long a hydrated snapshot is considered fresh. */\nconst SNAPSHOT_TTL_MS = 15_000;\n\n/** Cap on cached org snapshots; oldest-inserted evicted beyond this. */\nconst MAX_CACHED_ORGS = 1000;\n\n/** Sentinel org for no-auth mode — must match the custom provider routes. */\nconst LOCAL_ORG = 'local';\n\nclass OrgCustomProvidersSnapshot {\n readonly #orgId: string;\n readonly #storage: CustomProvidersStorage;\n #snapshot: MastraCodeCustomProvider[] = [];\n #fetchedAt = 0;\n #hydrating: Promise<void> | undefined;\n\n constructor(orgId: string, storage: CustomProvidersStorage) {\n this.#orgId = orgId;\n this.#storage = storage;\n }\n\n /** Hydrate the snapshot when stale; coalesces concurrent callers. */\n async ensureFresh(now = Date.now()): Promise<void> {\n if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;\n this.#hydrating ??= this.#hydrate().finally(() => {\n this.#hydrating = undefined;\n });\n await this.#hydrating;\n }\n\n async #hydrate(): Promise<void> {\n await this.#storage.ensureReady();\n const records = await this.#storage.list({ orgId: this.#orgId });\n this.#snapshot = records.map(record => ({\n name: record.name,\n url: record.url,\n apiKey: record.apiKey ?? undefined,\n models: record.models,\n }));\n this.#fetchedAt = Date.now();\n }\n\n /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */\n get(): MastraCodeCustomProvider[] {\n if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {\n void this.ensureFresh().catch(() => {});\n }\n return this.#snapshot;\n }\n}\n\nconst orgSnapshots = new Map<string, OrgCustomProvidersSnapshot>();\n\nlet registered: { storage: CustomProvidersStorage; authEnabled: boolean } | undefined;\n\nfunction snapshotFor(orgId: string, storage: CustomProvidersStorage): OrgCustomProvidersSnapshot {\n let snapshot = orgSnapshots.get(orgId);\n if (!snapshot) {\n if (orgSnapshots.size >= MAX_CACHED_ORGS) {\n const oldest = orgSnapshots.keys().next().value;\n if (oldest !== undefined) orgSnapshots.delete(oldest);\n }\n snapshot = new OrgCustomProvidersSnapshot(orgId, storage);\n orgSnapshots.set(orgId, snapshot);\n }\n return snapshot;\n}\n\n/** The org a call resolves to, or `undefined` to fail closed (tenant mode without identity). */\nfunction orgForTenant(\n tenant: { orgId?: string; userId: string } | undefined,\n authEnabled: boolean,\n): string | undefined {\n if (!authEnabled) return LOCAL_ORG;\n return tenant ? tenantOrgId(tenant) : undefined;\n}\n\n/**\n * Register the DB-backed custom providers source with the SDK. Called by the\n * factory after storage init with the `custom-providers` domain handle; from\n * then on model resolution and the gateway catalog never read settings.json\n * custom providers.\n */\nexport function registerCustomProvidersSource({\n storage,\n authEnabled,\n}: {\n storage: CustomProvidersStorage;\n authEnabled: boolean;\n}): void {\n registered = { storage, authEnabled };\n setCustomProvidersSource(tenant => {\n const orgId = orgForTenant(tenant, authEnabled);\n if (!orgId) return [];\n return snapshotFor(orgId, storage).get();\n });\n}\n\n/**\n * Drop the org's cached snapshot after a custom provider write so the change\n * is visible to the next model call immediately instead of after the TTL.\n */\nexport function invalidateCustomProvidersSnapshots(tenant: { orgId: string }): void {\n orgSnapshots.delete(tenant.orgId);\n}\n\n/** Test hook: clear registration and cached org snapshots. */\nexport function resetCustomProvidersSourceForTests(): void {\n setCustomProvidersSource(undefined);\n registered = undefined;\n orgSnapshots.clear();\n}\n\n/**\n * Middleware mounted after the web auth gate: primes the caller's org snapshot\n * so the request's first model call sees their custom providers without an\n * async seam in model resolution. Cheap when fresh (TTL check), best-effort\n * when not — a failed hydrate serves the last snapshot, never blocks a request.\n */\nexport function createCustomProvidersPrimer({\n auth,\n storage,\n authEnabled,\n}: {\n auth: RouteAuth;\n storage: CustomProvidersStorage;\n authEnabled: boolean;\n}): MiddlewareHandler {\n return async (c, next) => {\n const orgId = orgForTenant(auth.tenant(c), authEnabled);\n if (orgId) {\n try {\n await snapshotFor(orgId, storage).ensureFresh();\n } catch {\n // Fail open: model calls serve the last snapshot (or none).\n }\n }\n await next();\n };\n}\n\n/**\n * Prime the org snapshot for a run that has no inbound request (a notification\n * or peer-signal wake), so its first model call can resolve a custom-provider\n * model. Best-effort like the request primer: a failed hydrate serves the last\n * snapshot.\n */\nexport async function primeCustomProvidersForRequestContext(requestContext: RequestContext): Promise<void> {\n if (!registered) return;\n const orgId = orgForTenant(resolveTenantFromRequestContext(requestContext), registered.authEnabled);\n if (!orgId) return;\n try {\n await snapshotFor(orgId, registered.storage).ensureFresh();\n } catch {\n // Fail open: model calls serve the last snapshot (or none).\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAM,kBAAkB;;AAGxB,MAAM,kBAAkB;;AAGxB,MAAM,YAAY;AAElB,IAAM,6BAAN,MAAiC;CAC/B;CACA;CACA,YAAwC,CAAC;CACzC,aAAa;CACb;CAEA,YAAY,OAAe,SAAiC;EAC1D,KAAK,SAAS;EACd,KAAK,WAAW;CAClB;;CAGA,MAAM,YAAY,MAAM,KAAK,IAAI,GAAkB;EACjD,IAAI,MAAM,KAAK,aAAa,iBAAiB;EAC7C,KAAK,eAAe,KAAK,SAAS,CAAC,CAAC,cAAc;GAChD,KAAK,aAAa,KAAA;EACpB,CAAC;EACD,MAAM,KAAK;CACb;CAEA,MAAM,WAA0B;EAC9B,MAAM,KAAK,SAAS,YAAY;EAChC,MAAM,UAAU,MAAM,KAAK,SAAS,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;EAC/D,KAAK,YAAY,QAAQ,KAAI,YAAW;GACtC,MAAM,OAAO;GACb,KAAK,OAAO;GACZ,QAAQ,OAAO,UAAU,KAAA;GACzB,QAAQ,OAAO;EACjB,EAAE;EACF,KAAK,aAAa,KAAK,IAAI;CAC7B;;CAGA,MAAkC;EAChC,IAAI,KAAK,IAAI,IAAI,KAAK,cAAc,iBAClC,KAAU,YAAY,CAAC,CAAC,YAAY,CAAC,CAAC;EAExC,OAAO,KAAK;CACd;AACF;AAEA,MAAM,+BAAe,IAAI,IAAwC;AAEjE,IAAI;AAEJ,SAAS,YAAY,OAAe,SAA6D;CAC/F,IAAI,WAAW,aAAa,IAAI,KAAK;CACrC,IAAI,CAAC,UAAU;EACb,IAAI,aAAa,QAAQ,iBAAiB;GACxC,MAAM,SAAS,aAAa,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GAC1C,IAAI,WAAW,KAAA,GAAW,aAAa,OAAO,MAAM;EACtD;EACA,WAAW,IAAI,2BAA2B,OAAO,OAAO;EACxD,aAAa,IAAI,OAAO,QAAQ;CAClC;CACA,OAAO;AACT;;AAGA,SAAS,aACP,QACA,aACoB;CACpB,IAAI,CAAC,aAAa,OAAO;CACzB,OAAO,SAAS,YAAY,MAAM,IAAI,KAAA;AACxC;;;;;;;AAQA,SAAgB,8BAA8B,EAC5C,SACA,eAIO;CACP,aAAa;EAAE;EAAS;CAAY;CACpC,0BAAyB,WAAU;EACjC,MAAM,QAAQ,aAAa,QAAQ,WAAW;EAC9C,IAAI,CAAC,OAAO,OAAO,CAAC;EACpB,OAAO,YAAY,OAAO,OAAO,CAAC,CAAC,IAAI;CACzC,CAAC;AACH;;;;;AAMA,SAAgB,mCAAmC,QAAiC;CAClF,aAAa,OAAO,OAAO,KAAK;AAClC;;AAGA,SAAgB,qCAA2C;CACzD,yBAAyB,KAAA,CAAS;CAClC,aAAa,KAAA;CACb,aAAa,MAAM;AACrB;;;;;;;AAQA,SAAgB,4BAA4B,EAC1C,MACA,SACA,eAKoB;CACpB,OAAO,OAAO,GAAG,SAAS;EACxB,MAAM,QAAQ,aAAa,KAAK,OAAO,CAAC,GAAG,WAAW;EACtD,IAAI,OACF,IAAI;GACF,MAAM,YAAY,OAAO,OAAO,CAAC,CAAC,YAAY;EAChD,QAAQ,CAER;EAEF,MAAM,KAAK;CACb;AACF;;;;;;;AAQA,eAAsB,sCAAsC,gBAA+C;CACzG,IAAI,CAAC,YAAY;CACjB,MAAM,QAAQ,aAAa,gCAAgC,cAAc,GAAG,WAAW,WAAW;CAClG,IAAI,CAAC,OAAO;CACZ,IAAI;EACF,MAAM,YAAY,OAAO,WAAW,OAAO,CAAC,CAAC,YAAY;CAC3D,QAAQ,CAER;AACF"}