@mastra/factory 0.24.0-alpha.9 → 0.25.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/README.md +40 -13
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +18 -4
  4. package/dist/auth.js.map +1 -1
  5. package/dist/boards/review.d.ts.map +1 -1
  6. package/dist/boards/review.js +3 -2
  7. package/dist/boards/review.js.map +1 -1
  8. package/dist/environment/build-runner.js +2 -1
  9. package/dist/environment/build-runner.js.map +1 -1
  10. package/dist/environment/build-workflow.js +1 -1
  11. package/dist/environment/build-workflow.js.map +1 -1
  12. package/dist/environment/build.js +16 -14
  13. package/dist/environment/build.js.map +1 -1
  14. package/dist/factory.d.ts +3 -0
  15. package/dist/factory.d.ts.map +1 -1
  16. package/dist/factory.js +172 -88
  17. package/dist/factory.js.map +1 -1
  18. package/dist/integrations/base.d.ts +15 -2
  19. package/dist/integrations/base.d.ts.map +1 -1
  20. package/dist/integrations/github/app-identity.js.map +1 -1
  21. package/dist/integrations/github/integration.js +1 -1
  22. package/dist/integrations/github/integration.js.map +1 -1
  23. package/dist/integrations/github/reconcile-worker.js +1 -1
  24. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  25. package/dist/integrations/github/routes.d.ts +1 -1
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +24 -13
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/rules.d.ts.map +1 -1
  30. package/dist/integrations/github/rules.js +53 -9
  31. package/dist/integrations/github/rules.js.map +1 -1
  32. package/dist/integrations/github/sandbox.js +2 -2
  33. package/dist/integrations/github/sandbox.js.map +1 -1
  34. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  35. package/dist/integrations/github/session-subscriptions.js +1 -2
  36. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  37. package/dist/integrations/github/subscriptions.d.ts +7 -4
  38. package/dist/integrations/github/subscriptions.d.ts.map +1 -1
  39. package/dist/integrations/github/subscriptions.js +5 -5
  40. package/dist/integrations/github/subscriptions.js.map +1 -1
  41. package/dist/integrations/gitlab/api.js.map +1 -1
  42. package/dist/integrations/gitlab/integration.js.map +1 -1
  43. package/dist/integrations/gitlab/reconciliation-config.js +2 -1
  44. package/dist/integrations/gitlab/reconciliation-config.js.map +1 -1
  45. package/dist/integrations/gitlab/routes.d.ts.map +1 -1
  46. package/dist/integrations/gitlab/routes.js +10 -6
  47. package/dist/integrations/gitlab/routes.js.map +1 -1
  48. package/dist/integrations/gitlab/rules.js.map +1 -1
  49. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
  50. package/dist/integrations/gitlab/session-subscriptions.js +1 -2
  51. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
  52. package/dist/integrations/gitlab/subscriptions.d.ts +7 -4
  53. package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -1
  54. package/dist/integrations/gitlab/subscriptions.js +5 -5
  55. package/dist/integrations/gitlab/subscriptions.js.map +1 -1
  56. package/dist/integrations/incidentio/api.js.map +1 -1
  57. package/dist/integrations/incidentio/integration.js.map +1 -1
  58. package/dist/integrations/incidentio/rules.js.map +1 -1
  59. package/dist/integrations/issue-reconcile-worker.js +1 -1
  60. package/dist/integrations/issue-reconcile-worker.js.map +1 -1
  61. package/dist/integrations/issue-reconciler.js +1 -1
  62. package/dist/integrations/issue-reconciler.js.map +1 -1
  63. package/dist/integrations/jira/api.js.map +1 -1
  64. package/dist/integrations/jira/integration.js.map +1 -1
  65. package/dist/integrations/jira/rules.js.map +1 -1
  66. package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
  67. package/dist/integrations/linear/agent-tools.js.map +1 -1
  68. package/dist/integrations/linear/integration.js.map +1 -1
  69. package/dist/integrations/linear/rules.js.map +1 -1
  70. package/dist/integrations/platform/api-client.js.map +1 -1
  71. package/dist/integrations/platform/github/event-worker.js +1 -1
  72. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  73. package/dist/integrations/platform/github/integration.js +2 -2
  74. package/dist/integrations/platform/github/integration.js.map +1 -1
  75. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
  76. package/dist/integrations/platform/gitlab/integration.js.map +1 -1
  77. package/dist/integrations/platform/incidentio/integration.js +2 -1
  78. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  79. package/dist/integrations/platform/jira/integration.js.map +1 -1
  80. package/dist/integrations/platform/linear/event-worker.js +1 -1
  81. package/dist/integrations/platform/linear/event-worker.js.map +1 -1
  82. package/dist/integrations/platform/linear/integration.js.map +1 -1
  83. package/dist/{session → integrations/slack}/channel-reply-processor.d.ts +11 -9
  84. package/dist/integrations/slack/channel-reply-processor.d.ts.map +1 -0
  85. package/dist/integrations/slack/channel-reply-processor.js +61 -0
  86. package/dist/integrations/slack/channel-reply-processor.js.map +1 -0
  87. package/dist/integrations/slack/channel-session-link-processor.d.ts.map +1 -0
  88. package/dist/{session → integrations/slack}/channel-session-link-processor.js +14 -4
  89. package/dist/integrations/slack/channel-session-link-processor.js.map +1 -0
  90. package/dist/integrations/slack/connect-route.js +1 -1
  91. package/dist/integrations/slack/connect-route.js.map +1 -1
  92. package/dist/integrations/slack/feed-publisher.js +1 -1
  93. package/dist/integrations/slack/feed-publisher.js.map +1 -1
  94. package/dist/integrations/slack/integration.d.ts +25 -1
  95. package/dist/integrations/slack/integration.d.ts.map +1 -1
  96. package/dist/integrations/slack/integration.js +56 -6
  97. package/dist/integrations/slack/integration.js.map +1 -1
  98. package/dist/integrations/slack/reply-reminder-processor.d.ts +23 -0
  99. package/dist/integrations/slack/reply-reminder-processor.d.ts.map +1 -0
  100. package/dist/integrations/slack/reply-reminder-processor.js +58 -0
  101. package/dist/integrations/slack/reply-reminder-processor.js.map +1 -0
  102. package/dist/integrations/slack/reply-tool.d.ts +31 -0
  103. package/dist/integrations/slack/reply-tool.d.ts.map +1 -0
  104. package/dist/integrations/slack/reply-tool.js +203 -0
  105. package/dist/integrations/slack/reply-tool.js.map +1 -0
  106. package/dist/integrations/slack/share-file-tool.d.ts +45 -0
  107. package/dist/integrations/slack/share-file-tool.d.ts.map +1 -0
  108. package/dist/integrations/slack/share-file-tool.js +106 -0
  109. package/dist/integrations/slack/share-file-tool.js.map +1 -0
  110. package/dist/integrations/slack/slack.d.ts +16 -5
  111. package/dist/integrations/slack/slack.d.ts.map +1 -1
  112. package/dist/integrations/slack/slack.js +141 -40
  113. package/dist/integrations/slack/slack.js.map +1 -1
  114. package/dist/integrations/subscription-session.d.ts +0 -1
  115. package/dist/integrations/subscription-session.d.ts.map +1 -1
  116. package/dist/integrations/subscription-session.js +1 -4
  117. package/dist/integrations/subscription-session.js.map +1 -1
  118. package/dist/integrations/workos/integration.js.map +1 -1
  119. package/dist/mcp/events.d.ts +73 -0
  120. package/dist/mcp/events.d.ts.map +1 -0
  121. package/dist/mcp/events.js +158 -0
  122. package/dist/mcp/events.js.map +1 -0
  123. package/dist/mcp/routes.d.ts +0 -1
  124. package/dist/mcp/routes.d.ts.map +1 -1
  125. package/dist/mcp/routes.js +31 -4
  126. package/dist/mcp/routes.js.map +1 -1
  127. package/dist/packages/_internals/workspace/dist/index.js +18 -1
  128. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  129. package/dist/platform-auth-host.d.ts.map +1 -1
  130. package/dist/platform-auth-host.js +1 -1
  131. package/dist/platform-auth-host.js.map +1 -1
  132. package/dist/routes/attention-activity.js.map +1 -1
  133. package/dist/routes/attention-parked.js.map +1 -1
  134. package/dist/routes/attention-providers.js.map +1 -1
  135. package/dist/routes/automation-runs.js.map +1 -1
  136. package/dist/routes/config.d.ts +8 -7
  137. package/dist/routes/config.d.ts.map +1 -1
  138. package/dist/routes/config.js +70 -28
  139. package/dist/routes/config.js.map +1 -1
  140. package/dist/routes/contracts.d.ts +2 -0
  141. package/dist/routes/contracts.d.ts.map +1 -1
  142. package/dist/routes/contracts.js +2 -2
  143. package/dist/routes/contracts.js.map +1 -1
  144. package/dist/routes/custom-provider-source.js.map +1 -1
  145. package/dist/routes/fs.d.ts +31 -23
  146. package/dist/routes/fs.d.ts.map +1 -1
  147. package/dist/routes/fs.js +218 -64
  148. package/dist/routes/fs.js.map +1 -1
  149. package/dist/routes/intake.js +2 -1
  150. package/dist/routes/intake.js.map +1 -1
  151. package/dist/routes/knowledge.js +1 -1
  152. package/dist/routes/knowledge.js.map +1 -1
  153. package/dist/routes/oauth.js +3 -2
  154. package/dist/routes/oauth.js.map +1 -1
  155. package/dist/routes/projects.d.ts +1 -1
  156. package/dist/routes/projects.d.ts.map +1 -1
  157. package/dist/routes/projects.js +33 -31
  158. package/dist/routes/projects.js.map +1 -1
  159. package/dist/routes/provider-credentials.d.ts.map +1 -1
  160. package/dist/routes/provider-credentials.js +6 -1
  161. package/dist/routes/provider-credentials.js.map +1 -1
  162. package/dist/routes/session-access.d.ts +60 -0
  163. package/dist/routes/session-access.d.ts.map +1 -0
  164. package/dist/routes/session-access.js +202 -0
  165. package/dist/routes/session-access.js.map +1 -0
  166. package/dist/routes/skills.d.ts +2 -4
  167. package/dist/routes/skills.d.ts.map +1 -1
  168. package/dist/routes/skills.js +23 -68
  169. package/dist/routes/skills.js.map +1 -1
  170. package/dist/routes/source-control-sessions.d.ts +8 -1
  171. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  172. package/dist/routes/source-control-sessions.js +239 -19
  173. package/dist/routes/source-control-sessions.js.map +1 -1
  174. package/dist/routes/surface.d.ts +7 -0
  175. package/dist/routes/surface.d.ts.map +1 -1
  176. package/dist/routes/surface.js +20 -8
  177. package/dist/routes/surface.js.map +1 -1
  178. package/dist/routes/tenant-credentials.js.map +1 -1
  179. package/dist/routes/work-items.js.map +1 -1
  180. package/dist/rules/binding-context.js +11 -9
  181. package/dist/rules/binding-context.js.map +1 -1
  182. package/dist/rules/dispatcher.d.ts +1 -1
  183. package/dist/rules/dispatcher.d.ts.map +1 -1
  184. package/dist/rules/dispatcher.js +21 -13
  185. package/dist/rules/dispatcher.js.map +1 -1
  186. package/dist/rules/processor.d.ts +2 -2
  187. package/dist/rules/processor.d.ts.map +1 -1
  188. package/dist/rules/processor.js +3 -3
  189. package/dist/rules/processor.js.map +1 -1
  190. package/dist/rules/start-coordinator.d.ts +4 -0
  191. package/dist/rules/start-coordinator.d.ts.map +1 -1
  192. package/dist/rules/start-coordinator.js +36 -4
  193. package/dist/rules/start-coordinator.js.map +1 -1
  194. package/dist/rules/tools.js +2 -2
  195. package/dist/rules/tools.js.map +1 -1
  196. package/dist/rules/transition-service.js.map +1 -1
  197. package/dist/sandbox/callback-factory-sandbox.js.map +1 -1
  198. package/dist/sandbox/local-factory-sandbox.js.map +1 -1
  199. package/dist/sandbox/session-retirement.d.ts +11 -0
  200. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  201. package/dist/sandbox/session-retirement.js +38 -1
  202. package/dist/sandbox/session-retirement.js.map +1 -1
  203. package/dist/sandbox/session-sandbox.d.ts +2 -0
  204. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  205. package/dist/sandbox/session-sandbox.js +5 -1
  206. package/dist/sandbox/session-sandbox.js.map +1 -1
  207. package/dist/secret-encryption.d.ts.map +1 -1
  208. package/dist/secret-encryption.js +65 -25
  209. package/dist/secret-encryption.js.map +1 -1
  210. package/dist/session/credential-policy.d.ts +4 -2
  211. package/dist/session/credential-policy.d.ts.map +1 -1
  212. package/dist/session/credential-policy.js +18 -7
  213. package/dist/session/credential-policy.js.map +1 -1
  214. package/dist/session/default-model-hydration.d.ts +1 -0
  215. package/dist/session/default-model-hydration.d.ts.map +1 -1
  216. package/dist/session/default-model-hydration.js +3 -2
  217. package/dist/session/default-model-hydration.js.map +1 -1
  218. package/dist/session/environment-state-processor.js +1 -1
  219. package/dist/session/environment-state-processor.js.map +1 -1
  220. package/dist/session/factory-session.d.ts.map +1 -1
  221. package/dist/session/factory-session.js +14 -1
  222. package/dist/session/factory-session.js.map +1 -1
  223. package/dist/session/filesystem-capture.d.ts +3 -3
  224. package/dist/session/filesystem-capture.d.ts.map +1 -1
  225. package/dist/session/filesystem-capture.js +16 -17
  226. package/dist/session/filesystem-capture.js.map +1 -1
  227. package/dist/session/first-exec-capture.d.ts +1 -1
  228. package/dist/session/first-exec-capture.d.ts.map +1 -1
  229. package/dist/session/first-exec-capture.js +1 -1
  230. package/dist/session/first-exec-capture.js.map +1 -1
  231. package/dist/session/first-message-capture.d.ts +1 -1
  232. package/dist/session/first-message-capture.d.ts.map +1 -1
  233. package/dist/session/first-message-capture.js +1 -1
  234. package/dist/session/first-message-capture.js.map +1 -1
  235. package/dist/session/live-sessions.d.ts +1 -1
  236. package/dist/session/live-sessions.js +1 -1
  237. package/dist/session/live-sessions.js.map +1 -1
  238. package/dist/session/memory-settings-hydration.d.ts +1 -1
  239. package/dist/session/memory-settings-hydration.d.ts.map +1 -1
  240. package/dist/session/memory-settings-hydration.js +2 -1
  241. package/dist/session/memory-settings-hydration.js.map +1 -1
  242. package/dist/session/resource-migration.d.ts +105 -0
  243. package/dist/session/resource-migration.d.ts.map +1 -0
  244. package/dist/session/resource-migration.js +195 -0
  245. package/dist/session/resource-migration.js.map +1 -0
  246. package/dist/session/run-audit.d.ts +1 -0
  247. package/dist/session/run-audit.d.ts.map +1 -1
  248. package/dist/session/run-audit.js +7 -2
  249. package/dist/session/run-audit.js.map +1 -1
  250. package/dist/session/session-threads.d.ts +63 -0
  251. package/dist/session/session-threads.d.ts.map +1 -0
  252. package/dist/session/session-threads.js +97 -0
  253. package/dist/session/session-threads.js.map +1 -0
  254. package/dist/session/source-control-tools.js +8 -8
  255. package/dist/session/source-control-tools.js.map +1 -1
  256. package/dist/session/thinking-default.d.ts +6 -13
  257. package/dist/session/thinking-default.d.ts.map +1 -1
  258. package/dist/session/thinking-default.js +19 -24
  259. package/dist/session/thinking-default.js.map +1 -1
  260. package/dist/session/thread-session-stamp.d.ts +21 -0
  261. package/dist/session/thread-session-stamp.d.ts.map +1 -0
  262. package/dist/session/thread-session-stamp.js +20 -0
  263. package/dist/session/thread-session-stamp.js.map +1 -0
  264. package/dist/session/thread-title-mirror.d.ts +1 -1
  265. package/dist/session/thread-title-mirror.d.ts.map +1 -1
  266. package/dist/session/thread-title-mirror.js +1 -1
  267. package/dist/session/thread-title-mirror.js.map +1 -1
  268. package/dist/session-owner-signal.d.ts +2 -0
  269. package/dist/session-owner-signal.d.ts.map +1 -0
  270. package/dist/session-owner-signal.js +6 -0
  271. package/dist/session-owner-signal.js.map +1 -0
  272. package/dist/skills/service.d.ts +11 -14
  273. package/dist/skills/service.d.ts.map +1 -1
  274. package/dist/skills/service.js +12 -22
  275. package/dist/skills/service.js.map +1 -1
  276. package/dist/spa-static.js.map +1 -1
  277. package/dist/state-signing.js +1 -1
  278. package/dist/state-signing.js.map +1 -1
  279. package/dist/storage/domains/audit/actions.d.ts +1 -0
  280. package/dist/storage/domains/audit/actions.d.ts.map +1 -1
  281. package/dist/storage/domains/audit/actions.js +2 -1
  282. package/dist/storage/domains/audit/actions.js.map +1 -1
  283. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
  284. package/dist/storage/domains/audit/agent-audit.js +2 -2
  285. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  286. package/dist/storage/domains/audit/base.d.ts +18 -2
  287. package/dist/storage/domains/audit/base.d.ts.map +1 -1
  288. package/dist/storage/domains/audit/base.js +22 -4
  289. package/dist/storage/domains/audit/base.js.map +1 -1
  290. package/dist/storage/domains/audit/domain.js.map +1 -1
  291. package/dist/storage/domains/channel-identity/base.js.map +1 -1
  292. package/dist/storage/domains/comments/base.js.map +1 -1
  293. package/dist/storage/domains/comments/domain.js.map +1 -1
  294. package/dist/storage/domains/comments/feed-context.js.map +1 -1
  295. package/dist/storage/domains/credentials/base.js.map +1 -1
  296. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  297. package/dist/storage/domains/intake/base.js.map +1 -1
  298. package/dist/storage/domains/integrations/base.d.ts +8 -0
  299. package/dist/storage/domains/integrations/base.d.ts.map +1 -1
  300. package/dist/storage/domains/integrations/base.js +7 -0
  301. package/dist/storage/domains/integrations/base.js.map +1 -1
  302. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  303. package/dist/storage/domains/model-defaults/base.d.ts +8 -0
  304. package/dist/storage/domains/model-defaults/base.d.ts.map +1 -1
  305. package/dist/storage/domains/model-defaults/base.js +18 -0
  306. package/dist/storage/domains/model-defaults/base.js.map +1 -1
  307. package/dist/storage/domains/projects/base.js.map +1 -1
  308. package/dist/storage/domains/queue-health/base.js.map +1 -1
  309. package/dist/storage/domains/source-control/base.d.ts +44 -0
  310. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  311. package/dist/storage/domains/source-control/base.js +109 -3
  312. package/dist/storage/domains/source-control/base.js.map +1 -1
  313. package/dist/storage/domains/source-control/inmemory.d.ts +23 -1
  314. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  315. package/dist/storage/domains/source-control/inmemory.js +35 -0
  316. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  317. package/dist/storage/domains/work-items/base.d.ts +14 -0
  318. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  319. package/dist/storage/domains/work-items/base.js +25 -1
  320. package/dist/storage/domains/work-items/base.js.map +1 -1
  321. package/dist/storage/domains/work-items/board-version.js.map +1 -1
  322. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  323. package/dist/tools/approval-tools.js +1 -1
  324. package/dist/tools/approval-tools.js.map +1 -1
  325. package/dist/tools/deliver.d.ts +8 -5
  326. package/dist/tools/deliver.d.ts.map +1 -1
  327. package/dist/tools/deliver.js +22 -8
  328. package/dist/tools/deliver.js.map +1 -1
  329. package/dist/tools/messaging-tools.d.ts +1 -1
  330. package/dist/tools/messaging-tools.d.ts.map +1 -1
  331. package/dist/tools/messaging-tools.js +203 -72
  332. package/dist/tools/messaging-tools.js.map +1 -1
  333. package/dist/tools/permissions.d.ts +1 -1
  334. package/dist/tools/permissions.d.ts.map +1 -1
  335. package/dist/tools/permissions.js +3 -2
  336. package/dist/tools/permissions.js.map +1 -1
  337. package/dist/tools/reports.d.ts.map +1 -1
  338. package/dist/tools/reports.js +10 -3
  339. package/dist/tools/reports.js.map +1 -1
  340. package/dist/tools/run-watch.d.ts +2 -0
  341. package/dist/tools/run-watch.d.ts.map +1 -1
  342. package/dist/tools/run-watch.js +42 -2
  343. package/dist/tools/run-watch.js.map +1 -1
  344. package/dist/tools/sandbox-tools.d.ts +12 -0
  345. package/dist/tools/sandbox-tools.d.ts.map +1 -0
  346. package/dist/tools/sandbox-tools.js +40 -0
  347. package/dist/tools/sandbox-tools.js.map +1 -0
  348. package/dist/tools/scope.d.ts +2 -1
  349. package/dist/tools/scope.d.ts.map +1 -1
  350. package/dist/tools/scope.js +5 -7
  351. package/dist/tools/scope.js.map +1 -1
  352. package/dist/tools/session-tools.d.ts +1 -1
  353. package/dist/tools/session-tools.d.ts.map +1 -1
  354. package/dist/tools/session-tools.js +39 -18
  355. package/dist/tools/session-tools.js.map +1 -1
  356. package/dist/tools/sessions-state-processor.js +2 -2
  357. package/dist/tools/sessions-state-processor.js.map +1 -1
  358. package/dist/tools/side-sessions.d.ts +1 -1
  359. package/dist/tools/side-sessions.d.ts.map +1 -1
  360. package/dist/tools/side-sessions.js +1 -1
  361. package/dist/tools/side-sessions.js.map +1 -1
  362. package/dist/work-item-branch.d.ts +11 -3
  363. package/dist/work-item-branch.d.ts.map +1 -1
  364. package/dist/work-item-branch.js +34 -9
  365. package/dist/work-item-branch.js.map +1 -1
  366. package/dist/workspace.d.ts +7 -3
  367. package/dist/workspace.d.ts.map +1 -1
  368. package/dist/workspace.js +32 -17
  369. package/dist/workspace.js.map +1 -1
  370. package/factory-skills/configure-factory-rules/SKILL.md +2 -2
  371. package/package.json +16 -15
  372. package/dist/session/channel-reply-processor.d.ts.map +0 -1
  373. package/dist/session/channel-reply-processor.js +0 -46
  374. package/dist/session/channel-reply-processor.js.map +0 -1
  375. package/dist/session/channel-session-link-processor.d.ts.map +0 -1
  376. package/dist/session/channel-session-link-processor.js.map +0 -1
  377. /package/dist/{session → integrations/slack}/channel-session-link-processor.d.ts +0 -0
@@ -22,29 +22,30 @@ async function findSkill(session, name) {
22
22
  }
23
23
  return skills.get(name);
24
24
  }
25
- function escapeSkillBoundary(value) {
26
- return value.replaceAll("</skill>", "&lt;/skill&gt;");
25
+ /** Wraps skill contents in the `<skill>` envelope a human-invoked skill sends as its own message. */
26
+ function formatSkillMessage(name, contents) {
27
+ return `<skill name="${name}">\n${contents.replaceAll("</skill>", "&lt;/skill&gt;")}\n</skill>`;
27
28
  }
28
29
  /** Kicks a run off from a plain prompt, for runs that activate no skill. */
29
30
  async function resolvePromptInvocation(controller, input) {
30
- const session = await controller.getSessionByResource(input.resourceId, input.scope);
31
+ const session = await controller.getSession(input.sessionId);
31
32
  if (!session) throw new SkillInvocationError("session_not_found", "Agent controller session not found.");
32
33
  return {
33
34
  session,
34
- message: input.prompt
35
+ contents: input.prompt
35
36
  };
36
37
  }
37
38
  async function resolveSkillInvocation(controller, input) {
38
- const session = await controller.getSessionByResource(input.resourceId, input.scope);
39
+ const session = await controller.getSession(input.sessionId);
39
40
  if (!session) throw new SkillInvocationError("session_not_found", "Agent controller session not found.");
40
41
  const skill = await findSkill(session, input.name);
41
42
  if (!skill || skill["user-invocable"] === false) throw new SkillInvocationError("skill_not_found", `Skill not found: ${input.name}.`);
42
43
  const args = input.arguments?.trim();
43
- const content = `${formatSkillActivation(skill)}${args ? `\n\nARGUMENTS: ${args}` : ""}`.trim();
44
+ const contents = `${formatSkillActivation(skill)}${args ? `\n\nARGUMENTS: ${args}` : ""}`.trim();
44
45
  return {
45
46
  session,
46
47
  skillName: skill.name,
47
- message: `<skill name="${skill.name}">\n${escapeSkillBoundary(content)}\n</skill>`
48
+ contents
48
49
  };
49
50
  }
50
51
  /**
@@ -55,12 +56,12 @@ async function resolveSkillInvocation(controller, input) {
55
56
  * skill tool.
56
57
  */
57
58
  async function resolveSkillResumeInvocation(controller, input) {
58
- const session = await controller.getSessionByResource(input.resourceId, input.scope);
59
+ const session = await controller.getSession(input.sessionId);
59
60
  if (!session) throw new SkillInvocationError("session_not_found", "Agent controller session not found.");
60
61
  const skill = await findSkill(session, input.name);
61
62
  if (!skill || skill["user-invocable"] === false) throw new SkillInvocationError("skill_not_found", `Skill not found: ${input.name}.`);
62
63
  const args = input.arguments?.trim();
63
- const content = [
64
+ const contents = [
64
65
  `Resume the active ${skill.name} session — it is already open in this thread; continue it rather than restarting.`,
65
66
  `Re-open the ${skill.name} skill with the skill tool if its instructions are no longer in context.`,
66
67
  ...args ? [`ARGUMENTS: ${args}`] : []
@@ -68,21 +69,10 @@ async function resolveSkillResumeInvocation(controller, input) {
68
69
  return {
69
70
  session,
70
71
  skillName: skill.name,
71
- message: `<skill name="${skill.name}">\n${escapeSkillBoundary(content)}\n</skill>`
72
- };
73
- }
74
- async function dispatchSkillInvocation(controller, input, requestContext) {
75
- const resolved = await resolveSkillInvocation(controller, input);
76
- await resolved.session.sendMessage({
77
- content: resolved.message,
78
- ...requestContext ? { requestContext } : {}
79
- });
80
- return {
81
- skillName: resolved.skillName,
82
- message: resolved.message
72
+ contents
83
73
  };
84
74
  }
85
75
  //#endregion
86
- export { SkillInvocationError, dispatchSkillInvocation, resolvePromptInvocation, resolveSkillInvocation, resolveSkillResumeInvocation };
76
+ export { SkillInvocationError, formatSkillMessage, resolvePromptInvocation, resolveSkillInvocation, resolveSkillResumeInvocation };
87
77
 
88
78
  //# sourceMappingURL=service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","names":[],"sources":["../../src/skills/service.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { SendAgentSignalResult } from '@mastra/core/agent';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { formatSkillActivation } from '@mastra/core/workspace';\nimport type { Workspace } from '@mastra/core/workspace';\n\nimport { REVIEW_ONLY_FACTORY_SKILLS, rescanFactorySkills } from '../workspace.js';\n\nexport interface SkillInvocationInput {\n resourceId: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\nexport interface SkillSession {\n getWorkspace(): Workspace;\n sendMessage(input: { content: string; requestContext?: RequestContext }): Promise<unknown>;\n sendNotificationSignal(\n input: {\n source: string;\n kind: string;\n summary: string;\n priority?: 'low' | 'medium' | 'high' | 'urgent';\n payload?: unknown;\n dedupeKey?: string;\n sourceId?: string;\n },\n options?: {\n ifActive?: { behavior?: 'deliver' | 'persist' };\n ifIdle?: { behavior?: 'persist' | 'wake' };\n requestContext?: RequestContext;\n },\n ): Promise<Pick<SendAgentSignalResult, 'persisted' | 'accepted'>>;\n}\n\nexport class SkillInvocationError extends Error {\n readonly code: 'session_not_found' | 'skill_not_found';\n\n constructor(code: SkillInvocationError['code'], message: string) {\n super(message);\n this.name = 'SkillInvocationError';\n this.code = code;\n }\n}\n\nasync function findSkill(session: SkillSession, name: string) {\n const skills = session.getWorkspace().skills;\n if (!skills) return undefined;\n await skills.maybeRefresh();\n const skill = await skills.get(name);\n if (skill || !REVIEW_ONLY_FACTORY_SKILLS.has(name)) return skill;\n // The cache may predate the review binding this run was just given.\n try {\n await rescanFactorySkills(skills);\n } catch {\n return undefined;\n }\n return skills.get(name);\n}\n\nfunction escapeSkillBoundary(value: string): string {\n return value.replaceAll('</skill>', '&lt;/skill&gt;');\n}\n\n/** Kicks a run off from a plain prompt, for runs that activate no skill. */\nexport async function resolvePromptInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: { resourceId: string; scope?: SkillInvocationInput['scope']; prompt: string },\n): Promise<{ session: SkillSession; message: string }> {\n const session = (await controller.getSessionByResource(input.resourceId, input.scope)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n return { session, message: input.prompt };\n}\n\nexport async function resolveSkillInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n): Promise<{ session: SkillSession; skillName: string; message: string }> {\n const session = (await controller.getSessionByResource(input.resourceId, input.scope)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n\n const skill = await findSkill(session, input.name);\n if (!skill || skill['user-invocable'] === false) {\n throw new SkillInvocationError('skill_not_found', `Skill not found: ${input.name}.`);\n }\n\n const args = input.arguments?.trim();\n const content = `${formatSkillActivation(skill)}${args ? `\\n\\nARGUMENTS: ${args}` : ''}`.trim();\n return {\n session,\n skillName: skill.name,\n message: `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`,\n };\n}\n\n/**\n * Same-stage re-entry: the skill is already active in the card's live session.\n * Resolve a compact continuation that references the skill by name and carries\n * only the fresh arguments, instead of re-pasting the whole skill document. If\n * the skill body was compacted out of context, the agent can re-open it with the\n * skill tool.\n */\nexport async function resolveSkillResumeInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n): Promise<{ session: SkillSession; skillName: string; message: string }> {\n const session = (await controller.getSessionByResource(input.resourceId, input.scope)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n\n const skill = await findSkill(session, input.name);\n if (!skill || skill['user-invocable'] === false) {\n throw new SkillInvocationError('skill_not_found', `Skill not found: ${input.name}.`);\n }\n\n const args = input.arguments?.trim();\n const content = [\n `Resume the active ${skill.name} session — it is already open in this thread; continue it rather than restarting.`,\n `Re-open the ${skill.name} skill with the skill tool if its instructions are no longer in context.`,\n ...(args ? [`ARGUMENTS: ${args}`] : []),\n ].join('\\n');\n return {\n session,\n skillName: skill.name,\n message: `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`,\n };\n}\n\nexport async function dispatchSkillInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n requestContext?: RequestContext,\n): Promise<{ skillName: string; message: string }> {\n const resolved = await resolveSkillInvocation(controller, input);\n await resolved.session.sendMessage({ content: resolved.message, ...(requestContext ? { requestContext } : {}) });\n return { skillName: resolved.skillName, message: resolved.message };\n}\n"],"mappings":";;;AAqCA,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CAEA,YAAY,MAAoC,SAAiB;EAC/D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,eAAe,UAAU,SAAuB,MAAc;CAC5D,MAAM,SAAS,QAAQ,aAAa,CAAC,CAAC;CACtC,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,MAAM,OAAO,aAAa;CAC1B,MAAM,QAAQ,MAAM,OAAO,IAAI,IAAI;CACnC,IAAI,SAAS,CAAC,2BAA2B,IAAI,IAAI,GAAG,OAAO;CAE3D,IAAI;EACF,MAAM,oBAAoB,MAAM;CAClC,QAAQ;EACN;CACF;CACA,OAAO,OAAO,IAAI,IAAI;AACxB;AAEA,SAAS,oBAAoB,OAAuB;CAClD,OAAO,MAAM,WAAW,YAAY,gBAAgB;AACtD;;AAGA,eAAsB,wBACpB,YACA,OACqD;CACrD,MAAM,UAAW,MAAM,WAAW,qBAAqB,MAAM,YAAY,MAAM,KAAK;CACpF,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CACvG,OAAO;EAAE;EAAS,SAAS,MAAM;CAAO;AAC1C;AAEA,eAAsB,uBACpB,YACA,OACwE;CACxE,MAAM,UAAW,MAAM,WAAW,qBAAqB,MAAM,YAAY,MAAM,KAAK;CACpF,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CAEvG,MAAM,QAAQ,MAAM,UAAU,SAAS,MAAM,IAAI;CACjD,IAAI,CAAC,SAAS,MAAM,sBAAsB,OACxC,MAAM,IAAI,qBAAqB,mBAAmB,oBAAoB,MAAM,KAAK,EAAE;CAGrF,MAAM,OAAO,MAAM,WAAW,KAAK;CACnC,MAAM,UAAU,GAAG,sBAAsB,KAAK,IAAI,OAAO,kBAAkB,SAAS,KAAK,KAAK;CAC9F,OAAO;EACL;EACA,WAAW,MAAM;EACjB,SAAS,gBAAgB,MAAM,KAAK,MAAM,oBAAoB,OAAO,EAAE;CACzE;AACF;;;;;;;;AASA,eAAsB,6BACpB,YACA,OACwE;CACxE,MAAM,UAAW,MAAM,WAAW,qBAAqB,MAAM,YAAY,MAAM,KAAK;CACpF,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CAEvG,MAAM,QAAQ,MAAM,UAAU,SAAS,MAAM,IAAI;CACjD,IAAI,CAAC,SAAS,MAAM,sBAAsB,OACxC,MAAM,IAAI,qBAAqB,mBAAmB,oBAAoB,MAAM,KAAK,EAAE;CAGrF,MAAM,OAAO,MAAM,WAAW,KAAK;CACnC,MAAM,UAAU;EACd,qBAAqB,MAAM,KAAK;EAChC,eAAe,MAAM,KAAK;EAC1B,GAAI,OAAO,CAAC,cAAc,MAAM,IAAI,CAAC;CACvC,CAAC,CAAC,KAAK,IAAI;CACX,OAAO;EACL;EACA,WAAW,MAAM;EACjB,SAAS,gBAAgB,MAAM,KAAK,MAAM,oBAAoB,OAAO,EAAE;CACzE;AACF;AAEA,eAAsB,wBACpB,YACA,OACA,gBACiD;CACjD,MAAM,WAAW,MAAM,uBAAuB,YAAY,KAAK;CAC/D,MAAM,SAAS,QAAQ,YAAY;EAAE,SAAS,SAAS;EAAS,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;CAAG,CAAC;CAC/G,OAAO;EAAE,WAAW,SAAS;EAAW,SAAS,SAAS;CAAQ;AACpE"}
1
+ {"version":3,"file":"service.js","names":[],"sources":["../../src/skills/service.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { SendAgentSignalResult } from '@mastra/core/agent';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { formatSkillActivation } from '@mastra/core/workspace';\nimport type { Workspace } from '@mastra/core/workspace';\n\nimport { REVIEW_ONLY_FACTORY_SKILLS, rescanFactorySkills } from '../workspace.js';\n\nexport interface SkillInvocationInput {\n sessionId: string;\n name: string;\n arguments?: string;\n}\n\nexport interface SkillSession {\n getWorkspace(): Workspace;\n sendMessage(input: { content: string; requestContext?: RequestContext }): Promise<unknown>;\n sendNotificationSignal(\n input: {\n source: string;\n kind: string;\n summary: string;\n priority?: 'low' | 'medium' | 'high' | 'urgent';\n payload?: unknown;\n dedupeKey?: string;\n sourceId?: string;\n },\n options?: {\n ifActive?: { behavior?: 'deliver' | 'persist' };\n ifIdle?: { behavior?: 'persist' | 'wake' };\n requestContext?: RequestContext;\n },\n ): Promise<Pick<SendAgentSignalResult, 'persisted' | 'accepted'>>;\n}\n\nexport class SkillInvocationError extends Error {\n readonly code: 'session_not_found' | 'skill_not_found';\n\n constructor(code: SkillInvocationError['code'], message: string) {\n super(message);\n this.name = 'SkillInvocationError';\n this.code = code;\n }\n}\n\nasync function findSkill(session: SkillSession, name: string) {\n const skills = session.getWorkspace().skills;\n if (!skills) return undefined;\n await skills.maybeRefresh();\n const skill = await skills.get(name);\n if (skill || !REVIEW_ONLY_FACTORY_SKILLS.has(name)) return skill;\n // The cache may predate the review binding this run was just given.\n try {\n await rescanFactorySkills(skills);\n } catch {\n return undefined;\n }\n return skills.get(name);\n}\n\n/** Wraps skill contents in the `<skill>` envelope a human-invoked skill sends as its own message. */\nexport function formatSkillMessage(name: string, contents: string): string {\n return `<skill name=\"${name}\">\\n${contents.replaceAll('</skill>', '&lt;/skill&gt;')}\\n</skill>`;\n}\n\n/** Kicks a run off from a plain prompt, for runs that activate no skill. */\nexport async function resolvePromptInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSession'>,\n input: { sessionId: string; prompt: string },\n): Promise<{ session: SkillSession; skillName?: undefined; contents: string }> {\n const session = (await controller.getSession(input.sessionId)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n return { session, contents: input.prompt };\n}\n\nexport async function resolveSkillInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSession'>,\n input: SkillInvocationInput,\n): Promise<{ session: SkillSession; skillName: string; contents: string }> {\n const session = (await controller.getSession(input.sessionId)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n\n const skill = await findSkill(session, input.name);\n if (!skill || skill['user-invocable'] === false) {\n throw new SkillInvocationError('skill_not_found', `Skill not found: ${input.name}.`);\n }\n\n const args = input.arguments?.trim();\n const contents = `${formatSkillActivation(skill)}${args ? `\\n\\nARGUMENTS: ${args}` : ''}`.trim();\n return { session, skillName: skill.name, contents };\n}\n\n/**\n * Same-stage re-entry: the skill is already active in the card's live session.\n * Resolve a compact continuation that references the skill by name and carries\n * only the fresh arguments, instead of re-pasting the whole skill document. If\n * the skill body was compacted out of context, the agent can re-open it with the\n * skill tool.\n */\nexport async function resolveSkillResumeInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSession'>,\n input: SkillInvocationInput,\n): Promise<{ session: SkillSession; skillName: string; contents: string }> {\n const session = (await controller.getSession(input.sessionId)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n\n const skill = await findSkill(session, input.name);\n if (!skill || skill['user-invocable'] === false) {\n throw new SkillInvocationError('skill_not_found', `Skill not found: ${input.name}.`);\n }\n\n const args = input.arguments?.trim();\n const contents = [\n `Resume the active ${skill.name} session — it is already open in this thread; continue it rather than restarting.`,\n `Re-open the ${skill.name} skill with the skill tool if its instructions are no longer in context.`,\n ...(args ? [`ARGUMENTS: ${args}`] : []),\n ].join('\\n');\n return { session, skillName: skill.name, contents };\n}\n"],"mappings":";;;AAoCA,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CAEA,YAAY,MAAoC,SAAiB;EAC/D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,eAAe,UAAU,SAAuB,MAAc;CAC5D,MAAM,SAAS,QAAQ,aAAa,CAAC,CAAC;CACtC,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,MAAM,OAAO,aAAa;CAC1B,MAAM,QAAQ,MAAM,OAAO,IAAI,IAAI;CACnC,IAAI,SAAS,CAAC,2BAA2B,IAAI,IAAI,GAAG,OAAO;CAE3D,IAAI;EACF,MAAM,oBAAoB,MAAM;CAClC,QAAQ;EACN;CACF;CACA,OAAO,OAAO,IAAI,IAAI;AACxB;;AAGA,SAAgB,mBAAmB,MAAc,UAA0B;CACzE,OAAO,gBAAgB,KAAK,MAAM,SAAS,WAAW,YAAY,gBAAgB,EAAE;AACtF;;AAGA,eAAsB,wBACpB,YACA,OAC6E;CAC7E,MAAM,UAAW,MAAM,WAAW,WAAW,MAAM,SAAS;CAC5D,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CACvG,OAAO;EAAE;EAAS,UAAU,MAAM;CAAO;AAC3C;AAEA,eAAsB,uBACpB,YACA,OACyE;CACzE,MAAM,UAAW,MAAM,WAAW,WAAW,MAAM,SAAS;CAC5D,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CAEvG,MAAM,QAAQ,MAAM,UAAU,SAAS,MAAM,IAAI;CACjD,IAAI,CAAC,SAAS,MAAM,sBAAsB,OACxC,MAAM,IAAI,qBAAqB,mBAAmB,oBAAoB,MAAM,KAAK,EAAE;CAGrF,MAAM,OAAO,MAAM,WAAW,KAAK;CACnC,MAAM,WAAW,GAAG,sBAAsB,KAAK,IAAI,OAAO,kBAAkB,SAAS,KAAK,KAAK;CAC/F,OAAO;EAAE;EAAS,WAAW,MAAM;EAAM;CAAS;AACpD;;;;;;;;AASA,eAAsB,6BACpB,YACA,OACyE;CACzE,MAAM,UAAW,MAAM,WAAW,WAAW,MAAM,SAAS;CAC5D,IAAI,CAAC,SAAS,MAAM,IAAI,qBAAqB,qBAAqB,qCAAqC;CAEvG,MAAM,QAAQ,MAAM,UAAU,SAAS,MAAM,IAAI;CACjD,IAAI,CAAC,SAAS,MAAM,sBAAsB,OACxC,MAAM,IAAI,qBAAqB,mBAAmB,oBAAoB,MAAM,KAAK,EAAE;CAGrF,MAAM,OAAO,MAAM,WAAW,KAAK;CACnC,MAAM,WAAW;EACf,qBAAqB,MAAM,KAAK;EAChC,eAAe,MAAM,KAAK;EAC1B,GAAI,OAAO,CAAC,cAAc,MAAM,IAAI,CAAC;CACvC,CAAC,CAAC,KAAK,IAAI;CACX,OAAO;EAAE;EAAS,WAAW,MAAM;EAAM;CAAS;AACpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"spa-static.js","names":[],"sources":["../src/spa-static.ts"],"sourcesContent":["/**\n * Same-origin SPA serving for production.\n *\n * In dev the SPA is served by Vite (:5173) which proxies API paths to the\n * server (:4111) — this module is effectively idle there. In production the\n * built SPA (vite output) is served by the API server itself at `/`, so the\n * app is a single origin and no CORS / separate static host is required.\n *\n * The middleware is mounted via the Mastra entry's `server.middleware`, which\n * the deployer applies before routes: real file hits and SPA navigations are\n * answered here, everything else falls through to the API routes.\n */\n\nimport { existsSync } from 'node:fs';\nimport { readFile, stat } from 'node:fs/promises';\nimport { dirname, extname, join, normalize, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Context } from 'hono';\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json',\n '.webmanifest': 'application/manifest+json',\n '.map': 'application/json',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n '.txt': 'text/plain; charset=utf-8',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.wasm': 'application/wasm',\n};\n\n/** Server-owned path prefixes the SPA middleware must never answer for. */\nconst SERVER_PREFIXES = ['/api', '/web', '/auth', '/connect'];\n\n/**\n * Locate the built SPA (a dir containing `index.html`). Checked in order:\n * 1. `MASTRACODE_UI_DIST` — explicit override for custom layouts.\n * 2. `factory/` under cwd — both `mastra dev` (cwd is the Mastra public dir)\n * and `mastra start` (cwd is `.mastra/output`) expose assets this way.\n * 3. `factory/` next to the bundled server module.\n * 4. Source-layout fallbacks under `MASTRA_PROJECT_ROOT` or cwd.\n * Returns `undefined` when no build is found (e.g. plain `mastra dev` without\n * a prior vite build), in which case the middleware is simply not mounted.\n */\nexport function resolveUiDistDir(): string | undefined {\n const projectRoot = process.env.MASTRA_PROJECT_ROOT?.trim();\n const candidates = [\n process.env.MASTRACODE_UI_DIST,\n resolve(process.cwd(), 'factory'),\n join(dirname(fileURLToPath(import.meta.url)), 'factory'),\n projectRoot && resolve(projectRoot, 'src/mastra/public/factory'),\n resolve(process.cwd(), 'src/mastra/public/factory'),\n ];\n for (const candidate of candidates) {\n if (candidate && existsSync(join(candidate, 'index.html'))) return resolve(candidate);\n }\n return undefined;\n}\n\nasync function isFile(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isFile();\n } catch {\n return false;\n }\n}\n\nasync function serveFile(c: Context, filePath: string, immutable: boolean): Promise<Response> {\n const data = await readFile(filePath);\n c.header('Content-Type', MIME[extname(filePath).toLowerCase()] ?? 'application/octet-stream');\n // Vite emits content-hashed filenames under assets/ — cache those forever;\n // index.html (and anything unhashed) must revalidate so deploys roll out.\n c.header('Cache-Control', immutable ? 'public, max-age=31536000, immutable' : 'no-cache');\n return c.body(new Uint8Array(data));\n}\n\n/**\n * Hono middleware serving the built SPA from `uiDist`:\n * - exact file hits (js/css/assets) are served directly,\n * - GET navigations to non-server paths fall back to `index.html` (SPA\n * client-side routing),\n * - `/api`, `/web`, `/auth` and non-GET requests always pass through.\n */\nexport function createSpaStaticMiddleware(uiDist: string) {\n return async (c: Context, next: () => Promise<void>): Promise<Response | void> => {\n if (c.req.method !== 'GET' && c.req.method !== 'HEAD') return next();\n const path = c.req.path;\n if (SERVER_PREFIXES.some(prefix => path === prefix || path.startsWith(`${prefix}/`))) return next();\n\n // Resolve the request path inside uiDist, rejecting traversal escapes.\n const relative = normalize(decodeURIComponent(path)).replace(/^[/\\\\]+/, '');\n const filePath = resolve(uiDist, relative);\n // Use a trailing separator so a sibling like \"ui.key\" can't slip past the\n // prefix check (same pattern as fs-routes.ts isWithinRoot).\n const uiDistPrefix = uiDist.endsWith(sep) ? uiDist : uiDist + sep;\n if (filePath.startsWith(uiDistPrefix) && relative !== '' && (await isFile(filePath))) {\n return serveFile(c, filePath, relative.startsWith('assets/'));\n }\n\n // SPA fallback: serve index.html for root and html navigations.\n const accept = c.req.header('Accept') ?? '';\n if (path === '/' || accept.includes('text/html')) {\n return serveFile(c, join(uiDist, 'index.html'), false);\n }\n\n return next();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,MAAM,OAA+B;CACnC,SAAS;CACT,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,gBAAgB;CAChB,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,UAAU;CACV,QAAQ;CACR,SAAS;AACX;;AAGA,MAAM,kBAAkB;CAAC;CAAQ;CAAQ;CAAS;AAAU;;;;;;;;;;;AAY5D,SAAgB,mBAAuC;CACrD,MAAM,cAAc,QAAQ,IAAI,qBAAqB,KAAK;CAC1D,MAAM,aAAa;EACjB,QAAQ,IAAI;EACZ,QAAQ,QAAQ,IAAI,GAAG,SAAS;EAChC,KAAK,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC,GAAG,SAAS;EACvD,eAAe,QAAQ,aAAa,2BAA2B;EAC/D,QAAQ,QAAQ,IAAI,GAAG,2BAA2B;CACpD;CACA,KAAK,MAAM,aAAa,YACtB,IAAI,aAAa,WAAW,KAAK,WAAW,YAAY,CAAC,GAAG,OAAO,QAAQ,SAAS;AAGxF;AAEA,eAAe,OAAO,MAAgC;CACpD,IAAI;EACF,QAAQ,MAAM,KAAK,IAAI,EAAA,CAAG,OAAO;CACnC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAe,UAAU,GAAY,UAAkB,WAAuC;CAC5F,MAAM,OAAO,MAAM,SAAS,QAAQ;CACpC,EAAE,OAAO,gBAAgB,KAAK,QAAQ,QAAQ,CAAC,CAAC,YAAY,MAAM,0BAA0B;CAG5F,EAAE,OAAO,iBAAiB,YAAY,wCAAwC,UAAU;CACxF,OAAO,EAAE,KAAK,IAAI,WAAW,IAAI,CAAC;AACpC;;;;;;;;AASA,SAAgB,0BAA0B,QAAgB;CACxD,OAAO,OAAO,GAAY,SAAwD;EAChF,IAAI,EAAE,IAAI,WAAW,SAAS,EAAE,IAAI,WAAW,QAAQ,OAAO,KAAK;EACnE,MAAM,OAAO,EAAE,IAAI;EACnB,IAAI,gBAAgB,MAAK,WAAU,SAAS,UAAU,KAAK,WAAW,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,KAAK;EAGlG,MAAM,WAAW,UAAU,mBAAmB,IAAI,CAAC,CAAC,CAAC,QAAQ,WAAW,EAAE;EAC1E,MAAM,WAAW,QAAQ,QAAQ,QAAQ;EAGzC,MAAM,eAAe,OAAO,SAAS,GAAG,IAAI,SAAS,SAAS;EAC9D,IAAI,SAAS,WAAW,YAAY,KAAK,aAAa,MAAO,MAAM,OAAO,QAAQ,GAChF,OAAO,UAAU,GAAG,UAAU,SAAS,WAAW,SAAS,CAAC;EAI9D,MAAM,SAAS,EAAE,IAAI,OAAO,QAAQ,KAAK;EACzC,IAAI,SAAS,OAAO,OAAO,SAAS,WAAW,GAC7C,OAAO,UAAU,GAAG,KAAK,QAAQ,YAAY,GAAG,KAAK;EAGvD,OAAO,KAAK;CACd;AACF"}
1
+ {"version":3,"file":"spa-static.js","names":[],"sources":["../src/spa-static.ts"],"sourcesContent":["/**\n * Same-origin SPA serving for production.\n *\n * In dev the SPA is served by Vite (:5173) which proxies API paths to the\n * server (:4111) — this module is effectively idle there. In production the\n * built SPA (vite output) is served by the API server itself at `/`, so the\n * app is a single origin and no CORS / separate static host is required.\n *\n * The middleware is mounted via the Mastra entry's `server.middleware`, which\n * the deployer applies before routes: real file hits and SPA navigations are\n * answered here, everything else falls through to the API routes.\n */\n\nimport { existsSync } from 'node:fs';\nimport { readFile, stat } from 'node:fs/promises';\nimport { dirname, extname, join, normalize, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Context } from 'hono';\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json',\n '.webmanifest': 'application/manifest+json',\n '.map': 'application/json',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n '.txt': 'text/plain; charset=utf-8',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.wasm': 'application/wasm',\n};\n\n/** Server-owned path prefixes the SPA middleware must never answer for. */\nconst SERVER_PREFIXES = ['/api', '/web', '/auth', '/connect'];\n\n/**\n * Locate the built SPA (a dir containing `index.html`). Checked in order:\n * 1. `MASTRACODE_UI_DIST` — explicit override for custom layouts.\n * 2. `factory/` under cwd — both `mastra dev` (cwd is the Mastra public dir)\n * and `mastra start` (cwd is `.mastra/output`) expose assets this way.\n * 3. `factory/` next to the bundled server module.\n * 4. Source-layout fallbacks under `MASTRA_PROJECT_ROOT` or cwd.\n * Returns `undefined` when no build is found (e.g. plain `mastra dev` without\n * a prior vite build), in which case the middleware is simply not mounted.\n */\nexport function resolveUiDistDir(): string | undefined {\n const projectRoot = process.env.MASTRA_PROJECT_ROOT?.trim();\n const candidates = [\n process.env.MASTRACODE_UI_DIST,\n resolve(process.cwd(), 'factory'),\n join(dirname(fileURLToPath(import.meta.url)), 'factory'),\n projectRoot && resolve(projectRoot, 'src/mastra/public/factory'),\n resolve(process.cwd(), 'src/mastra/public/factory'),\n ];\n for (const candidate of candidates) {\n if (candidate && existsSync(join(candidate, 'index.html'))) return resolve(candidate);\n }\n return undefined;\n}\n\nasync function isFile(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isFile();\n } catch {\n return false;\n }\n}\n\nasync function serveFile(c: Context, filePath: string, immutable: boolean): Promise<Response> {\n const data = await readFile(filePath);\n c.header('Content-Type', MIME[extname(filePath).toLowerCase()] ?? 'application/octet-stream');\n // Vite emits content-hashed filenames under assets/ — cache those forever;\n // index.html (and anything unhashed) must revalidate so deploys roll out.\n c.header('Cache-Control', immutable ? 'public, max-age=31536000, immutable' : 'no-cache');\n return c.body(new Uint8Array(data));\n}\n\n/**\n * Hono middleware serving the built SPA from `uiDist`:\n * - exact file hits (js/css/assets) are served directly,\n * - GET navigations to non-server paths fall back to `index.html` (SPA\n * client-side routing),\n * - `/api`, `/web`, `/auth` and non-GET requests always pass through.\n */\nexport function createSpaStaticMiddleware(uiDist: string) {\n return async (c: Context, next: () => Promise<void>): Promise<Response | void> => {\n if (c.req.method !== 'GET' && c.req.method !== 'HEAD') return next();\n const path = c.req.path;\n if (SERVER_PREFIXES.some(prefix => path === prefix || path.startsWith(`${prefix}/`))) return next();\n\n // Resolve the request path inside uiDist, rejecting traversal escapes.\n const relative = normalize(decodeURIComponent(path)).replace(/^[/\\\\]+/, '');\n const filePath = resolve(uiDist, relative);\n // Use a trailing separator so a sibling like \"ui.key\" can't slip past the\n // prefix check (same pattern as fs-routes.ts isWithinRoot).\n const uiDistPrefix = uiDist.endsWith(sep) ? uiDist : uiDist + sep;\n if (filePath.startsWith(uiDistPrefix) && relative !== '' && (await isFile(filePath))) {\n return serveFile(c, filePath, relative.startsWith('assets/'));\n }\n\n // SPA fallback: serve index.html for root and html navigations.\n const accept = c.req.header('Accept') ?? '';\n if (path === '/' || accept.includes('text/html')) {\n return serveFile(c, join(uiDist, 'index.html'), false);\n }\n\n return next();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,MAAM,OAA+B;CACnC,SAAS;CACT,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,gBAAgB;CAChB,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,UAAU;CACV,QAAQ;CACR,SAAS;AACX;;AAGA,MAAM,kBAAkB;CAAC;CAAQ;CAAQ;CAAS;AAAU;;;;;;;;;;;AAY5D,SAAgB,mBAAuC;CACrD,MAAM,cAAc,QAAQ,IAAI,qBAAqB,KAAK;CAC1D,MAAM,aAAa;EACjB,QAAQ,IAAI;EACZ,QAAQ,QAAQ,IAAI,GAAG,SAAS;EAChC,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,SAAS;EACvD,eAAe,QAAQ,aAAa,2BAA2B;EAC/D,QAAQ,QAAQ,IAAI,GAAG,2BAA2B;CACpD;CACA,KAAK,MAAM,aAAa,YACtB,IAAI,aAAa,WAAW,KAAK,WAAW,YAAY,CAAC,GAAG,OAAO,QAAQ,SAAS;AAGxF;AAEA,eAAe,OAAO,MAAgC;CACpD,IAAI;EACF,QAAQ,MAAM,KAAK,IAAI,EAAA,CAAG,OAAO;CACnC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAe,UAAU,GAAY,UAAkB,WAAuC;CAC5F,MAAM,OAAO,MAAM,SAAS,QAAQ;CACpC,EAAE,OAAO,gBAAgB,KAAK,QAAQ,QAAQ,CAAC,CAAC,YAAY,MAAM,0BAA0B;CAG5F,EAAE,OAAO,iBAAiB,YAAY,wCAAwC,UAAU;CACxF,OAAO,EAAE,KAAK,IAAI,WAAW,IAAI,CAAC;AACpC;;;;;;;;AASA,SAAgB,0BAA0B,QAAgB;CACxD,OAAO,OAAO,GAAY,SAAwD;EAChF,IAAI,EAAE,IAAI,WAAW,SAAS,EAAE,IAAI,WAAW,QAAQ,OAAO,KAAK;EACnE,MAAM,OAAO,EAAE,IAAI;EACnB,IAAI,gBAAgB,MAAK,WAAU,SAAS,UAAU,KAAK,WAAW,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,KAAK;EAGlG,MAAM,WAAW,UAAU,mBAAmB,IAAI,CAAC,CAAC,CAAC,QAAQ,WAAW,EAAE;EAC1E,MAAM,WAAW,QAAQ,QAAQ,QAAQ;EAGzC,MAAM,eAAe,OAAO,SAAS,GAAG,IAAI,SAAS,SAAS;EAC9D,IAAI,SAAS,WAAW,YAAY,KAAK,aAAa,MAAO,MAAM,OAAO,QAAQ,GAChF,OAAO,UAAU,GAAG,UAAU,SAAS,WAAW,SAAS,CAAC;EAI9D,MAAM,SAAS,EAAE,IAAI,OAAO,QAAQ,KAAK;EACzC,IAAI,SAAS,OAAO,OAAO,SAAS,WAAW,GAC7C,OAAO,UAAU,GAAG,KAAK,QAAQ,YAAY,GAAG,KAAK;EAGvD,OAAO,KAAK;CACd;AACF"}
@@ -25,7 +25,7 @@ import { createHmac, timingSafeEqual } from "crypto";
25
25
  * so in-flight OAuth states survive a deploy.
26
26
  */
27
27
  /** Signed `state` values expire after this window to bound the CSRF token. */
28
- const STATE_MAX_AGE_MS = 600 * 1e3;
28
+ const STATE_MAX_AGE_MS = 6e5;
29
29
  /**
30
30
  * Create a state signer. With a `secret`, the signer is deployment-stable
31
31
  * (`stable: true`); without one it falls back to a per-process random secret
@@ -1 +1 @@
1
- {"version":3,"file":"state-signing.js","names":[],"sources":["../src/state-signing.ts"],"sourcesContent":["/**\n * Shared OAuth/install `state` signing for web integrations.\n *\n * The GitHub, Linear, and Slack OAuth/OIDC flows each round-trip a signed `state`\n * value\n * through the third party to bind the callback to the `(orgId, userId)` tenant\n * that initiated it (CSRF protection + tenant routing). The signer is a system\n * facility: `MastraFactory` creates ONE signer at boot and hands it to every\n * registered integration through `IntegrationContext` (see\n * `./factory-integration.ts`), so all integrations sign and verify with the\n * same secret.\n *\n * Secret resolution happens in the factory, not here: explicit\n * `config.stateSecret` → the GitHub integration's webhook secret → a\n * per-process random secret. A random secret is NOT stable across replicas —\n * a `state` signed by one replica cannot be verified by another — which is\n * what the `stable` flag reports. The factory fails loud at boot when a\n * registered integration requires a stable signer but only a random one is\n * available.\n *\n * The wire format (base64url JSON payload + `.` + HMAC-SHA256 base64url\n * signature) is unchanged from the previous `github/config.ts` implementation\n * so in-flight OAuth states survive a deploy.\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n/** Verified tenant and optional Factory context carried by a signed `state`. */\nexport interface StateTenant {\n orgId: string;\n userId: string;\n /** Factory that initiated the integration flow, when the caller supplied one. */\n factoryProjectId?: string;\n /**\n * Per-`state` random value. A signed `state` stays valid for its whole\n * lifetime, so a flow that must not run twice off one `state` (account\n * binding, for instance) can key single-use bookkeeping on this.\n */\n nonce: string;\n}\n\n/** Signs and verifies OAuth `state` values bound to a tenant and optional Factory. */\nexport interface StateSigner {\n /** Build a signed `state` bound to the tenant and optional initiating Factory. */\n sign(orgId: string, userId: string, context?: { factoryProjectId?: string }): string;\n /** Verify a signed `state`; returns the bound tenant, or `null` if invalid. */\n verify(state: string | undefined): StateTenant | null;\n /**\n * True when the signer was built from an explicit deployment-stable secret.\n * False means a per-process random secret: fine for single-process/local\n * dev, broken for multi-replica deploys (see module docs).\n */\n readonly stable: boolean;\n}\n\ninterface StatePayload {\n orgId: string;\n userId: string;\n factoryProjectId?: string;\n nonce: string;\n issuedAt: number;\n}\n\n/** Signed `state` values expire after this window to bound the CSRF token. */\nconst STATE_MAX_AGE_MS = 10 * 60 * 1000;\n\n/**\n * Create a state signer. With a `secret`, the signer is deployment-stable\n * (`stable: true`); without one it falls back to a per-process random secret\n * (`stable: false`).\n */\nexport function createStateSigner(secret?: string): StateSigner {\n const stable = typeof secret === 'string' && secret.length > 0;\n const key = stable ? secret : Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(32))).toString('hex');\n return {\n stable,\n sign(orgId: string, userId: string, context?: { factoryProjectId?: string }): string {\n const payload: StatePayload = {\n orgId,\n userId,\n ...(context?.factoryProjectId ? { factoryProjectId: context.factoryProjectId } : {}),\n nonce: Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(8))).toString('hex'),\n issuedAt: Date.now(),\n };\n const body = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');\n const sig = createHmac('sha256', key).update(body).digest('base64url');\n return `${body}.${sig}`;\n },\n verify(state: string | undefined): StateTenant | null {\n if (!state) return null;\n const dot = state.lastIndexOf('.');\n if (dot <= 0) return null;\n const body = state.slice(0, dot);\n const sig = state.slice(dot + 1);\n const expected = createHmac('sha256', key).update(body).digest('base64url');\n const sigBuf = Buffer.from(sig);\n const expectedBuf = Buffer.from(expected);\n if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {\n return null;\n }\n try {\n const parsed = JSON.parse(Buffer.from(body, 'base64url').toString('utf8')) as StatePayload;\n if (typeof parsed.orgId !== 'string' || typeof parsed.userId !== 'string') return null;\n if (typeof parsed.issuedAt !== 'number' || !Number.isFinite(parsed.issuedAt)) return null;\n if (typeof parsed.nonce !== 'string' || parsed.nonce.length === 0) return null;\n if (\n parsed.factoryProjectId !== undefined &&\n (typeof parsed.factoryProjectId !== 'string' || parsed.factoryProjectId.length === 0)\n ) {\n return null;\n }\n const age = Date.now() - parsed.issuedAt;\n if (age < 0 || age > STATE_MAX_AGE_MS) return null;\n return {\n orgId: parsed.orgId,\n userId: parsed.userId,\n ...(parsed.factoryProjectId ? { factoryProjectId: parsed.factoryProjectId } : {}),\n nonce: parsed.nonce,\n };\n } catch {\n return null;\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,MAAM,mBAAmB,MAAU;;;;;;AAOnC,SAAgB,kBAAkB,QAA8B;CAC9D,MAAM,SAAS,OAAO,WAAW,YAAY,OAAO,SAAS;CAC7D,MAAM,MAAM,SAAS,SAAS,OAAO,KAAK,WAAW,OAAO,gCAAgB,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK;CAC/G,OAAO;EACL;EACA,KAAK,OAAe,QAAgB,SAAiD;GACnF,MAAM,UAAwB;IAC5B;IACA;IACA,GAAI,SAAS,mBAAmB,EAAE,kBAAkB,QAAQ,iBAAiB,IAAI,CAAC;IAClF,OAAO,OAAO,KAAK,WAAW,OAAO,gCAAgB,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK;IACvF,UAAU,KAAK,IAAI;GACrB;GACA,MAAM,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO,GAAG,MAAM,CAAC,CAAC,SAAS,WAAW;GAE9E,OAAO,GAAG,KAAK,GADH,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,WACtC;EACtB;EACA,OAAO,OAA+C;GACpD,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,MAAM,MAAM,YAAY,GAAG;GACjC,IAAI,OAAO,GAAG,OAAO;GACrB,MAAM,OAAO,MAAM,MAAM,GAAG,GAAG;GAC/B,MAAM,MAAM,MAAM,MAAM,MAAM,CAAC;GAC/B,MAAM,WAAW,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,WAAW;GAC1E,MAAM,SAAS,OAAO,KAAK,GAAG;GAC9B,MAAM,cAAc,OAAO,KAAK,QAAQ;GACxC,IAAI,OAAO,WAAW,YAAY,UAAU,CAAC,gBAAgB,QAAQ,WAAW,GAC9E,OAAO;GAET,IAAI;IACF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;IACzE,IAAI,OAAO,OAAO,UAAU,YAAY,OAAO,OAAO,WAAW,UAAU,OAAO;IAClF,IAAI,OAAO,OAAO,aAAa,YAAY,CAAC,OAAO,SAAS,OAAO,QAAQ,GAAG,OAAO;IACrF,IAAI,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,WAAW,GAAG,OAAO;IAC1E,IACE,OAAO,qBAAqB,KAAA,MAC3B,OAAO,OAAO,qBAAqB,YAAY,OAAO,iBAAiB,WAAW,IAEnF,OAAO;IAET,MAAM,MAAM,KAAK,IAAI,IAAI,OAAO;IAChC,IAAI,MAAM,KAAK,MAAM,kBAAkB,OAAO;IAC9C,OAAO;KACL,OAAO,OAAO;KACd,QAAQ,OAAO;KACf,GAAI,OAAO,mBAAmB,EAAE,kBAAkB,OAAO,iBAAiB,IAAI,CAAC;KAC/E,OAAO,OAAO;IAChB;GACF,QAAQ;IACN,OAAO;GACT;EACF;CACF;AACF"}
1
+ {"version":3,"file":"state-signing.js","names":[],"sources":["../src/state-signing.ts"],"sourcesContent":["/**\n * Shared OAuth/install `state` signing for web integrations.\n *\n * The GitHub, Linear, and Slack OAuth/OIDC flows each round-trip a signed `state`\n * value\n * through the third party to bind the callback to the `(orgId, userId)` tenant\n * that initiated it (CSRF protection + tenant routing). The signer is a system\n * facility: `MastraFactory` creates ONE signer at boot and hands it to every\n * registered integration through `IntegrationContext` (see\n * `./factory-integration.ts`), so all integrations sign and verify with the\n * same secret.\n *\n * Secret resolution happens in the factory, not here: explicit\n * `config.stateSecret` → the GitHub integration's webhook secret → a\n * per-process random secret. A random secret is NOT stable across replicas —\n * a `state` signed by one replica cannot be verified by another — which is\n * what the `stable` flag reports. The factory fails loud at boot when a\n * registered integration requires a stable signer but only a random one is\n * available.\n *\n * The wire format (base64url JSON payload + `.` + HMAC-SHA256 base64url\n * signature) is unchanged from the previous `github/config.ts` implementation\n * so in-flight OAuth states survive a deploy.\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n/** Verified tenant and optional Factory context carried by a signed `state`. */\nexport interface StateTenant {\n orgId: string;\n userId: string;\n /** Factory that initiated the integration flow, when the caller supplied one. */\n factoryProjectId?: string;\n /**\n * Per-`state` random value. A signed `state` stays valid for its whole\n * lifetime, so a flow that must not run twice off one `state` (account\n * binding, for instance) can key single-use bookkeeping on this.\n */\n nonce: string;\n}\n\n/** Signs and verifies OAuth `state` values bound to a tenant and optional Factory. */\nexport interface StateSigner {\n /** Build a signed `state` bound to the tenant and optional initiating Factory. */\n sign(orgId: string, userId: string, context?: { factoryProjectId?: string }): string;\n /** Verify a signed `state`; returns the bound tenant, or `null` if invalid. */\n verify(state: string | undefined): StateTenant | null;\n /**\n * True when the signer was built from an explicit deployment-stable secret.\n * False means a per-process random secret: fine for single-process/local\n * dev, broken for multi-replica deploys (see module docs).\n */\n readonly stable: boolean;\n}\n\ninterface StatePayload {\n orgId: string;\n userId: string;\n factoryProjectId?: string;\n nonce: string;\n issuedAt: number;\n}\n\n/** Signed `state` values expire after this window to bound the CSRF token. */\nconst STATE_MAX_AGE_MS = 10 * 60 * 1000;\n\n/**\n * Create a state signer. With a `secret`, the signer is deployment-stable\n * (`stable: true`); without one it falls back to a per-process random secret\n * (`stable: false`).\n */\nexport function createStateSigner(secret?: string): StateSigner {\n const stable = typeof secret === 'string' && secret.length > 0;\n const key = stable ? secret : Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(32))).toString('hex');\n return {\n stable,\n sign(orgId: string, userId: string, context?: { factoryProjectId?: string }): string {\n const payload: StatePayload = {\n orgId,\n userId,\n ...(context?.factoryProjectId ? { factoryProjectId: context.factoryProjectId } : {}),\n nonce: Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(8))).toString('hex'),\n issuedAt: Date.now(),\n };\n const body = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');\n const sig = createHmac('sha256', key).update(body).digest('base64url');\n return `${body}.${sig}`;\n },\n verify(state: string | undefined): StateTenant | null {\n if (!state) return null;\n const dot = state.lastIndexOf('.');\n if (dot <= 0) return null;\n const body = state.slice(0, dot);\n const sig = state.slice(dot + 1);\n const expected = createHmac('sha256', key).update(body).digest('base64url');\n const sigBuf = Buffer.from(sig);\n const expectedBuf = Buffer.from(expected);\n if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {\n return null;\n }\n try {\n const parsed = JSON.parse(Buffer.from(body, 'base64url').toString('utf8')) as StatePayload;\n if (typeof parsed.orgId !== 'string' || typeof parsed.userId !== 'string') return null;\n if (typeof parsed.issuedAt !== 'number' || !Number.isFinite(parsed.issuedAt)) return null;\n if (typeof parsed.nonce !== 'string' || parsed.nonce.length === 0) return null;\n if (\n parsed.factoryProjectId !== undefined &&\n (typeof parsed.factoryProjectId !== 'string' || parsed.factoryProjectId.length === 0)\n ) {\n return null;\n }\n const age = Date.now() - parsed.issuedAt;\n if (age < 0 || age > STATE_MAX_AGE_MS) return null;\n return {\n orgId: parsed.orgId,\n userId: parsed.userId,\n ...(parsed.factoryProjectId ? { factoryProjectId: parsed.factoryProjectId } : {}),\n nonce: parsed.nonce,\n };\n } catch {\n return null;\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,MAAM,mBAAmB;;;;;;AAOzB,SAAgB,kBAAkB,QAA8B;CAC9D,MAAM,SAAS,OAAO,WAAW,YAAY,OAAO,SAAS;CAC7D,MAAM,MAAM,SAAS,SAAS,OAAO,KAAK,WAAW,OAAO,gCAAgB,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK;CAC/G,OAAO;EACL;EACA,KAAK,OAAe,QAAgB,SAAiD;GACnF,MAAM,UAAwB;IAC5B;IACA;IACA,GAAI,SAAS,mBAAmB,EAAE,kBAAkB,QAAQ,iBAAiB,IAAI,CAAC;IAClF,OAAO,OAAO,KAAK,WAAW,OAAO,gCAAgB,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK;IACvF,UAAU,KAAK,IAAI;GACrB;GACA,MAAM,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO,GAAG,MAAM,CAAC,CAAC,SAAS,WAAW;GAE9E,OAAO,GAAG,KAAK,GADH,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,WACtC;EACtB;EACA,OAAO,OAA+C;GACpD,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,MAAM,MAAM,YAAY,GAAG;GACjC,IAAI,OAAO,GAAG,OAAO;GACrB,MAAM,OAAO,MAAM,MAAM,GAAG,GAAG;GAC/B,MAAM,MAAM,MAAM,MAAM,MAAM,CAAC;GAC/B,MAAM,WAAW,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,WAAW;GAC1E,MAAM,SAAS,OAAO,KAAK,GAAG;GAC9B,MAAM,cAAc,OAAO,KAAK,QAAQ;GACxC,IAAI,OAAO,WAAW,YAAY,UAAU,CAAC,gBAAgB,QAAQ,WAAW,GAC9E,OAAO;GAET,IAAI;IACF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;IACzE,IAAI,OAAO,OAAO,UAAU,YAAY,OAAO,OAAO,WAAW,UAAU,OAAO;IAClF,IAAI,OAAO,OAAO,aAAa,YAAY,CAAC,OAAO,SAAS,OAAO,QAAQ,GAAG,OAAO;IACrF,IAAI,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,WAAW,GAAG,OAAO;IAC1E,IACE,OAAO,qBAAqB,KAAA,MAC3B,OAAO,OAAO,qBAAqB,YAAY,OAAO,iBAAiB,WAAW,IAEnF,OAAO;IAET,MAAM,MAAM,KAAK,IAAI,IAAI,OAAO;IAChC,IAAI,MAAM,KAAK,MAAM,kBAAkB,OAAO;IAC9C,OAAO;KACL,OAAO,OAAO;KACd,QAAQ,OAAO;KACf,GAAI,OAAO,mBAAmB,EAAE,kBAAkB,OAAO,iBAAiB,IAAI,CAAC;KAC/E,OAAO,OAAO;IAChB;GACF,QAAQ;IACN,OAAO;GACT;EACF;CACF;AACF"}
@@ -8,6 +8,7 @@ export declare const AUDIT_ACTIONS: {
8
8
  readonly git: readonly ["commit", "push", "pr_opened"];
9
9
  readonly agent: readonly ["commit", "push", "pr_opened", "signaled"];
10
10
  readonly intake: readonly ["config_updated", "binding_updated", "label_route_updated"];
11
+ readonly session: readonly ["owner_transferred"];
11
12
  };
12
13
  export type AuditNamespace = keyof typeof AUDIT_ACTIONS;
13
14
  export type AuditAction = {
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/actions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;;;CAiBhB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,aAAa,CAAC;AAExD,MAAM,MAAM,WAAW,GAAG;KACvB,SAAS,IAAI,cAAc,GAAG,WAAW,SAAS,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE;CACnG,CAAC,cAAc,CAAC,CAAC;AAElB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc,CAEvE;AAED,wBAAgB,eAAe,IAAI,cAAc,EAAE,CAElD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIxG;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,WAAW,CAGjE;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,EAAE,CAExF"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/actions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;;;;CAkBhB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,aAAa,CAAC;AAExD,MAAM,MAAM,WAAW,GAAG;KACvB,SAAS,IAAI,cAAc,GAAG,WAAW,SAAS,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE;CACnG,CAAC,cAAc,CAAC,CAAC;AAElB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc,CAEvE;AAED,wBAAgB,eAAe,IAAI,cAAc,EAAE,CAElD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIxG;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,WAAW,CAGjE;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,EAAE,CAExF"}
@@ -40,7 +40,8 @@ const AUDIT_ACTIONS = {
40
40
  "config_updated",
41
41
  "binding_updated",
42
42
  "label_route_updated"
43
- ]
43
+ ],
44
+ session: ["owner_transferred"]
44
45
  };
45
46
  function isAuditNamespace(value) {
46
47
  return Object.hasOwn(AUDIT_ACTIONS, value);
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","names":[],"sources":["../../../../src/storage/domains/audit/actions.ts"],"sourcesContent":["/**\n * Every action the trail can hold, by namespace. Register new ones in the\n * WorkOS dashboard under Audit Logs → Events before the mirror accepts them.\n */\nexport const AUDIT_ACTIONS = {\n work_item: [\n 'created',\n 'updated',\n 'deleted',\n 'stage_moved',\n 'transition_rejected',\n 'comment_created',\n 'comment_edited',\n 'comment_deleted',\n 'comment_mentioned',\n 'labels_reconciled',\n ],\n run: ['started', 'ended', 'approved', 'dismissed', 'retry', 'queued', 'rejected'],\n git: ['commit', 'push', 'pr_opened'],\n agent: ['commit', 'push', 'pr_opened', 'signaled'],\n intake: ['config_updated', 'binding_updated', 'label_route_updated'],\n} as const;\n\nexport type AuditNamespace = keyof typeof AUDIT_ACTIONS;\n\nexport type AuditAction = {\n [Namespace in AuditNamespace]: `factory.${Namespace}.${(typeof AUDIT_ACTIONS)[Namespace][number]}`;\n}[AuditNamespace];\n\nexport function isAuditNamespace(value: string): value is AuditNamespace {\n return Object.hasOwn(AUDIT_ACTIONS, value);\n}\n\nexport function auditNamespaces(): AuditNamespace[] {\n return Object.keys(AUDIT_ACTIONS).filter(isAuditNamespace);\n}\n\nexport function parseAuditAction(action: string): { namespace: AuditNamespace; leaf: string } | undefined {\n const [prefix, namespace, leaf, ...rest] = action.split('.');\n if (prefix !== 'factory' || namespace === undefined || leaf === undefined || rest.length > 0) return undefined;\n return isAuditNamespace(namespace) ? { namespace, leaf } : undefined;\n}\n\nexport function isAuditAction(value: string): value is AuditAction {\n const parsed = parseAuditAction(value);\n return parsed !== undefined && AUDIT_ACTIONS[parsed.namespace].some(leaf => leaf === parsed.leaf);\n}\n\nexport function auditActionsInNamespaces(namespaces: readonly AuditNamespace[]): string[] {\n return namespaces.flatMap(namespace => AUDIT_ACTIONS[namespace].map(leaf => `factory.${namespace}.${leaf}`));\n}\n"],"mappings":";;;;;AAIA,MAAa,gBAAgB;CAC3B,WAAW;EACT;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,KAAK;EAAC;EAAW;EAAS;EAAY;EAAa;EAAS;EAAU;CAAU;CAChF,KAAK;EAAC;EAAU;EAAQ;CAAW;CACnC,OAAO;EAAC;EAAU;EAAQ;EAAa;CAAU;CACjD,QAAQ;EAAC;EAAkB;EAAmB;CAAqB;AACrE;AAQA,SAAgB,iBAAiB,OAAwC;CACvE,OAAO,OAAO,OAAO,eAAe,KAAK;AAC3C;AAEA,SAAgB,kBAAoC;CAClD,OAAO,OAAO,KAAK,aAAa,CAAC,CAAC,OAAO,gBAAgB;AAC3D;AAEA,SAAgB,iBAAiB,QAAyE;CACxG,MAAM,CAAC,QAAQ,WAAW,MAAM,GAAG,QAAQ,OAAO,MAAM,GAAG;CAC3D,IAAI,WAAW,aAAa,cAAc,KAAA,KAAa,SAAS,KAAA,KAAa,KAAK,SAAS,GAAG,OAAO,KAAA;CACrG,OAAO,iBAAiB,SAAS,IAAI;EAAE;EAAW;CAAK,IAAI,KAAA;AAC7D;AAEA,SAAgB,cAAc,OAAqC;CACjE,MAAM,SAAS,iBAAiB,KAAK;CACrC,OAAO,WAAW,KAAA,KAAa,cAAc,OAAO,UAAU,CAAC,MAAK,SAAQ,SAAS,OAAO,IAAI;AAClG;AAEA,SAAgB,yBAAyB,YAAiD;CACxF,OAAO,WAAW,SAAQ,cAAa,cAAc,UAAU,CAAC,KAAI,SAAQ,WAAW,UAAU,GAAG,MAAM,CAAC;AAC7G"}
1
+ {"version":3,"file":"actions.js","names":[],"sources":["../../../../src/storage/domains/audit/actions.ts"],"sourcesContent":["/**\n * Every action the trail can hold, by namespace. Register new ones in the\n * WorkOS dashboard under Audit Logs → Events before the mirror accepts them.\n */\nexport const AUDIT_ACTIONS = {\n work_item: [\n 'created',\n 'updated',\n 'deleted',\n 'stage_moved',\n 'transition_rejected',\n 'comment_created',\n 'comment_edited',\n 'comment_deleted',\n 'comment_mentioned',\n 'labels_reconciled',\n ],\n run: ['started', 'ended', 'approved', 'dismissed', 'retry', 'queued', 'rejected'],\n git: ['commit', 'push', 'pr_opened'],\n agent: ['commit', 'push', 'pr_opened', 'signaled'],\n intake: ['config_updated', 'binding_updated', 'label_route_updated'],\n session: ['owner_transferred'],\n} as const;\n\nexport type AuditNamespace = keyof typeof AUDIT_ACTIONS;\n\nexport type AuditAction = {\n [Namespace in AuditNamespace]: `factory.${Namespace}.${(typeof AUDIT_ACTIONS)[Namespace][number]}`;\n}[AuditNamespace];\n\nexport function isAuditNamespace(value: string): value is AuditNamespace {\n return Object.hasOwn(AUDIT_ACTIONS, value);\n}\n\nexport function auditNamespaces(): AuditNamespace[] {\n return Object.keys(AUDIT_ACTIONS).filter(isAuditNamespace);\n}\n\nexport function parseAuditAction(action: string): { namespace: AuditNamespace; leaf: string } | undefined {\n const [prefix, namespace, leaf, ...rest] = action.split('.');\n if (prefix !== 'factory' || namespace === undefined || leaf === undefined || rest.length > 0) return undefined;\n return isAuditNamespace(namespace) ? { namespace, leaf } : undefined;\n}\n\nexport function isAuditAction(value: string): value is AuditAction {\n const parsed = parseAuditAction(value);\n return parsed !== undefined && AUDIT_ACTIONS[parsed.namespace].some(leaf => leaf === parsed.leaf);\n}\n\nexport function auditActionsInNamespaces(namespaces: readonly AuditNamespace[]): string[] {\n return namespaces.flatMap(namespace => AUDIT_ACTIONS[namespace].map(leaf => `factory.${namespace}.${leaf}`));\n}\n"],"mappings":";;;;;AAIA,MAAa,gBAAgB;CAC3B,WAAW;EACT;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,KAAK;EAAC;EAAW;EAAS;EAAY;EAAa;EAAS;EAAU;CAAU;CAChF,KAAK;EAAC;EAAU;EAAQ;CAAW;CACnC,OAAO;EAAC;EAAU;EAAQ;EAAa;CAAU;CACjD,QAAQ;EAAC;EAAkB;EAAmB;CAAqB;CACnE,SAAS,CAAC,mBAAmB;AAC/B;AAQA,SAAgB,iBAAiB,OAAwC;CACvE,OAAO,OAAO,OAAO,eAAe,KAAK;AAC3C;AAEA,SAAgB,kBAAoC;CAClD,OAAO,OAAO,KAAK,aAAa,CAAC,CAAC,OAAO,gBAAgB;AAC3D;AAEA,SAAgB,iBAAiB,QAAyE;CACxG,MAAM,CAAC,QAAQ,WAAW,MAAM,GAAG,QAAQ,OAAO,MAAM,GAAG;CAC3D,IAAI,WAAW,aAAa,cAAc,KAAA,KAAa,SAAS,KAAA,KAAa,KAAK,SAAS,GAAG,OAAO,KAAA;CACrG,OAAO,iBAAiB,SAAS,IAAI;EAAE;EAAW;CAAK,IAAI,KAAA;AAC7D;AAEA,SAAgB,cAAc,OAAqC;CACjE,MAAM,SAAS,iBAAiB,KAAK;CACrC,OAAO,WAAW,KAAA,KAAa,cAAc,OAAO,UAAU,CAAC,MAAK,SAAQ,SAAS,OAAO,IAAI;AAClG;AAEA,SAAgB,yBAAyB,YAAiD;CACxF,OAAO,WAAW,SAAQ,cAAa,cAAc,UAAU,CAAC,KAAI,SAAQ,WAAW,UAAU,GAAG,MAAM,CAAC;AAC7G"}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-audit.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/agent-audit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIrD,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,cAAc,CAAC;CACzB;AAgCD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,EAC1C,KAAK,EACL,WAAW,EACX,sBAAsB,GACvB,EAAE;IACD,KAAK,EAAE,iBAAiB,CAAC;IACzB,WAAW,EAAE,mBAAmB,CAAC;IACjC,sBAAsB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDhB"}
1
+ {"version":3,"file":"agent-audit.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/agent-audit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIrD,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,cAAc,CAAC;CACzB;AAgCD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,EAC1C,KAAK,EACL,WAAW,EACX,sBAAsB,GACvB,EAAE;IACD,KAAK,EAAE,iBAAiB,CAAC;IACzB,WAAW,EAAE,mBAAmB,CAAC;IACjC,sBAAsB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDhB"}
@@ -39,7 +39,7 @@ async function observeAgentGitAction({ audit, toolContext, resolveRepositorySlug
39
39
  if (typeof rawCommand !== "string") return;
40
40
  const command = executableCommand(rawCommand);
41
41
  const controller = toolContext.context.get("controller");
42
- const worktreePath = controller?.scope;
42
+ const worktreePath = controller?.getState().projectPath || void 0;
43
43
  const targets = worktreePath ? [{
44
44
  type: "worktree",
45
45
  id: worktreePath
@@ -65,7 +65,7 @@ async function observeAgentGitAction({ audit, toolContext, resolveRepositorySlug
65
65
  const pullRequestUrl = runsPullRequestCreate(rawCommand) ? createdPullRequestUrl(toolContext.output) : void 0;
66
66
  if (pullRequestUrl) {
67
67
  const slug = pullRequestRepository(pullRequestUrl);
68
- const slugs = resolveRepositorySlugs && controller?.resourceId ? await resolveRepositorySlugs(controller.resourceId) : [];
68
+ const slugs = resolveRepositorySlugs && controller?.sessionId ? await resolveRepositorySlugs(controller.sessionId) : [];
69
69
  const repository = slug && slugs.find((candidate) => candidate.toLowerCase() === slug.toLowerCase());
70
70
  if (slugs.length > 0 && !repository) console.warn("[Audit] Pull request URL is not in this Factory's environment", { url: pullRequestUrl });
71
71
  await audit.emitAgent({
@@ -1 +1 @@
1
- {"version":3,"file":"agent-audit.js","names":[],"sources":["../../../../src/storage/domains/audit/agent-audit.ts"],"sourcesContent":["/**\n * Git and GitHub actions performed by agents inside runs never touch web routes,\n * so this observer detects their externally-visible side effects in the command\n * and delegates recording to the factory-owned audit domain.\n */\n\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { executableCommand, runsPullRequestCreate } from '../../../session/shell-commands.js';\nimport type { AuditAgentEmitter } from './domain.js';\n\ntype FactorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\ninterface ToolObserverContext {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n context: RequestContext;\n}\n\n/** Match command-start positions while ignoring command text embedded in heredoc bodies. */\nconst GIT_COMMIT_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+commit(?:\\s|$)/;\nconst GIT_PUSH_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push(?:\\s|$)/;\n/** `gh pr create` prints the new pull request URL alone on the last line, and nothing else does. */\nconst CREATED_PULL_REQUEST_URL_RE = /^https:\\/\\/\\S+\\/pull\\/\\d+$/;\n\n/**\n * The pull request `gh pr create` actually opened, or nothing. A command that\n * printed no URL created no pull request: `--dry-run` prints a preview, `--web`\n * prints a `/compare/` link, and a failure prints its exit code last.\n */\nfunction createdPullRequestUrl(output: unknown): string | undefined {\n if (typeof output !== 'string') return undefined;\n const lastLine = output.trimEnd().split('\\n').at(-1)?.trim();\n return lastLine !== undefined && CREATED_PULL_REQUEST_URL_RE.test(lastLine) ? lastLine : undefined;\n}\n\n/** Parse the branch from a plain `git push <remote> <branch>` invocation. */\nfunction parsePushedBranch(command: string): string | undefined {\n const match = command.match(\n /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push\\s+(?:-[^\\s]+\\s+)*([^\\s;&|-][^\\s;&|]*)\\s+([^\\s;&|-][^\\s;&|]*)/,\n );\n return match?.[2];\n}\n\n/** The `owner/repo` a pull request URL names, as GitHub prints it. */\nfunction pullRequestRepository(url: string): string | undefined {\n return url.match(/^https:\\/\\/[^/]+\\/([^/]+\\/[^/]+)\\/pull\\/\\d+$/)?.[1];\n}\n\n/**\n * Detect externally-visible git and GitHub side effects in a completed tool\n * call and record `factory.agent.*` audit events for them. One command can emit\n * multiple events (`git commit && git push` emits both). Never throws.\n *\n * `resolveRepositorySlugs` names the repositories the session may target so a\n * pull request can be attributed to one of them; a URL elsewhere is still\n * audited, with a warning and no `repository`.\n */\nexport async function observeAgentGitAction({\n audit,\n toolContext,\n resolveRepositorySlugs,\n}: {\n audit: AuditAgentEmitter;\n toolContext: ToolObserverContext;\n resolveRepositorySlugs?: (sessionId: string) => Promise<string[]>;\n}): Promise<void> {\n try {\n if (toolContext.toolName !== 'execute_command' || toolContext.error) return;\n const rawCommand = (toolContext.input as { command?: unknown } | undefined)?.command;\n if (typeof rawCommand !== 'string') return;\n const command = executableCommand(rawCommand);\n\n const controller = toolContext.context.get('controller') as\n | AgentControllerRequestContext<FactorySessionState>\n | undefined;\n const worktreePath = controller?.scope;\n const targets = worktreePath ? [{ type: 'worktree', id: worktreePath }] : [];\n\n if (GIT_COMMIT_RE.test(command)) {\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: { action: 'factory.agent.commit', targets },\n });\n }\n\n if (GIT_PUSH_RE.test(command)) {\n const branch = parsePushedBranch(command);\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: { action: 'factory.agent.push', targets, ...(branch ? { metadata: { branch } } : {}) },\n });\n }\n\n const pullRequestUrl = runsPullRequestCreate(rawCommand) ? createdPullRequestUrl(toolContext.output) : undefined;\n if (pullRequestUrl) {\n const slug = pullRequestRepository(pullRequestUrl);\n const slugs =\n resolveRepositorySlugs && controller?.resourceId ? await resolveRepositorySlugs(controller.resourceId) : [];\n const repository = slug && slugs.find(candidate => candidate.toLowerCase() === slug.toLowerCase());\n if (slugs.length > 0 && !repository) {\n console.warn(\"[Audit] Pull request URL is not in this Factory's environment\", { url: pullRequestUrl });\n }\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: {\n action: 'factory.agent.pr_opened',\n targets: [{ type: 'pull_request', id: pullRequestUrl }, ...targets],\n metadata: { url: pullRequestUrl, ...(repository ? { repository } : {}) },\n },\n });\n }\n } catch (err) {\n console.warn('[Audit] Failed to observe agent git action', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n"],"mappings":";;;AAuBA,MAAM,gBAAgB;AACtB,MAAM,cAAc;;AAEpB,MAAM,8BAA8B;;;;;;AAOpC,SAAS,sBAAsB,QAAqC;CAClE,IAAI,OAAO,WAAW,UAAU,OAAO,KAAA;CACvC,MAAM,WAAW,OAAO,QAAQ,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK;CAC3D,OAAO,aAAa,KAAA,KAAa,4BAA4B,KAAK,QAAQ,IAAI,WAAW,KAAA;AAC3F;;AAGA,SAAS,kBAAkB,SAAqC;CAI9D,OAHc,QAAQ,MACpB,8FAES,CAAC,GAAG;AACjB;;AAGA,SAAS,sBAAsB,KAAiC;CAC9D,OAAO,IAAI,MAAM,8CAA8C,CAAC,GAAG;AACrE;;;;;;;;;;AAWA,eAAsB,sBAAsB,EAC1C,OACA,aACA,0BAKgB;CAChB,IAAI;EACF,IAAI,YAAY,aAAa,qBAAqB,YAAY,OAAO;EACrE,MAAM,aAAc,YAAY,OAA6C;EAC7E,IAAI,OAAO,eAAe,UAAU;EACpC,MAAM,UAAU,kBAAkB,UAAU;EAE5C,MAAM,aAAa,YAAY,QAAQ,IAAI,YAAY;EAGvD,MAAM,eAAe,YAAY;EACjC,MAAM,UAAU,eAAe,CAAC;GAAE,MAAM;GAAY,IAAI;EAAa,CAAC,IAAI,CAAC;EAE3E,IAAI,cAAc,KAAK,OAAO,GAC5B,MAAM,MAAM,UAAU;GACpB,gBAAgB,YAAY;GAC5B,OAAO;IAAE,QAAQ;IAAwB;GAAQ;EACnD,CAAC;EAGH,IAAI,YAAY,KAAK,OAAO,GAAG;GAC7B,MAAM,SAAS,kBAAkB,OAAO;GACxC,MAAM,MAAM,UAAU;IACpB,gBAAgB,YAAY;IAC5B,OAAO;KAAE,QAAQ;KAAsB;KAAS,GAAI,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;IAAG;GAC9F,CAAC;EACH;EAEA,MAAM,iBAAiB,sBAAsB,UAAU,IAAI,sBAAsB,YAAY,MAAM,IAAI,KAAA;EACvG,IAAI,gBAAgB;GAClB,MAAM,OAAO,sBAAsB,cAAc;GACjD,MAAM,QACJ,0BAA0B,YAAY,aAAa,MAAM,uBAAuB,WAAW,UAAU,IAAI,CAAC;GAC5G,MAAM,aAAa,QAAQ,MAAM,MAAK,cAAa,UAAU,YAAY,MAAM,KAAK,YAAY,CAAC;GACjG,IAAI,MAAM,SAAS,KAAK,CAAC,YACvB,QAAQ,KAAK,iEAAiE,EAAE,KAAK,eAAe,CAAC;GAEvG,MAAM,MAAM,UAAU;IACpB,gBAAgB,YAAY;IAC5B,OAAO;KACL,QAAQ;KACR,SAAS,CAAC;MAAE,MAAM;MAAgB,IAAI;KAAe,GAAG,GAAG,OAAO;KAClE,UAAU;MAAE,KAAK;MAAgB,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;KAAG;IACzE;GACF,CAAC;EACH;CACF,SAAS,KAAK;EACZ,QAAQ,KAAK,8CAA8C,EACzD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"agent-audit.js","names":[],"sources":["../../../../src/storage/domains/audit/agent-audit.ts"],"sourcesContent":["/**\n * Git and GitHub actions performed by agents inside runs never touch web routes,\n * so this observer detects their externally-visible side effects in the command\n * and delegates recording to the factory-owned audit domain.\n */\n\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { executableCommand, runsPullRequestCreate } from '../../../session/shell-commands.js';\nimport type { AuditAgentEmitter } from './domain.js';\n\ntype FactorySessionState = { factoryProjectId?: string; projectRepositoryId?: string; projectPath?: string };\n\ninterface ToolObserverContext {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n context: RequestContext;\n}\n\n/** Match command-start positions while ignoring command text embedded in heredoc bodies. */\nconst GIT_COMMIT_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+commit(?:\\s|$)/;\nconst GIT_PUSH_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push(?:\\s|$)/;\n/** `gh pr create` prints the new pull request URL alone on the last line, and nothing else does. */\nconst CREATED_PULL_REQUEST_URL_RE = /^https:\\/\\/\\S+\\/pull\\/\\d+$/;\n\n/**\n * The pull request `gh pr create` actually opened, or nothing. A command that\n * printed no URL created no pull request: `--dry-run` prints a preview, `--web`\n * prints a `/compare/` link, and a failure prints its exit code last.\n */\nfunction createdPullRequestUrl(output: unknown): string | undefined {\n if (typeof output !== 'string') return undefined;\n const lastLine = output.trimEnd().split('\\n').at(-1)?.trim();\n return lastLine !== undefined && CREATED_PULL_REQUEST_URL_RE.test(lastLine) ? lastLine : undefined;\n}\n\n/** Parse the branch from a plain `git push <remote> <branch>` invocation. */\nfunction parsePushedBranch(command: string): string | undefined {\n const match = command.match(\n /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push\\s+(?:-[^\\s]+\\s+)*([^\\s;&|-][^\\s;&|]*)\\s+([^\\s;&|-][^\\s;&|]*)/,\n );\n return match?.[2];\n}\n\n/** The `owner/repo` a pull request URL names, as GitHub prints it. */\nfunction pullRequestRepository(url: string): string | undefined {\n return url.match(/^https:\\/\\/[^/]+\\/([^/]+\\/[^/]+)\\/pull\\/\\d+$/)?.[1];\n}\n\n/**\n * Detect externally-visible git and GitHub side effects in a completed tool\n * call and record `factory.agent.*` audit events for them. One command can emit\n * multiple events (`git commit && git push` emits both). Never throws.\n *\n * `resolveRepositorySlugs` names the repositories the session may target so a\n * pull request can be attributed to one of them; a URL elsewhere is still\n * audited, with a warning and no `repository`.\n */\nexport async function observeAgentGitAction({\n audit,\n toolContext,\n resolveRepositorySlugs,\n}: {\n audit: AuditAgentEmitter;\n toolContext: ToolObserverContext;\n resolveRepositorySlugs?: (sessionId: string) => Promise<string[]>;\n}): Promise<void> {\n try {\n if (toolContext.toolName !== 'execute_command' || toolContext.error) return;\n const rawCommand = (toolContext.input as { command?: unknown } | undefined)?.command;\n if (typeof rawCommand !== 'string') return;\n const command = executableCommand(rawCommand);\n\n const controller = toolContext.context.get('controller') as\n | AgentControllerRequestContext<FactorySessionState>\n | undefined;\n // The workspace sets `projectPath` to the session's checkout.\n const worktreePath = controller?.getState().projectPath || undefined;\n const targets = worktreePath ? [{ type: 'worktree', id: worktreePath }] : [];\n\n if (GIT_COMMIT_RE.test(command)) {\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: { action: 'factory.agent.commit', targets },\n });\n }\n\n if (GIT_PUSH_RE.test(command)) {\n const branch = parsePushedBranch(command);\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: { action: 'factory.agent.push', targets, ...(branch ? { metadata: { branch } } : {}) },\n });\n }\n\n const pullRequestUrl = runsPullRequestCreate(rawCommand) ? createdPullRequestUrl(toolContext.output) : undefined;\n if (pullRequestUrl) {\n const slug = pullRequestRepository(pullRequestUrl);\n const slugs =\n resolveRepositorySlugs && controller?.sessionId ? await resolveRepositorySlugs(controller.sessionId) : [];\n const repository = slug && slugs.find(candidate => candidate.toLowerCase() === slug.toLowerCase());\n if (slugs.length > 0 && !repository) {\n console.warn(\"[Audit] Pull request URL is not in this Factory's environment\", { url: pullRequestUrl });\n }\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: {\n action: 'factory.agent.pr_opened',\n targets: [{ type: 'pull_request', id: pullRequestUrl }, ...targets],\n metadata: { url: pullRequestUrl, ...(repository ? { repository } : {}) },\n },\n });\n }\n } catch (err) {\n console.warn('[Audit] Failed to observe agent git action', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n"],"mappings":";;;AAuBA,MAAM,gBAAgB;AACtB,MAAM,cAAc;;AAEpB,MAAM,8BAA8B;;;;;;AAOpC,SAAS,sBAAsB,QAAqC;CAClE,IAAI,OAAO,WAAW,UAAU,OAAO,KAAA;CACvC,MAAM,WAAW,OAAO,QAAQ,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK;CAC3D,OAAO,aAAa,KAAA,KAAa,4BAA4B,KAAK,QAAQ,IAAI,WAAW,KAAA;AAC3F;;AAGA,SAAS,kBAAkB,SAAqC;CAI9D,OAHc,QAAQ,MACpB,8FAES,CAAC,GAAG;AACjB;;AAGA,SAAS,sBAAsB,KAAiC;CAC9D,OAAO,IAAI,MAAM,8CAA8C,CAAC,GAAG;AACrE;;;;;;;;;;AAWA,eAAsB,sBAAsB,EAC1C,OACA,aACA,0BAKgB;CAChB,IAAI;EACF,IAAI,YAAY,aAAa,qBAAqB,YAAY,OAAO;EACrE,MAAM,aAAc,YAAY,OAA6C;EAC7E,IAAI,OAAO,eAAe,UAAU;EACpC,MAAM,UAAU,kBAAkB,UAAU;EAE5C,MAAM,aAAa,YAAY,QAAQ,IAAI,YAAY;EAIvD,MAAM,eAAe,YAAY,SAAS,CAAC,CAAC,eAAe,KAAA;EAC3D,MAAM,UAAU,eAAe,CAAC;GAAE,MAAM;GAAY,IAAI;EAAa,CAAC,IAAI,CAAC;EAE3E,IAAI,cAAc,KAAK,OAAO,GAC5B,MAAM,MAAM,UAAU;GACpB,gBAAgB,YAAY;GAC5B,OAAO;IAAE,QAAQ;IAAwB;GAAQ;EACnD,CAAC;EAGH,IAAI,YAAY,KAAK,OAAO,GAAG;GAC7B,MAAM,SAAS,kBAAkB,OAAO;GACxC,MAAM,MAAM,UAAU;IACpB,gBAAgB,YAAY;IAC5B,OAAO;KAAE,QAAQ;KAAsB;KAAS,GAAI,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;IAAG;GAC9F,CAAC;EACH;EAEA,MAAM,iBAAiB,sBAAsB,UAAU,IAAI,sBAAsB,YAAY,MAAM,IAAI,KAAA;EACvG,IAAI,gBAAgB;GAClB,MAAM,OAAO,sBAAsB,cAAc;GACjD,MAAM,QACJ,0BAA0B,YAAY,YAAY,MAAM,uBAAuB,WAAW,SAAS,IAAI,CAAC;GAC1G,MAAM,aAAa,QAAQ,MAAM,MAAK,cAAa,UAAU,YAAY,MAAM,KAAK,YAAY,CAAC;GACjG,IAAI,MAAM,SAAS,KAAK,CAAC,YACvB,QAAQ,KAAK,iEAAiE,EAAE,KAAK,eAAe,CAAC;GAEvG,MAAM,MAAM,UAAU;IACpB,gBAAgB,YAAY;IAC5B,OAAO;KACL,QAAQ;KACR,SAAS,CAAC;MAAE,MAAM;MAAgB,IAAI;KAAe,GAAG,GAAG,OAAO;KAClE,UAAU;MAAE,KAAK;MAAgB,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;KAAG;IACzE;GACF,CAAC;EACH;CACF,SAAS,KAAK;EACZ,QAAQ,KAAK,8CAA8C,EACzD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC;CACH;AACF"}
@@ -92,11 +92,16 @@ export interface ListAuditEventsInput {
92
92
  actorId?: string;
93
93
  /** Opaque cursor from a previous page (`nextCursor`). */
94
94
  before?: string;
95
+ /**
96
+ * Page oldest-first, strictly after this cursor: `nextCursor` of a previous
97
+ * `after` page, or `encodeAuditCursor` of a row. Can't be combined with `before`.
98
+ */
99
+ after?: string;
95
100
  limit?: number;
96
101
  }
97
102
  export interface AuditEventPage {
98
103
  events: AuditEventRow[];
99
- /** Pass back as `before` to fetch the next (older) page; absent at the end. */
104
+ /** Pass back as `before` (or `after`, when paging forward) to fetch the next page; absent at the end. */
100
105
  nextCursor?: string;
101
106
  }
102
107
  /** Truncate oversized metadata rather than dropping the whole event. */
@@ -110,6 +115,17 @@ export declare function decodeAuditCursor(cursor: string): {
110
115
  occurredAt: Date;
111
116
  id: string;
112
117
  } | undefined;
118
+ /** An `after` cursor at an instant: paging from it skips every row recorded at or before then. */
119
+ export declare function auditCursorAt(instant: Date): string;
120
+ /**
121
+ * Decode an `after` cursor, or `undefined` when it can't be paged from. Its id
122
+ * must be a UUID like every audit row id: Postgres rejects any other id in the
123
+ * keyset comparison.
124
+ */
125
+ export declare function decodeAuditAfterCursor(cursor: string): {
126
+ occurredAt: Date;
127
+ id: string;
128
+ } | undefined;
113
129
  export declare const AUDIT_EVENTS_SCHEMA: CollectionSchema;
114
130
  /**
115
131
  * Audit event storage, written once against the generic `FactoryStorageOps`
@@ -127,7 +143,7 @@ export declare class AuditStorage extends FactoryStorageDomain {
127
143
  event: AuditEventRow;
128
144
  created: boolean;
129
145
  }>;
130
- /** List an org's audit events newest-first with keyset pagination. */
146
+ /** List an org's audit events newest-first (or oldest-first with `after`) with keyset pagination. */
131
147
  list(input: ListAuditEventsInput): Promise<AuditEventPage>;
132
148
  }
133
149
  //# sourceMappingURL=base.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/base.ts"],"names":[],"mappings":"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"}
@@ -59,6 +59,20 @@ function decodeAuditCursor(cursor) {
59
59
  id
60
60
  };
61
61
  }
62
+ /** An `after` cursor at an instant: paging from it skips every row recorded at or before then. */
63
+ function auditCursorAt(instant) {
64
+ return `${instant.toISOString()}_ffffffff-ffff-ffff-ffff-ffffffffffff`;
65
+ }
66
+ const AUDIT_EVENT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
67
+ /**
68
+ * Decode an `after` cursor, or `undefined` when it can't be paged from. Its id
69
+ * must be a UUID like every audit row id: Postgres rejects any other id in the
70
+ * keyset comparison.
71
+ */
72
+ function decodeAuditAfterCursor(cursor) {
73
+ const decoded = decodeAuditCursor(cursor);
74
+ return decoded && AUDIT_EVENT_ID.test(decoded.id) ? decoded : void 0;
75
+ }
62
76
  const AUDIT_EVENTS_SCHEMA = {
63
77
  name: "audit_events",
64
78
  columns: {
@@ -178,16 +192,20 @@ var AuditStorage = class extends FactoryStorageDomain {
178
192
  occurred_at: input.occurredAt ?? /* @__PURE__ */ new Date()
179
193
  }));
180
194
  }
181
- /** List an org's audit events newest-first with keyset pagination. */
195
+ /** List an org's audit events newest-first (or oldest-first with `after`) with keyset pagination. */
182
196
  async list(input) {
197
+ if (input.before && input.after) throw new Error("Audit list takes `before` or `after`, not both");
183
198
  const limit = clampAuditLimit(input.limit);
184
199
  const where = { org_id: input.orgId };
185
200
  if (input.factoryProjectId) where.factory_project_id = input.factoryProjectId;
186
201
  if (input.actions && input.actions.length > 0) where.action = { in: input.actions };
187
202
  if (input.actorId) where.actor_id = input.actorId;
188
- const cursor = input.before ? decodeAuditCursor(input.before) : void 0;
203
+ const forward = input.after !== void 0;
204
+ const cursor = input.after !== void 0 ? decodeAuditAfterCursor(input.after) : input.before ? decodeAuditCursor(input.before) : void 0;
205
+ if (forward && !cursor) throw new Error(`Invalid audit cursor '${input.after}'`);
206
+ const direction = forward ? "asc" : "desc";
189
207
  const rows = await this.#db.findMany("audit_events", where, {
190
- orderBy: [["occurred_at", "desc"], ["id", "desc"]],
208
+ orderBy: [["occurred_at", direction], ["id", direction]],
191
209
  limit: limit + 1,
192
210
  ...cursor ? { cursor: { values: [cursor.occurredAt, cursor.id] } } : {}
193
211
  });
@@ -201,6 +219,6 @@ var AuditStorage = class extends FactoryStorageDomain {
201
219
  }
202
220
  };
203
221
  //#endregion
204
- export { ACTOR_PROFILE_METADATA_KEY, AUDIT_EVENTS_SCHEMA, AuditStorage, auditActorProfile, boundAuditMetadata, clampAuditLimit, decodeAuditCursor, encodeAuditCursor };
222
+ export { ACTOR_PROFILE_METADATA_KEY, AUDIT_EVENTS_SCHEMA, AuditStorage, auditActorProfile, auditCursorAt, boundAuditMetadata, clampAuditLimit, decodeAuditAfterCursor, decodeAuditCursor, encodeAuditCursor };
205
223
 
206
224
  //# sourceMappingURL=base.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":["#db","#insert"],"sources":["../../../../src/storage/domains/audit/base.ts"],"sourcesContent":["/**\n * Factory audit events domain — the append-only \"who did what, when\" trail\n * behind the software factory.\n *\n * Rows are append-only: there is no update/delete API, and the table is the\n * local source of truth even when the WorkOS Audit Logs mirror is unavailable.\n * Tenancy is org-first, like `work_items`: `actor_id` records who acted but\n * never scopes reads.\n *\n * The actions the trail holds are listed in `./actions.ts`.\n *\n * Agent events carry `actor_type = 'agent'` with `actor_id = 'agent:<threadId>'`\n * and `metadata.startedBy = <userId>` chaining accountability back to the human\n * whose message drove the run. Rule-driven events carry `actor_type = 'system'`.\n */\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\n\nimport type { AuditActorType } from './actors.js';\n\n/** What an audit event acted on (WorkOS Audit Logs target shape). */\nexport interface AuditTarget {\n /** Target kind, e.g. 'work_item', 'worktree', 'issue', 'pull_request'. */\n type: string;\n /** Stable identifier of the target (row id, branch name, issue number...). */\n id: string;\n /** Human-readable label (work-item title, branch name...). */\n name?: string;\n}\n\nexport type { AuditActorType } from './actors.js';\n\n/** Display name and avatar of a human actor, stamped at record time because MastraAuthStudio cannot resolve users by id. */\nexport interface AuditActorProfileInput {\n name?: string;\n avatarUrl?: string;\n}\n\nexport const ACTOR_PROFILE_METADATA_KEY = '__actorProfile';\n\nexport function auditActorProfile(\n user: { name?: string; email?: string; avatarUrl?: string } | undefined,\n): AuditActorProfileInput | undefined {\n const name = user?.name?.trim() || user?.email?.trim();\n const avatarUrl = user?.avatarUrl?.trim();\n if (!name && !avatarUrl) return undefined;\n return { ...(name ? { name } : {}), ...(avatarUrl ? { avatarUrl } : {}) };\n}\n\n/** Request context captured alongside the event. */\nexport interface AuditContext {\n /** Client IP (first hop of `x-forwarded-for`) when available. */\n location?: string;\n /** Request `user-agent` header when available. */\n userAgent?: string;\n}\n\n/** One persisted audit event. */\nexport interface AuditEventRow {\n id: string;\n /** Owning WorkOS organization id — the trail is org-wide. */\n orgId: string;\n /** WorkOS user id of whoever performed the action, or `agent:<threadId>`. */\n actorId: string;\n /** Whether a human or an agent (inside a run) performed the action. */\n actorType: AuditActorType;\n /** Dot-namespaced action, e.g. 'factory.work_item.stage_moved'. */\n action: string;\n /** What was acted on. */\n targets: AuditTarget[];\n /** Bounded event summary — never full payloads, never secrets. */\n metadata: Record<string, unknown>;\n /** Factory project the event is scoped to; null for org-level events. */\n factoryProjectId: string | null;\n /** Project-repository link affected by a repository-specific action. */\n projectRepositoryId: string | null;\n /** Request context (`x-forwarded-for` / `user-agent`). */\n context: AuditContext;\n occurredAt: Date;\n}\n\nexport interface RecordAuditEventInput<Action extends string = string> {\n idempotencyKey?: string;\n orgId: string;\n actorId: string;\n /** Who performed the action; defaults to 'human'. */\n actorType?: AuditActorType;\n actorProfile?: AuditActorProfileInput;\n action: Action;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n factoryProjectId?: string;\n projectRepositoryId?: string;\n context?: AuditContext;\n occurredAt?: Date;\n}\n\n/** A fully-normalized event ready to persist (id assigned on insert). */\nexport type AuditEventInsert = Omit<AuditEventRow, 'id'>;\n\nexport interface ListAuditEventsInput {\n orgId: string;\n factoryProjectId?: string;\n /** Restrict to these actions (exact match). */\n actions?: string[];\n actorId?: string;\n /** Opaque cursor from a previous page (`nextCursor`). */\n before?: string;\n limit?: number;\n}\n\nexport interface AuditEventPage {\n events: AuditEventRow[];\n /** Pass back as `before` to fetch the next (older) page; absent at the end. */\n nextCursor?: string;\n}\n\n/** Metadata is a bounded summary — never full payloads, never secrets. */\nconst MAX_METADATA_JSON_LENGTH = 4096;\n\nconst DEFAULT_PAGE_SIZE = 50;\nconst MAX_PAGE_SIZE = 200;\n\n/** Truncate oversized metadata rather than dropping the whole event. */\nexport function boundAuditMetadata(metadata: Record<string, unknown> | undefined): Record<string, unknown> {\n if (!metadata) return {};\n try {\n if (JSON.stringify(metadata).length <= MAX_METADATA_JSON_LENGTH) return metadata;\n } catch {\n return { truncated: true };\n }\n return { truncated: true };\n}\n\n/** Normalize a requested page size to a finite integer in `[1, MAX_PAGE_SIZE]`. */\nexport function clampAuditLimit(limit: number | undefined): number {\n const normalized = typeof limit === 'number' && Number.isFinite(limit) ? Math.trunc(limit) : DEFAULT_PAGE_SIZE;\n return Math.min(Math.max(normalized, 1), MAX_PAGE_SIZE);\n}\n\n/** Encode the `(occurredAt, id)` keyset cursor of a row. */\nexport function encodeAuditCursor(row: AuditEventRow): string {\n return `${row.occurredAt.toISOString()}_${row.id}`;\n}\n\n/** Decode a cursor back into its `(occurredAt, id)` parts, or `undefined`. */\nexport function decodeAuditCursor(cursor: string): { occurredAt: Date; id: string } | undefined {\n const sep = cursor.lastIndexOf('_');\n if (sep <= 0) return undefined;\n const occurredAt = new Date(cursor.slice(0, sep));\n const id = cursor.slice(sep + 1);\n if (Number.isNaN(occurredAt.getTime()) || !id) return undefined;\n return { occurredAt, id };\n}\n\nexport const AUDIT_EVENTS_SCHEMA: CollectionSchema = {\n name: 'audit_events',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n actor_id: { type: 'text' },\n actor_type: { type: 'text', default: 'human' },\n action: { type: 'text' },\n targets: { type: 'json' },\n metadata: { type: 'json' },\n factory_project_id: { type: 'text', nullable: true },\n project_repository_id: { type: 'text', nullable: true },\n context: { type: 'json' },\n occurred_at: { type: 'timestamp' },\n },\n indexes: [\n { name: 'audit_events_org_occurred_idx', columns: ['org_id', 'occurred_at'] },\n { name: 'audit_events_org_project_occurred_idx', columns: ['org_id', 'factory_project_id', 'occurred_at'] },\n ],\n};\n\n/** Column shape of one `audit_events` row as returned by ops. */\ninterface AuditEventDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n actor_id: string;\n actor_type: AuditActorType;\n action: string;\n targets: AuditTarget[];\n metadata: Record<string, unknown>;\n factory_project_id: string | null;\n project_repository_id: string | null;\n context: AuditContext;\n occurred_at: Date;\n}\n\nfunction toRow(db: AuditEventDbRow): AuditEventRow {\n return {\n id: db.id,\n orgId: db.org_id,\n actorId: db.actor_id,\n actorType: db.actor_type,\n action: db.action,\n targets: db.targets,\n metadata: db.metadata,\n factoryProjectId: db.factory_project_id,\n projectRepositoryId: db.project_repository_id,\n context: db.context,\n occurredAt: db.occurred_at,\n };\n}\n\n/**\n * Audit event storage, written once against the generic `FactoryStorageOps`\n * surface. `record()` normalizes inputs (defaults, metadata bounding);\n * `list()` is keyset-paginated on `(occurred_at, id)` newest-first.\n */\nexport class AuditStorage extends FactoryStorageDomain {\n constructor() {\n super('audit');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([AUDIT_EVENTS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('audit_events', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n /** Append one audit event. Throws on failure — swallow-on-failure lives in the caller. */\n async record(input: RecordAuditEventInput): Promise<AuditEventRow> {\n if (input.idempotencyKey) return (await this.recordOnce(input)).event;\n return this.#insert(input);\n }\n\n async recordOnce(input: RecordAuditEventInput): Promise<{ event: AuditEventRow; created: boolean }> {\n if (!input.idempotencyKey) throw new Error('An audit idempotency key is required');\n const hash = Buffer.from(\n await globalThis.crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(\n JSON.stringify([input.orgId, input.factoryProjectId ?? null, input.action, input.idempotencyKey]),\n ),\n ),\n ).toString('hex');\n const id = `${hash.slice(0, 8)}-${hash.slice(8, 12)}-8${hash.slice(13, 16)}-a${hash.slice(17, 20)}-${hash.slice(20, 32)}`;\n try {\n return { event: await this.#insert(input, id), created: true };\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n const existing = await this.#db.findOne<AuditEventDbRow>('audit_events', { id, org_id: input.orgId });\n if (!existing) throw error;\n return { event: toRow(existing), created: false };\n }\n }\n\n async #insert(input: RecordAuditEventInput, id?: string): Promise<AuditEventRow> {\n const inserted = await this.#db.insertOne<AuditEventDbRow>('audit_events', {\n ...(id ? { id } : {}),\n org_id: input.orgId,\n actor_id: input.actorId,\n actor_type: input.actorType ?? 'human',\n action: input.action,\n targets: input.targets,\n metadata: boundAuditMetadata(\n input.actorProfile ? { ...input.metadata, [ACTOR_PROFILE_METADATA_KEY]: input.actorProfile } : input.metadata,\n ),\n factory_project_id: input.factoryProjectId ?? null,\n project_repository_id: input.projectRepositoryId ?? null,\n context: input.context ?? {},\n occurred_at: input.occurredAt ?? new Date(),\n });\n return toRow(inserted);\n }\n\n /** List an org's audit events newest-first with keyset pagination. */\n async list(input: ListAuditEventsInput): Promise<AuditEventPage> {\n const limit = clampAuditLimit(input.limit);\n\n const where: CollectionWhere = { org_id: input.orgId };\n if (input.factoryProjectId) where.factory_project_id = input.factoryProjectId;\n if (input.actions && input.actions.length > 0) where.action = { in: input.actions };\n if (input.actorId) where.actor_id = input.actorId;\n\n const cursor = input.before ? decodeAuditCursor(input.before) : undefined;\n\n const rows = await this.#db.findMany<AuditEventDbRow>('audit_events', where, {\n orderBy: [\n ['occurred_at', 'desc'],\n ['id', 'desc'],\n ],\n limit: limit + 1,\n ...(cursor ? { cursor: { values: [cursor.occurredAt, cursor.id] } } : {}),\n });\n\n const events = rows.slice(0, limit).map(toRow);\n const hasMore = rows.length > limit;\n const last = events[events.length - 1];\n return {\n events,\n ...(hasMore && last ? { nextCursor: encodeAuditCursor(last) } : {}),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,MAAa,6BAA6B;AAE1C,SAAgB,kBACd,MACoC;CACpC,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK,MAAM,OAAO,KAAK;CACrD,MAAM,YAAY,MAAM,WAAW,KAAK;CACxC,IAAI,CAAC,QAAQ,CAAC,WAAW,OAAO,KAAA;CAChC,OAAO;EAAE,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EAAI,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CAAG;AAC1E;;AAuEA,MAAM,2BAA2B;AAEjC,MAAM,oBAAoB;AAC1B,MAAM,gBAAgB;;AAGtB,SAAgB,mBAAmB,UAAwE;CACzG,IAAI,CAAC,UAAU,OAAO,CAAC;CACvB,IAAI;EACF,IAAI,KAAK,UAAU,QAAQ,CAAC,CAAC,UAAU,0BAA0B,OAAO;CAC1E,QAAQ;EACN,OAAO,EAAE,WAAW,KAAK;CAC3B;CACA,OAAO,EAAE,WAAW,KAAK;AAC3B;;AAGA,SAAgB,gBAAgB,OAAmC;CAEjE,OAAO,KAAK,IAAI,KAAK,IADF,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,mBACxD,CAAC,GAAG,aAAa;AACxD;;AAGA,SAAgB,kBAAkB,KAA4B;CAC5D,OAAO,GAAG,IAAI,WAAW,YAAY,EAAE,GAAG,IAAI;AAChD;;AAGA,SAAgB,kBAAkB,QAA8D;CAC9F,MAAM,MAAM,OAAO,YAAY,GAAG;CAClC,IAAI,OAAO,GAAG,OAAO,KAAA;CACrB,MAAM,aAAa,IAAI,KAAK,OAAO,MAAM,GAAG,GAAG,CAAC;CAChD,MAAM,KAAK,OAAO,MAAM,MAAM,CAAC;CAC/B,IAAI,OAAO,MAAM,WAAW,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAA;CACtD,OAAO;EAAE;EAAY;CAAG;AAC1B;AAEA,MAAa,sBAAwC;CACnD,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,QAAQ,EAAE,MAAM,OAAO;EACvB,UAAU,EAAE,MAAM,OAAO;EACzB,YAAY;GAAE,MAAM;GAAQ,SAAS;EAAQ;EAC7C,QAAQ,EAAE,MAAM,OAAO;EACvB,SAAS,EAAE,MAAM,OAAO;EACxB,UAAU,EAAE,MAAM,OAAO;EACzB,oBAAoB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACnD,uBAAuB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACtD,SAAS,EAAE,MAAM,OAAO;EACxB,aAAa,EAAE,MAAM,YAAY;CACnC;CACA,SAAS,CACP;EAAE,MAAM;EAAiC,SAAS,CAAC,UAAU,aAAa;CAAE,GAC5E;EAAE,MAAM;EAAyC,SAAS;GAAC;GAAU;GAAsB;EAAa;CAAE,CAC5G;AACF;AAiBA,SAAS,MAAM,IAAoC;CACjD,OAAO;EACL,IAAI,GAAG;EACP,OAAO,GAAG;EACV,SAAS,GAAG;EACZ,WAAW,GAAG;EACd,QAAQ,GAAG;EACX,SAAS,GAAG;EACZ,UAAU,GAAG;EACb,kBAAkB,GAAG;EACrB,qBAAqB,GAAG;EACxB,SAAS,GAAG;EACZ,YAAY,GAAG;CACjB;AACF;;;;;;AAOA,IAAa,eAAb,cAAkC,qBAAqB;CACrD,cAAc;EACZ,MAAM,OAAO;CACf;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,kBAAkB,CAAC,mBAAmB,CAAC;CACpD;CAEA,MAAM,sBAAqC;EACzC,MAAM,KAAK,IAAI,WAAW,gBAAgB,CAAC,CAAC;CAC9C;CAEA,IAAIA,MAAyB;EAC3B,OAAO,KAAK;CACd;;CAGA,MAAM,OAAO,OAAsD;EACjE,IAAI,MAAM,gBAAgB,QAAQ,MAAM,KAAK,WAAW,KAAK,EAAA,CAAG;EAChE,OAAO,KAAKC,QAAQ,KAAK;CAC3B;CAEA,MAAM,WAAW,OAAmF;EAClG,IAAI,CAAC,MAAM,gBAAgB,MAAM,IAAI,MAAM,sCAAsC;EACjF,MAAM,OAAO,OAAO,KAClB,MAAM,WAAW,OAAO,OAAO,OAC7B,WACA,IAAI,YAAY,CAAC,CAAC,OAChB,KAAK,UAAU;GAAC,MAAM;GAAO,MAAM,oBAAoB;GAAM,MAAM;GAAQ,MAAM;EAAc,CAAC,CAClG,CACF,CACF,CAAC,CAAC,SAAS,KAAK;EAChB,MAAM,KAAK,GAAG,KAAK,MAAM,GAAG,CAAC,EAAE,GAAG,KAAK,MAAM,GAAG,EAAE,EAAE,IAAI,KAAK,MAAM,IAAI,EAAE,EAAE,IAAI,KAAK,MAAM,IAAI,EAAE,EAAE,GAAG,KAAK,MAAM,IAAI,EAAE;EACtH,IAAI;GACF,OAAO;IAAE,OAAO,MAAM,KAAKA,QAAQ,OAAO,EAAE;IAAG,SAAS;GAAK;EAC/D,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;GACpD,MAAM,WAAW,MAAM,KAAKD,IAAI,QAAyB,gBAAgB;IAAE;IAAI,QAAQ,MAAM;GAAM,CAAC;GACpG,IAAI,CAAC,UAAU,MAAM;GACrB,OAAO;IAAE,OAAO,MAAM,QAAQ;IAAG,SAAS;GAAM;EAClD;CACF;CAEA,MAAMC,QAAQ,OAA8B,IAAqC;EAgB/E,OAAO,MAAM,MAfU,KAAKD,IAAI,UAA2B,gBAAgB;GACzE,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;GACnB,QAAQ,MAAM;GACd,UAAU,MAAM;GAChB,YAAY,MAAM,aAAa;GAC/B,QAAQ,MAAM;GACd,SAAS,MAAM;GACf,UAAU,mBACR,MAAM,eAAe;IAAE,GAAG,MAAM;KAAW,6BAA6B,MAAM;GAAa,IAAI,MAAM,QACvG;GACA,oBAAoB,MAAM,oBAAoB;GAC9C,uBAAuB,MAAM,uBAAuB;GACpD,SAAS,MAAM,WAAW,CAAC;GAC3B,aAAa,MAAM,8BAAc,IAAI,KAAK;EAC5C,CAAC,CACoB;CACvB;;CAGA,MAAM,KAAK,OAAsD;EAC/D,MAAM,QAAQ,gBAAgB,MAAM,KAAK;EAEzC,MAAM,QAAyB,EAAE,QAAQ,MAAM,MAAM;EACrD,IAAI,MAAM,kBAAkB,MAAM,qBAAqB,MAAM;EAC7D,IAAI,MAAM,WAAW,MAAM,QAAQ,SAAS,GAAG,MAAM,SAAS,EAAE,IAAI,MAAM,QAAQ;EAClF,IAAI,MAAM,SAAS,MAAM,WAAW,MAAM;EAE1C,MAAM,SAAS,MAAM,SAAS,kBAAkB,MAAM,MAAM,IAAI,KAAA;EAEhE,MAAM,OAAO,MAAM,KAAKA,IAAI,SAA0B,gBAAgB,OAAO;GAC3E,SAAS,CACP,CAAC,eAAe,MAAM,GACtB,CAAC,MAAM,MAAM,CACf;GACA,OAAO,QAAQ;GACf,GAAI,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,YAAY,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC;EACzE,CAAC;EAED,MAAM,SAAS,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK;EAC7C,MAAM,UAAU,KAAK,SAAS;EAC9B,MAAM,OAAO,OAAO,OAAO,SAAS;EACpC,OAAO;GACL;GACA,GAAI,WAAW,OAAO,EAAE,YAAY,kBAAkB,IAAI,EAAE,IAAI,CAAC;EACnE;CACF;AACF"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/audit/base.ts"],"sourcesContent":["/**\n * Factory audit events domain — the append-only \"who did what, when\" trail\n * behind the software factory.\n *\n * Rows are append-only: there is no update/delete API, and the table is the\n * local source of truth even when the WorkOS Audit Logs mirror is unavailable.\n * Tenancy is org-first, like `work_items`: `actor_id` records who acted but\n * never scopes reads.\n *\n * The actions the trail holds are listed in `./actions.ts`.\n *\n * Agent events carry `actor_type = 'agent'` with `actor_id = 'agent:<threadId>'`\n * and `metadata.startedBy = <userId>` chaining accountability back to the human\n * whose message drove the run. Rule-driven events carry `actor_type = 'system'`.\n */\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\n\nimport type { AuditActorType } from './actors.js';\n\n/** What an audit event acted on (WorkOS Audit Logs target shape). */\nexport interface AuditTarget {\n /** Target kind, e.g. 'work_item', 'worktree', 'issue', 'pull_request'. */\n type: string;\n /** Stable identifier of the target (row id, branch name, issue number...). */\n id: string;\n /** Human-readable label (work-item title, branch name...). */\n name?: string;\n}\n\nexport type { AuditActorType } from './actors.js';\n\n/** Display name and avatar of a human actor, stamped at record time because MastraAuthStudio cannot resolve users by id. */\nexport interface AuditActorProfileInput {\n name?: string;\n avatarUrl?: string;\n}\n\nexport const ACTOR_PROFILE_METADATA_KEY = '__actorProfile';\n\nexport function auditActorProfile(\n user: { name?: string; email?: string; avatarUrl?: string } | undefined,\n): AuditActorProfileInput | undefined {\n const name = user?.name?.trim() || user?.email?.trim();\n const avatarUrl = user?.avatarUrl?.trim();\n if (!name && !avatarUrl) return undefined;\n return { ...(name ? { name } : {}), ...(avatarUrl ? { avatarUrl } : {}) };\n}\n\n/** Request context captured alongside the event. */\nexport interface AuditContext {\n /** Client IP (first hop of `x-forwarded-for`) when available. */\n location?: string;\n /** Request `user-agent` header when available. */\n userAgent?: string;\n}\n\n/** One persisted audit event. */\nexport interface AuditEventRow {\n id: string;\n /** Owning WorkOS organization id — the trail is org-wide. */\n orgId: string;\n /** WorkOS user id of whoever performed the action, or `agent:<threadId>`. */\n actorId: string;\n /** Whether a human or an agent (inside a run) performed the action. */\n actorType: AuditActorType;\n /** Dot-namespaced action, e.g. 'factory.work_item.stage_moved'. */\n action: string;\n /** What was acted on. */\n targets: AuditTarget[];\n /** Bounded event summary — never full payloads, never secrets. */\n metadata: Record<string, unknown>;\n /** Factory project the event is scoped to; null for org-level events. */\n factoryProjectId: string | null;\n /** Project-repository link affected by a repository-specific action. */\n projectRepositoryId: string | null;\n /** Request context (`x-forwarded-for` / `user-agent`). */\n context: AuditContext;\n occurredAt: Date;\n}\n\nexport interface RecordAuditEventInput<Action extends string = string> {\n idempotencyKey?: string;\n orgId: string;\n actorId: string;\n /** Who performed the action; defaults to 'human'. */\n actorType?: AuditActorType;\n actorProfile?: AuditActorProfileInput;\n action: Action;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n factoryProjectId?: string;\n projectRepositoryId?: string;\n context?: AuditContext;\n occurredAt?: Date;\n}\n\n/** A fully-normalized event ready to persist (id assigned on insert). */\nexport type AuditEventInsert = Omit<AuditEventRow, 'id'>;\n\nexport interface ListAuditEventsInput {\n orgId: string;\n factoryProjectId?: string;\n /** Restrict to these actions (exact match). */\n actions?: string[];\n actorId?: string;\n /** Opaque cursor from a previous page (`nextCursor`). */\n before?: string;\n /**\n * Page oldest-first, strictly after this cursor: `nextCursor` of a previous\n * `after` page, or `encodeAuditCursor` of a row. Can't be combined with `before`.\n */\n after?: string;\n limit?: number;\n}\n\nexport interface AuditEventPage {\n events: AuditEventRow[];\n /** Pass back as `before` (or `after`, when paging forward) to fetch the next page; absent at the end. */\n nextCursor?: string;\n}\n\n/** Metadata is a bounded summary — never full payloads, never secrets. */\nconst MAX_METADATA_JSON_LENGTH = 4096;\n\nconst DEFAULT_PAGE_SIZE = 50;\nconst MAX_PAGE_SIZE = 200;\n\n/** Truncate oversized metadata rather than dropping the whole event. */\nexport function boundAuditMetadata(metadata: Record<string, unknown> | undefined): Record<string, unknown> {\n if (!metadata) return {};\n try {\n if (JSON.stringify(metadata).length <= MAX_METADATA_JSON_LENGTH) return metadata;\n } catch {\n return { truncated: true };\n }\n return { truncated: true };\n}\n\n/** Normalize a requested page size to a finite integer in `[1, MAX_PAGE_SIZE]`. */\nexport function clampAuditLimit(limit: number | undefined): number {\n const normalized = typeof limit === 'number' && Number.isFinite(limit) ? Math.trunc(limit) : DEFAULT_PAGE_SIZE;\n return Math.min(Math.max(normalized, 1), MAX_PAGE_SIZE);\n}\n\n/** Encode the `(occurredAt, id)` keyset cursor of a row. */\nexport function encodeAuditCursor(row: AuditEventRow): string {\n return `${row.occurredAt.toISOString()}_${row.id}`;\n}\n\n/** Decode a cursor back into its `(occurredAt, id)` parts, or `undefined`. */\nexport function decodeAuditCursor(cursor: string): { occurredAt: Date; id: string } | undefined {\n const sep = cursor.lastIndexOf('_');\n if (sep <= 0) return undefined;\n const occurredAt = new Date(cursor.slice(0, sep));\n const id = cursor.slice(sep + 1);\n if (Number.isNaN(occurredAt.getTime()) || !id) return undefined;\n return { occurredAt, id };\n}\n\n/** An `after` cursor at an instant: paging from it skips every row recorded at or before then. */\nexport function auditCursorAt(instant: Date): string {\n return `${instant.toISOString()}_ffffffff-ffff-ffff-ffff-ffffffffffff`;\n}\n\nconst AUDIT_EVENT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\n/**\n * Decode an `after` cursor, or `undefined` when it can't be paged from. Its id\n * must be a UUID like every audit row id: Postgres rejects any other id in the\n * keyset comparison.\n */\nexport function decodeAuditAfterCursor(cursor: string): { occurredAt: Date; id: string } | undefined {\n const decoded = decodeAuditCursor(cursor);\n return decoded && AUDIT_EVENT_ID.test(decoded.id) ? decoded : undefined;\n}\n\nexport const AUDIT_EVENTS_SCHEMA: CollectionSchema = {\n name: 'audit_events',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n actor_id: { type: 'text' },\n actor_type: { type: 'text', default: 'human' },\n action: { type: 'text' },\n targets: { type: 'json' },\n metadata: { type: 'json' },\n factory_project_id: { type: 'text', nullable: true },\n project_repository_id: { type: 'text', nullable: true },\n context: { type: 'json' },\n occurred_at: { type: 'timestamp' },\n },\n indexes: [\n { name: 'audit_events_org_occurred_idx', columns: ['org_id', 'occurred_at'] },\n { name: 'audit_events_org_project_occurred_idx', columns: ['org_id', 'factory_project_id', 'occurred_at'] },\n ],\n};\n\n/** Column shape of one `audit_events` row as returned by ops. */\ninterface AuditEventDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n actor_id: string;\n actor_type: AuditActorType;\n action: string;\n targets: AuditTarget[];\n metadata: Record<string, unknown>;\n factory_project_id: string | null;\n project_repository_id: string | null;\n context: AuditContext;\n occurred_at: Date;\n}\n\nfunction toRow(db: AuditEventDbRow): AuditEventRow {\n return {\n id: db.id,\n orgId: db.org_id,\n actorId: db.actor_id,\n actorType: db.actor_type,\n action: db.action,\n targets: db.targets,\n metadata: db.metadata,\n factoryProjectId: db.factory_project_id,\n projectRepositoryId: db.project_repository_id,\n context: db.context,\n occurredAt: db.occurred_at,\n };\n}\n\n/**\n * Audit event storage, written once against the generic `FactoryStorageOps`\n * surface. `record()` normalizes inputs (defaults, metadata bounding);\n * `list()` is keyset-paginated on `(occurred_at, id)` newest-first.\n */\nexport class AuditStorage extends FactoryStorageDomain {\n constructor() {\n super('audit');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([AUDIT_EVENTS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('audit_events', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n /** Append one audit event. Throws on failure — swallow-on-failure lives in the caller. */\n async record(input: RecordAuditEventInput): Promise<AuditEventRow> {\n if (input.idempotencyKey) return (await this.recordOnce(input)).event;\n return this.#insert(input);\n }\n\n async recordOnce(input: RecordAuditEventInput): Promise<{ event: AuditEventRow; created: boolean }> {\n if (!input.idempotencyKey) throw new Error('An audit idempotency key is required');\n const hash = Buffer.from(\n await globalThis.crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(\n JSON.stringify([input.orgId, input.factoryProjectId ?? null, input.action, input.idempotencyKey]),\n ),\n ),\n ).toString('hex');\n const id = `${hash.slice(0, 8)}-${hash.slice(8, 12)}-8${hash.slice(13, 16)}-a${hash.slice(17, 20)}-${hash.slice(20, 32)}`;\n try {\n return { event: await this.#insert(input, id), created: true };\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n const existing = await this.#db.findOne<AuditEventDbRow>('audit_events', { id, org_id: input.orgId });\n if (!existing) throw error;\n return { event: toRow(existing), created: false };\n }\n }\n\n async #insert(input: RecordAuditEventInput, id?: string): Promise<AuditEventRow> {\n const inserted = await this.#db.insertOne<AuditEventDbRow>('audit_events', {\n ...(id ? { id } : {}),\n org_id: input.orgId,\n actor_id: input.actorId,\n actor_type: input.actorType ?? 'human',\n action: input.action,\n targets: input.targets,\n metadata: boundAuditMetadata(\n input.actorProfile ? { ...input.metadata, [ACTOR_PROFILE_METADATA_KEY]: input.actorProfile } : input.metadata,\n ),\n factory_project_id: input.factoryProjectId ?? null,\n project_repository_id: input.projectRepositoryId ?? null,\n context: input.context ?? {},\n occurred_at: input.occurredAt ?? new Date(),\n });\n return toRow(inserted);\n }\n\n /** List an org's audit events newest-first (or oldest-first with `after`) with keyset pagination. */\n async list(input: ListAuditEventsInput): Promise<AuditEventPage> {\n if (input.before && input.after) throw new Error('Audit list takes `before` or `after`, not both');\n const limit = clampAuditLimit(input.limit);\n\n const where: CollectionWhere = { org_id: input.orgId };\n if (input.factoryProjectId) where.factory_project_id = input.factoryProjectId;\n if (input.actions && input.actions.length > 0) where.action = { in: input.actions };\n if (input.actorId) where.actor_id = input.actorId;\n\n const forward = input.after !== undefined;\n const cursor =\n input.after !== undefined\n ? decodeAuditAfterCursor(input.after)\n : input.before\n ? decodeAuditCursor(input.before)\n : undefined;\n // Paging forward from an unreadable cursor would replay the whole trail.\n if (forward && !cursor) throw new Error(`Invalid audit cursor '${input.after}'`);\n const direction = forward ? 'asc' : 'desc';\n\n const rows = await this.#db.findMany<AuditEventDbRow>('audit_events', where, {\n orderBy: [\n ['occurred_at', direction],\n ['id', direction],\n ],\n limit: limit + 1,\n ...(cursor ? { cursor: { values: [cursor.occurredAt, cursor.id] } } : {}),\n });\n\n const events = rows.slice(0, limit).map(toRow);\n const hasMore = rows.length > limit;\n const last = events[events.length - 1];\n return {\n events,\n ...(hasMore && last ? { nextCursor: encodeAuditCursor(last) } : {}),\n };\n }\n}\n"],"mappings":"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"}