@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
@@ -8,10 +8,30 @@ import { createTool } from "@mastra/core/tools";
8
8
  //#region src/tools/messaging-tools.ts
9
9
  /** Messages plus replies, per sender session, per target thread, per window. */
10
10
  const SIGNAL_RATE_LIMIT = 10;
11
- const SIGNAL_RATE_WINDOW_MS = 600 * 1e3;
11
+ const SIGNAL_RATE_WINDOW_MS = 6e5;
12
12
  /** How many recent `factory-session` notifications on a thread the rate and duplicate checks read. */
13
13
  const RECENT_SCAN = 100;
14
- const RATE_LIMITED = "Too many messages to that session in the last 10 minutes; wait before sending more.";
14
+ /** How often a waiting queue is reconsidered. Delivery still waits for a free slot in the window. */
15
+ const QUEUE_FLUSH_MS = 1e3;
16
+ /**
17
+ * Waiting messages, per sender session and target thread, oldest first.
18
+ * Process-local: a restart drops what was still waiting, the same as a message
19
+ * that had not been handed to the tool yet. Delivered messages live in the
20
+ * notification store and are unaffected.
21
+ */
22
+ const pendingQueues = /* @__PURE__ */ new Map();
23
+ const flushTimers = /* @__PURE__ */ new Map();
24
+ const queueOwners = /* @__PURE__ */ new WeakMap();
25
+ let nextQueueOwnerId = 0;
26
+ const queueKey = (owner, senderSessionId, threadId) => {
27
+ let id = queueOwners.get(owner);
28
+ if (id === void 0) {
29
+ nextQueueOwnerId += 1;
30
+ id = nextQueueOwnerId;
31
+ queueOwners.set(owner, id);
32
+ }
33
+ return `${id}\0${senderSessionId}\0${threadId}`;
34
+ };
15
35
  const message = z.string().trim().min(1).max(4e3);
16
36
  function senderAttributes(scope) {
17
37
  return {
@@ -53,16 +73,146 @@ function createFactoryMessagingTools(scope, deps) {
53
73
  if (row.visibility === "private" && row.userId !== scope.userId) return "private";
54
74
  return null;
55
75
  };
56
- /** Messages, replies and reports (not progress) this session put on a thread inside the rate window. */
57
- const overRateLimit = (records) => {
76
+ const overRateLimit = (records) => countedInWindow(records).length >= 10;
77
+ /** This session's delivered messages on the thread that still sit inside the window, oldest first. */
78
+ const countedInWindow = (records) => {
58
79
  const since = Date.now() - SIGNAL_RATE_WINDOW_MS;
59
- return records.filter((record) => record.kind !== "progress" && record.kind !== "stage" && record.attributes?.["from-session"] === scope.sessionId && record.createdAt.getTime() >= since).length >= 10;
80
+ return records.filter((record) => record.kind !== "progress" && record.kind !== "stage" && record.attributes?.["from-session"] === scope.sessionId && record.createdAt.getTime() >= since).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
81
+ };
82
+ /**
83
+ * When the next message can be delivered: one window after the oldest message
84
+ * that has to age out to free a slot, given how many are already waiting.
85
+ */
86
+ const nextDeliveryAt = (counted, waiting) => {
87
+ const blocking = counted[counted.length + waiting - 10];
88
+ return (blocking ? blocking.createdAt.getTime() : Date.now()) + SIGNAL_RATE_WINDOW_MS;
60
89
  };
90
+ const queuedResult = (deliverAt) => ({
91
+ delivered: "queued",
92
+ deliverAt: new Date(deliverAt).toISOString(),
93
+ note: `Queued; it will be delivered at about ${new Date(deliverAt).toISOString()}. Do not send it again.`
94
+ });
61
95
  const recent = (notifications, threadId) => notifications.listNotifications({
62
96
  threadId,
63
97
  source: FACTORY_SESSION_NOTIFICATION_SOURCE,
64
98
  limit: RECENT_SCAN
65
99
  });
100
+ const actor = () => ({
101
+ actorId: human ? scope.userId : `agent:${scope.threadId}`,
102
+ actorType: human ? "human" : "agent"
103
+ });
104
+ /**
105
+ * Deliver now, or hold until the window has a slot. The cap limits how fast
106
+ * messages are delivered, not whether they are accepted.
107
+ */
108
+ const accept = async (messaging, target, notification) => {
109
+ const counted = countedInWindow(await recent(messaging.notifications, target.threadId));
110
+ if (counted.length < 10) {
111
+ const result = await deliverFactorySessionNotification({
112
+ controller: messaging.controller,
113
+ sessions: deps.sessions,
114
+ runs: deps.workItems,
115
+ target,
116
+ notification
117
+ });
118
+ if (result.delivered === "skipped") throw unavailable(result);
119
+ await audit({
120
+ targetSessionId: target.sessionId,
121
+ priority: notification.priority ?? "medium",
122
+ kind: notification.kind,
123
+ delivery: result.delivered
124
+ });
125
+ return {
126
+ notificationId: result.notificationId,
127
+ delivered: result.delivered
128
+ };
129
+ }
130
+ const key = queueKey(deps, scope.sessionId, target.threadId);
131
+ const waiting = pendingQueues.get(key)?.length ?? 0;
132
+ const deliverAt = nextDeliveryAt(counted, waiting);
133
+ enqueue(key, {
134
+ notification,
135
+ acceptedAt: Date.now(),
136
+ actor: actor(),
137
+ deliverAt,
138
+ target
139
+ });
140
+ return queuedResult(deliverAt);
141
+ };
142
+ const enqueue = (key, delivery) => {
143
+ const queue = pendingQueues.get(key) ?? [];
144
+ queue.push(delivery);
145
+ queue.sort((a, b) => a.acceptedAt - b.acceptedAt);
146
+ pendingQueues.set(key, queue);
147
+ scheduleFlush(key);
148
+ };
149
+ /**
150
+ * Deliver the oldest waiting message once the window has a free slot, then
151
+ * the next, in the order they were accepted. A target that is gone by then
152
+ * is dropped and recorded, the same way an undeliverable live message is.
153
+ */
154
+ const flush = async (key) => {
155
+ flushTimers.delete(key);
156
+ const queue = pendingQueues.get(key);
157
+ if (!queue || queue.length === 0) {
158
+ pendingQueues.delete(key);
159
+ return;
160
+ }
161
+ const messaging = await deps.messaging();
162
+ if (!messaging) {
163
+ scheduleFlush(key);
164
+ return;
165
+ }
166
+ const head = queue[0];
167
+ if (!head || head.deliverAt > Date.now()) {
168
+ scheduleFlush(key);
169
+ return;
170
+ }
171
+ if (countedInWindow(await recent(messaging.notifications, head.target.threadId)).length >= 10) {
172
+ scheduleFlush(key);
173
+ return;
174
+ }
175
+ queue.shift();
176
+ if (queue.length === 0) pendingQueues.delete(key);
177
+ const result = await deliverFactorySessionNotification({
178
+ controller: messaging.controller,
179
+ sessions: deps.sessions,
180
+ runs: deps.workItems,
181
+ target: head.target,
182
+ notification: head.notification
183
+ });
184
+ await deps.auditRecorder.record({
185
+ orgId: scope.orgId,
186
+ actorId: head.actor.actorId,
187
+ actorType: head.actor.actorType,
188
+ action: "factory.agent.signaled",
189
+ factoryProjectId: scope.factoryProjectId,
190
+ targets: [{
191
+ type: "session",
192
+ id: head.target.sessionId
193
+ }],
194
+ metadata: {
195
+ targetSessionId: head.target.sessionId,
196
+ priority: head.notification.priority ?? "medium",
197
+ kind: head.notification.kind,
198
+ delivery: result.delivered === "skipped" ? "dropped" : result.delivered,
199
+ ...result.delivered === "skipped" ? { reason: result.reason } : {}
200
+ }
201
+ });
202
+ if (queue.length > 0) scheduleFlush(key);
203
+ };
204
+ function scheduleFlush(key) {
205
+ if (flushTimers.has(key)) return;
206
+ if (!pendingQueues.get(key)?.[0]) return;
207
+ const timer = setTimeout(() => {
208
+ flushTimers.delete(key);
209
+ flush(key).catch(() => {
210
+ if ((pendingQueues.get(key)?.length ?? 0) > 0) scheduleFlush(key);
211
+ });
212
+ }, QUEUE_FLUSH_MS);
213
+ timer.unref?.();
214
+ flushTimers.set(key, timer);
215
+ }
66
216
  /** On turns nobody signed in started, only sessions working on items this session follows. */
67
217
  const followedWorkers = async () => {
68
218
  const project = {
@@ -126,37 +276,20 @@ function createFactoryMessagingTools(scope, deps) {
126
276
  sessionId
127
277
  });
128
278
  if (!threadId) throw new Error("The session is not available (thread-not-found).");
129
- if (overRateLimit(await recent(messaging.notifications, threadId))) throw new Error(RATE_LIMITED);
130
- const result = await deliverFactorySessionNotification({
131
- controller: messaging.controller,
132
- sessions: deps.sessions,
133
- target: {
134
- sessionId,
135
- threadId
136
- },
137
- notification: {
138
- kind: "message",
139
- priority,
140
- summary: text,
141
- payload: { message: text },
142
- sourceId: scope.sessionId,
143
- attributes: {
144
- ...await workItemAttribute(sessionId, row.orgId, scope.factoryProjectId),
145
- ...senderAttributes(scope)
146
- }
147
- }
148
- });
149
- if (result.delivered === "skipped") throw unavailable(result);
150
- await audit({
151
- targetSessionId: sessionId,
152
- priority,
279
+ return accept(messaging, {
280
+ sessionId,
281
+ threadId
282
+ }, {
153
283
  kind: "message",
154
- delivery: result.delivered
284
+ priority,
285
+ summary: text,
286
+ payload: { message: text },
287
+ sourceId: scope.sessionId,
288
+ attributes: {
289
+ ...await workItemAttribute(sessionId, row.orgId, scope.factoryProjectId),
290
+ ...senderAttributes(scope)
291
+ }
155
292
  });
156
- return {
157
- notificationId: result.notificationId,
158
- delivered: result.delivered
159
- };
160
293
  }
161
294
  });
162
295
  const reply = createTool({
@@ -192,40 +325,22 @@ function createFactoryMessagingTools(scope, deps) {
192
325
  const blocked = unreachable(await deps.sessions.getBySessionId(fromSession));
193
326
  if (blocked === "not-found") throw new Error(`Message ${inReplyTo} didn't come from a session in this Factory.`);
194
327
  if (blocked === "private") throw new Error(`Message ${inReplyTo} came from a private session this session can't answer.`);
195
- const onSenderThread = await recent(messaging.notifications, fromThread);
196
- if (onSenderThread.some((record) => record.kind === "reply" && record.attributes?.["in-reply-to"] === inReplyTo && record.attributes?.["from-session"] === scope.sessionId)) throw new Error(`You already answered ${inReplyTo}.`);
197
- if (overRateLimit(onSenderThread)) throw new Error(RATE_LIMITED);
198
- const result = await deliverFactorySessionNotification({
199
- controller: messaging.controller,
200
- sessions: deps.sessions,
201
- target: {
202
- sessionId: fromSession,
203
- threadId: fromThread
204
- },
205
- notification: {
206
- kind: "reply",
207
- priority: "high",
208
- summary: text,
209
- payload: { message: text },
210
- sourceId: scope.sessionId,
211
- attributes: {
212
- ...senderAttributes(scope),
213
- "in-reply-to": inReplyTo,
214
- ...status ? { status } : {}
215
- }
216
- }
217
- });
218
- if (result.delivered === "skipped") throw unavailable(result);
219
- await audit({
220
- targetSessionId: fromSession,
221
- priority: "high",
328
+ if ((await recent(messaging.notifications, fromThread)).some((record) => record.kind === "reply" && record.attributes?.["in-reply-to"] === inReplyTo && record.attributes?.["from-session"] === scope.sessionId)) throw new Error(`You already answered ${inReplyTo}.`);
329
+ return accept(messaging, {
330
+ sessionId: fromSession,
331
+ threadId: fromThread
332
+ }, {
222
333
  kind: "reply",
223
- delivery: result.delivered
334
+ priority: "high",
335
+ summary: text,
336
+ payload: { message: text },
337
+ sourceId: scope.sessionId,
338
+ attributes: {
339
+ ...senderAttributes(scope),
340
+ "in-reply-to": inReplyTo,
341
+ ...status ? { status } : {}
342
+ }
224
343
  });
225
- return {
226
- notificationId: result.notificationId,
227
- delivered: result.delivered
228
- };
229
344
  }
230
345
  });
231
346
  /** A report with no message to answer: to the origin, else to the item's followers. */
@@ -278,16 +393,28 @@ function createFactoryMessagingTools(scope, deps) {
278
393
  const results = [];
279
394
  for (const recipient of recipients) {
280
395
  if (status !== "progress" && overRateLimit(await recent(messaging.notifications, recipient.threadId))) {
396
+ const counted = countedInWindow(await recent(messaging.notifications, recipient.threadId));
397
+ const key = queueKey(deps, scope.sessionId, recipient.threadId);
398
+ const waiting = pendingQueues.get(key)?.length ?? 0;
399
+ const at = nextDeliveryAt(counted, waiting);
400
+ enqueue(key, {
401
+ notification,
402
+ acceptedAt: Date.now(),
403
+ actor: actor(),
404
+ deliverAt: at,
405
+ target: recipient
406
+ });
281
407
  results.push({
282
408
  sessionId: recipient.sessionId,
283
- delivered: "skipped",
284
- reason: "rate-limited"
409
+ delivered: "queued",
410
+ deliverAt: new Date(at).toISOString()
285
411
  });
286
412
  continue;
287
413
  }
288
414
  const result = await deliverFactorySessionNotification({
289
415
  controller: messaging.controller,
290
416
  sessions: deps.sessions,
417
+ runs: deps.workItems,
291
418
  target: recipient,
292
419
  notification
293
420
  });
@@ -303,11 +430,15 @@ function createFactoryMessagingTools(scope, deps) {
303
430
  delivery: result.delivered
304
431
  });
305
432
  }
306
- const delivered = results.filter((result) => result.delivered !== "skipped").length;
307
- if (delivered === 0 && results.every((result) => result.reason === "rate-limited")) throw new Error(RATE_LIMITED);
433
+ const delivered = results.filter((result) => result.delivered !== "skipped" && result.delivered !== "queued").length;
434
+ const queued = results.filter((result) => result.delivered === "queued").length;
308
435
  return {
309
436
  delivered,
310
- recipients: results
437
+ recipients: results,
438
+ ...queued > 0 ? {
439
+ queued,
440
+ note: "Queued past the delivery cap. It will be delivered when the window allows. Do not send it again."
441
+ } : {}
311
442
  };
312
443
  }
313
444
  const listSide = createTool({
@@ -327,7 +458,7 @@ function createFactoryMessagingTools(scope, deps) {
327
458
  sessions: deps.sessions,
328
459
  workItems: deps.workItems,
329
460
  notifications: messaging?.notifications ?? null,
330
- isRunning: async (sessionId) => (await messaging?.controller.getSessionByResource(sessionId))?.stream.isActive() ?? false,
461
+ isRunning: async (sessionId) => (await messaging?.controller.getSession(sessionId))?.stream.isActive() ?? false,
331
462
  model: messaging ? (sessionId) => sessionModel(messaging.controller, deps.sessions, deps.workItems, sessionId) : void 0
332
463
  }, scope)).filter((row) => (!state || row.state === state) && (muted === void 0 || row.mutedAt !== null === muted)) };
333
464
  }
@@ -1 +1 @@
1
- {"version":3,"file":"messaging-tools.js","names":[],"sources":["../../src/tools/messaging-tools.ts"],"sourcesContent":["/**\n * Messages between Factory sessions. A user session can message any session in\n * its Factory it can see; the receiver answers that exact message. Both\n * directions are `factory-session` notifications on the receiver's stored thread.\n */\n\nimport type { NotificationPriority, NotificationRecord, NotificationsStorage } from '@mastra/core/notifications';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport {\n FACTORY_SESSION_NOTIFICATION_SOURCE,\n deliverFactorySessionNotification,\n resolveSessionThread,\n} from './deliver.js';\nimport type { FactoryDeliveryController, FactoryDeliveryResult } from './deliver.js';\nimport { workItemNumber } from './read-tools.js';\nimport { reportNotification, workItemReportAttributes, workItemReportRecipients } from './reports.js';\nimport { requirePerson } from './scope.js';\nimport type { FactoryToolScope } from './scope.js';\nimport { listSideSessions, sessionModel } from './side-sessions.js';\n\n/** Messages plus replies, per sender session, per target thread, per window. */\nexport const SIGNAL_RATE_LIMIT = 10;\nexport const SIGNAL_RATE_WINDOW_MS = 10 * 60 * 1000;\n/** How many recent `factory-session` notifications on a thread the rate and duplicate checks read. */\nconst RECENT_SCAN = 100;\nconst RATE_LIMITED = 'Too many messages to that session in the last 10 minutes; wait before sending more.';\n\nexport interface FactoryMessaging {\n controller: FactoryDeliveryController;\n notifications: Pick<NotificationsStorage, 'listNotifications' | 'getNotification'>;\n}\n\nexport interface FactoryMessagingToolDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'listByOrigin' | 'setOriginMuted'>;\n workItems: Pick<\n WorkItemsStorage,\n | 'findActiveRunBindingForSession'\n | 'getForProject'\n | 'listSessionSubscriptions'\n | 'listRunBindings'\n | 'listWorkItemSubscribers'\n >;\n auditRecorder: AuditRecorder;\n /** The live controller and notifications store; resolved per call because they exist only once the mount is up. */\n messaging: () => Promise<FactoryMessaging | null>;\n}\n\nconst message = z.string().trim().min(1).max(4000);\n\nfunction senderAttributes(scope: FactoryToolScope) {\n return {\n 'from-session': scope.sessionId,\n 'from-thread': scope.threadId,\n 'from-user': scope.userId,\n };\n}\n\nfunction unavailable(result: Extract<FactoryDeliveryResult, { delivered: 'skipped' }>) {\n return new Error(`The session is not available (${result.reason}).`);\n}\n\nexport function createFactoryMessagingTools(\n scope: FactoryToolScope,\n deps: FactoryMessagingToolDependencies,\n): IntegrationTools {\n const requireMessaging = async () => {\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Session messaging is not available on this server.');\n return messaging;\n };\n // Only a user-session turn the person started is the person's act. Workers\n // (even when someone types into one) and turns nobody signed in started\n // (wakes, webhooks) act as the thread's agent.\n const human = scope.kind === 'user' && scope.interactive;\n const audit = (input: { targetSessionId: string; priority: NotificationPriority; kind: string; delivery: string }) =>\n deps.auditRecorder.record({\n orgId: scope.orgId,\n actorId: human ? scope.userId : `agent:${scope.threadId}`,\n actorType: human ? 'human' : 'agent',\n action: 'factory.agent.signaled',\n factoryProjectId: scope.factoryProjectId,\n targets: [{ type: 'session', id: input.targetSessionId }],\n metadata: {\n targetSessionId: input.targetSessionId,\n priority: input.priority,\n kind: input.kind,\n delivery: input.delivery,\n },\n });\n /** Why this session can't reach `row`, or null when it can: the rule for message targets and reply targets alike. */\n const unreachable = (row: SourceControlSession | null): 'not-found' | 'private' | null => {\n if (!row || row.orgId !== scope.orgId || row.factoryProjectId !== scope.factoryProjectId) return 'not-found';\n if (row.visibility === 'private' && row.userId !== scope.userId) return 'private';\n return null;\n };\n /** Messages, replies and reports (not progress) this session put on a thread inside the rate window. */\n const overRateLimit = (records: NotificationRecord[]) => {\n const since = Date.now() - SIGNAL_RATE_WINDOW_MS;\n return (\n records.filter(\n record =>\n // Everything this session sends counts except coalescing progress updates.\n record.kind !== 'progress' &&\n record.kind !== 'stage' &&\n record.attributes?.['from-session'] === scope.sessionId &&\n record.createdAt.getTime() >= since,\n ).length >= SIGNAL_RATE_LIMIT\n );\n };\n const recent = (notifications: FactoryMessaging['notifications'], threadId: string) =>\n notifications.listNotifications({ threadId, source: FACTORY_SESSION_NOTIFICATION_SOURCE, limit: RECENT_SCAN });\n\n /** On turns nobody signed in started, only sessions working on items this session follows. */\n const followedWorkers = async () => {\n const project = { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId };\n const follows = await deps.workItems.listSessionSubscriptions({ ...project, sessionId: scope.sessionId });\n const bindings = await Promise.all(\n follows.map(follow => deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, follow.workItemId)),\n );\n const sideSessions = await deps.sessions.listByOrigin({ orgId: scope.orgId, originSessionId: scope.sessionId });\n return new Set([\n ...bindings\n .flat()\n .filter(binding => binding.status === 'active')\n .map(binding => binding.sessionId),\n ...sideSessions.map(session => session.sessionId),\n ]);\n };\n\n const workItemAttribute = async (sessionId: string, orgId: string, factoryProjectId: string) => {\n const binding = await deps.workItems.findActiveRunBindingForSession({ orgId, factoryProjectId, sessionId });\n if (!binding) return {};\n const item = await deps.workItems.getForProject(orgId, factoryProjectId, binding.workItemId);\n if (!item) return {};\n const number = workItemNumber(item);\n return { 'work-item': number === null ? item.id : `#${number}` };\n };\n\n const signal = createTool({\n id: 'factory_signal_session',\n description: [\n 'Send a message to another session in this Factory: a work item role session, or a teammate session you can see.',\n 'Replies come back to this session as `factory-session` notifications with kind `reply` and an `in-reply-to` attribute naming your message.',\n 'Priority `medium` (default) wakes an idle session and waits for a busy one to finish its step; use `urgent` only to make it stop and change course.',\n '`delivered` is `woken` when the session was idle and is now running, `mid-run` when it was already busy and will read the message when its run next checks in.',\n ...(scope.interactive\n ? []\n : ['On this turn you can only message your side sessions and sessions working on items this session follows.']),\n ].join(' '),\n inputSchema: z\n .object({\n sessionId: z.string().trim().min(1).max(200).describe('The session to message.'),\n message: message.describe('What to say. It arrives as-is, so make it self-contained.'),\n priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium'),\n })\n .strict(),\n execute: async ({ sessionId, message: text, priority }) => {\n if (sessionId === scope.sessionId) throw new Error(\"You can't message your own session.\");\n const row = await deps.sessions.getBySessionId(sessionId);\n const blocked = unreachable(row);\n if (blocked === 'not-found' || !row) throw new Error(`No session ${sessionId} was found in this Factory.`);\n if (blocked === 'private') throw new Error(`Session ${sessionId} is private to its owner.`);\n if (!scope.interactive && !(await followedWorkers()).has(sessionId)) {\n throw new Error(\n `On this turn you can only message this session's side sessions and sessions working on items it follows; ${sessionId} isn't one.`,\n );\n }\n\n const messaging = await requireMessaging();\n const threadId = await resolveSessionThread({\n controller: messaging.controller,\n sessions: deps.sessions,\n workItems: deps.workItems,\n sessionId,\n });\n if (!threadId) throw new Error('The session is not available (thread-not-found).');\n\n if (overRateLimit(await recent(messaging.notifications, threadId))) {\n throw new Error(RATE_LIMITED);\n }\n\n const result = await deliverFactorySessionNotification({\n controller: messaging.controller,\n sessions: deps.sessions,\n target: { sessionId, threadId },\n notification: {\n kind: 'message',\n priority,\n summary: text,\n payload: { message: text },\n sourceId: scope.sessionId,\n attributes: {\n ...(await workItemAttribute(sessionId, row.orgId, scope.factoryProjectId)),\n ...senderAttributes(scope),\n },\n },\n });\n if (result.delivered === 'skipped') throw unavailable(result);\n await audit({ targetSessionId: sessionId, priority, kind: 'message', delivery: result.delivered });\n return { notificationId: result.notificationId, delivered: result.delivered };\n },\n });\n\n const reply = createTool({\n id: 'factory_message_origin',\n description: [\n 'Answer a message another Factory session sent you, or report on your work.',\n 'With `inReplyTo` (the id of a `factory-session` notification of kind `message`) the answer goes back to the session and thread that sent it; each message can be answered once.',\n 'Without it, the report goes to whoever follows your work: the session that started you, or everyone following your work item.',\n \"Set `status`: `progress` for an update, `blocked` when you need an answer to go on, `done` when finished, `failed` when you can't finish. Send one `done` or `failed` when you finish.\",\n ].join(' '),\n inputSchema: z\n .object({\n message: message.describe('Your answer.'),\n inReplyTo: z\n .string()\n .trim()\n .min(1)\n .max(200)\n .optional()\n .describe('Id of the message notification you are answering.'),\n status: z.enum(['progress', 'blocked', 'done', 'failed']).optional(),\n })\n .strict(),\n execute: async ({ message: text, inReplyTo, status }) => {\n if (!inReplyTo) return report(text, status);\n const messaging = await requireMessaging();\n // Only a message that reached this session's own thread can be answered,\n // and the answer goes where that message came from: never to an address\n // the model supplies.\n const original: NotificationRecord | null = await messaging.notifications.getNotification({\n threadId: scope.threadId,\n id: inReplyTo,\n });\n if (!original || original.source !== FACTORY_SESSION_NOTIFICATION_SOURCE) {\n throw new Error(`No message ${inReplyTo} was sent to this session.`);\n }\n if (original.kind !== 'message') {\n throw new Error(`Only messages can be answered; ${inReplyTo} is a ${original.kind}.`);\n }\n const fromSession = original.attributes?.['from-session'];\n const fromThread = original.attributes?.['from-thread'];\n if (typeof fromSession !== 'string' || typeof fromThread !== 'string' || original.sourceId !== fromSession) {\n throw new Error(`Message ${inReplyTo} doesn't say which session sent it.`);\n }\n // Notification records can be written outside these tools, so the\n // sender they name gets the same checks a message to it would.\n const blocked = unreachable(await deps.sessions.getBySessionId(fromSession));\n if (blocked === 'not-found') throw new Error(`Message ${inReplyTo} didn't come from a session in this Factory.`);\n if (blocked === 'private') {\n throw new Error(`Message ${inReplyTo} came from a private session this session can't answer.`);\n }\n const onSenderThread = await recent(messaging.notifications, fromThread);\n // Replies count toward the same cap as messages, so records written to\n // this session can't turn it into an unlimited high-priority sender.\n const answered = onSenderThread.some(\n record =>\n record.kind === 'reply' &&\n record.attributes?.['in-reply-to'] === inReplyTo &&\n record.attributes?.['from-session'] === scope.sessionId,\n );\n if (answered) throw new Error(`You already answered ${inReplyTo}.`);\n if (overRateLimit(onSenderThread)) throw new Error(RATE_LIMITED);\n\n const result = await deliverFactorySessionNotification({\n controller: messaging.controller,\n sessions: deps.sessions,\n target: { sessionId: fromSession, threadId: fromThread },\n notification: {\n kind: 'reply',\n priority: 'high',\n summary: text,\n payload: { message: text },\n sourceId: scope.sessionId,\n attributes: {\n ...senderAttributes(scope),\n 'in-reply-to': inReplyTo,\n ...(status ? { status } : {}),\n },\n },\n });\n if (result.delivered === 'skipped') throw unavailable(result);\n await audit({ targetSessionId: fromSession, priority: 'high', kind: 'reply', delivery: result.delivered });\n return { notificationId: result.notificationId, delivered: result.delivered };\n },\n });\n\n /** A report with no message to answer: to the origin, else to the item's followers. */\n async function report(text: string, status: 'progress' | 'blocked' | 'done' | 'failed' | undefined) {\n const sender = { sessionId: scope.sessionId, threadId: scope.threadId, userId: scope.userId };\n let recipients: Array<{ sessionId: string; threadId: string }>;\n let attributes: Record<string, string> = {};\n if (scope.origin) {\n // The origin can mute this side session; the session keeps working, its reports just stop.\n // Blocked reports still go through, since the session cannot go on until the origin answers.\n const row = await deps.sessions.getBySessionId(scope.sessionId);\n if (row?.originMutedAt && status !== 'blocked') {\n return { delivered: 0, recipients: [], muted: true, note: 'Your origin muted your reports; keep working.' };\n }\n recipients = [scope.origin];\n } else {\n const binding = await deps.workItems.findActiveRunBindingForSession({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: scope.sessionId,\n });\n if (!binding) throw new Error('Nothing started this session; pass inReplyTo to answer a message.');\n recipients = await workItemReportRecipients(deps.workItems, {\n orgId: scope.orgId,\n workItemId: binding.workItemId,\n senderSessionId: scope.sessionId,\n });\n ({ attributes } = await workItemReportAttributes(deps.workItems, {\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n workItemId: binding.workItemId,\n role: binding.role,\n }));\n }\n if (recipients.length === 0) return { delivered: 0, recipients: [] };\n\n const messaging = await requireMessaging();\n const notification = reportNotification({ sender, status, message: text, attributes });\n const results: Array<{ sessionId: string; delivered: FactoryDeliveryResult['delivered']; reason?: string }> = [];\n for (const recipient of recipients) {\n // Progress replaces itself while unread; anything else can wake a follower, so it counts.\n if (status !== 'progress' && overRateLimit(await recent(messaging.notifications, recipient.threadId))) {\n results.push({ sessionId: recipient.sessionId, delivered: 'skipped', reason: 'rate-limited' });\n continue;\n }\n const result = await deliverFactorySessionNotification({\n controller: messaging.controller,\n sessions: deps.sessions,\n target: recipient,\n notification,\n });\n results.push({ sessionId: recipient.sessionId, delivered: result.delivered });\n if (result.delivered === 'skipped') continue;\n await audit({\n targetSessionId: recipient.sessionId,\n priority: notification.priority ?? 'high',\n kind: notification.kind,\n delivery: result.delivered,\n });\n }\n const delivered = results.filter(result => result.delivered !== 'skipped').length;\n if (delivered === 0 && results.every(result => result.reason === 'rate-limited')) throw new Error(RATE_LIMITED);\n return { delivered, recipients: results };\n }\n\n const listSide = createTool({\n id: 'factory_list_side_sessions',\n description: [\n 'List the side sessions this session started with factory_start_session: state (running, parked waiting on an answer, idle), last report, whether you muted it, its model, and the work item it works on.',\n 'Use the session ids with factory_signal_session and factory_switch_session_model. On a turn a person started, also with factory_answer_approval, factory_unsubscribe_session and factory_subscribe_session.',\n ].join(' '),\n inputSchema: z\n .object({\n state: z.enum(['running', 'parked', 'idle']).optional().describe('Only sessions in this state.'),\n muted: z.boolean().optional().describe('true for only muted sessions, false for only unmuted ones.'),\n })\n .strict(),\n execute: async ({ state, muted }) => {\n const messaging = await deps.messaging();\n const sessions = await listSideSessions(\n {\n sessions: deps.sessions,\n workItems: deps.workItems,\n notifications: messaging?.notifications ?? null,\n isRunning: async sessionId =>\n (await messaging?.controller.getSessionByResource(sessionId))?.stream.isActive() ?? false,\n model: messaging\n ? sessionId => sessionModel(messaging.controller, deps.sessions, deps.workItems, sessionId)\n : undefined,\n },\n scope,\n );\n return {\n sessions: sessions.filter(\n row => (!state || row.state === state) && (muted === undefined || (row.mutedAt !== null) === muted),\n ),\n };\n },\n });\n\n /** A side session this session started, or an error naming why it isn't one. */\n const ownSideSession = async (sessionId: string) => {\n const row = await deps.sessions.getBySessionId(sessionId);\n if (\n !row ||\n row.orgId !== scope.orgId ||\n row.factoryProjectId !== scope.factoryProjectId ||\n row.originSessionId !== scope.sessionId\n ) {\n throw new Error(`Session ${sessionId} is not a side session you started; see factory_list_side_sessions.`);\n }\n return row;\n };\n const sideSessionId = z\n .string()\n .trim()\n .min(1)\n .max(200)\n .describe('The side session, from factory_list_side_sessions.');\n\n const unsubscribe = createTool({\n id: 'factory_unsubscribe_session',\n description:\n \"Mute a side session you started: its reports and run-end notices stop reaching you, but it keeps working. Approval requests and blocked reports still arrive, since the session can't go on without an answer. Undo with factory_subscribe_session. Only on a turn a person started.\",\n inputSchema: z.object({ sessionId: sideSessionId }).strict(),\n execute: async ({ sessionId }) => {\n requirePerson(scope, 'mute a side session');\n const row = await ownSideSession(sessionId);\n if (row.originMutedAt) return { sessionId, muted: true, mutedAt: row.originMutedAt.toISOString() };\n await deps.sessions.setOriginMuted({ sessionId, muted: true });\n const updated = await deps.sessions.getBySessionId(sessionId);\n return { sessionId, muted: true, mutedAt: updated?.originMutedAt?.toISOString() ?? null };\n },\n });\n\n const subscribe = createTool({\n id: 'factory_subscribe_session',\n description:\n 'Unmute a side session you muted with factory_unsubscribe_session, so its reports reach you again. Reports it sent while muted are not replayed. Only on a turn a person started.',\n inputSchema: z.object({ sessionId: sideSessionId }).strict(),\n execute: async ({ sessionId }) => {\n requirePerson(scope, 'unmute a side session');\n const row = await ownSideSession(sessionId);\n if (row.originMutedAt) await deps.sessions.setOriginMuted({ sessionId, muted: false });\n return { sessionId, muted: false };\n },\n });\n\n if (scope.kind === 'worker') return { factory_message_origin: reply };\n return {\n factory_signal_session: signal,\n factory_message_origin: reply,\n factory_list_side_sessions: listSide,\n factory_unsubscribe_session: unsubscribe,\n factory_subscribe_session: subscribe,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"messaging-tools.js","names":[],"sources":["../../src/tools/messaging-tools.ts"],"sourcesContent":["/**\n * Messages between Factory sessions. A user session can message any session in\n * its Factory it can see; the receiver answers that exact message. Both\n * directions are `factory-session` notifications on the receiver's stored thread.\n */\n\nimport type {\n NotificationPriority,\n NotificationRecord,\n NotificationsStorage,\n SendNotificationSignalInput,\n} from '@mastra/core/notifications';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport {\n FACTORY_SESSION_NOTIFICATION_SOURCE,\n deliverFactorySessionNotification,\n resolveSessionThread,\n} from './deliver.js';\nimport type { FactoryDeliveryController, FactoryDeliveryResult } from './deliver.js';\nimport { workItemNumber } from './read-tools.js';\nimport { reportNotification, workItemReportAttributes, workItemReportRecipients } from './reports.js';\nimport { requirePerson } from './scope.js';\nimport type { FactoryToolScope } from './scope.js';\nimport { listSideSessions, sessionModel } from './side-sessions.js';\n\n/** Messages plus replies, per sender session, per target thread, per window. */\nexport const SIGNAL_RATE_LIMIT = 10;\nexport const SIGNAL_RATE_WINDOW_MS = 10 * 60 * 1000;\n/** How many recent `factory-session` notifications on a thread the rate and duplicate checks read. */\nconst RECENT_SCAN = 100;\n/** How often a waiting queue is reconsidered. Delivery still waits for a free slot in the window. */\nconst QUEUE_FLUSH_MS = 1000;\n\n/** A message accepted past the delivery cap, held until the window has a free slot. */\ninterface PendingMessage {\n notification: Omit<SendNotificationSignalInput, 'source'>;\n /** When the sender handed it over. Oldest goes first. */\n acceptedAt: number;\n actor: { actorId: string; actorType: 'human' | 'agent' };\n}\n\ninterface QueuedDelivery extends PendingMessage {\n /** The moment a slot in the window is expected to free. Set once, when it joins the queue. */\n deliverAt: number;\n target: { sessionId: string; threadId: string };\n}\n\n/**\n * Waiting messages, per sender session and target thread, oldest first.\n * Process-local: a restart drops what was still waiting, the same as a message\n * that had not been handed to the tool yet. Delivered messages live in the\n * notification store and are unaffected.\n */\nconst pendingQueues = new Map<string, QueuedDelivery[]>();\nconst flushTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nconst queueOwners = new WeakMap<object, number>();\nlet nextQueueOwnerId = 0;\n\nconst queueKey = (owner: object, senderSessionId: string, threadId: string): string => {\n let id = queueOwners.get(owner);\n if (id === undefined) {\n nextQueueOwnerId += 1;\n id = nextQueueOwnerId;\n queueOwners.set(owner, id);\n }\n return `${id}\\0${senderSessionId}\\0${threadId}`;\n};\nexport interface FactoryMessaging {\n controller: FactoryDeliveryController;\n notifications: Pick<NotificationsStorage, 'listNotifications' | 'getNotification'>;\n}\n\nexport interface FactoryMessagingToolDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId' | 'listByOrigin' | 'setOriginMuted'>;\n workItems: Pick<\n WorkItemsStorage,\n | 'findActiveRunBindingForSession'\n | 'getForProject'\n | 'hasRunBindingForSession'\n | 'listSessionSubscriptions'\n | 'listRunBindings'\n | 'listWorkItemSubscribers'\n >;\n auditRecorder: AuditRecorder;\n /** The live controller and notifications store; resolved per call because they exist only once the mount is up. */\n messaging: () => Promise<FactoryMessaging | null>;\n}\n\nconst message = z.string().trim().min(1).max(4000);\n\nfunction senderAttributes(scope: FactoryToolScope) {\n return {\n 'from-session': scope.sessionId,\n 'from-thread': scope.threadId,\n 'from-user': scope.userId,\n };\n}\n\nfunction unavailable(result: Extract<FactoryDeliveryResult, { delivered: 'skipped' }>) {\n return new Error(`The session is not available (${result.reason}).`);\n}\n\nexport function createFactoryMessagingTools(\n scope: FactoryToolScope,\n deps: FactoryMessagingToolDependencies,\n): IntegrationTools {\n const requireMessaging = async () => {\n const messaging = await deps.messaging();\n if (!messaging) throw new Error('Session messaging is not available on this server.');\n return messaging;\n };\n // Only a user-session turn the person started is the person's act. Workers\n // (even when someone types into one) and turns nobody signed in started\n // (wakes, webhooks) act as the thread's agent.\n const human = scope.kind === 'user' && scope.interactive;\n const audit = (input: { targetSessionId: string; priority: NotificationPriority; kind: string; delivery: string }) =>\n deps.auditRecorder.record({\n orgId: scope.orgId,\n actorId: human ? scope.userId : `agent:${scope.threadId}`,\n actorType: human ? 'human' : 'agent',\n action: 'factory.agent.signaled',\n factoryProjectId: scope.factoryProjectId,\n targets: [{ type: 'session', id: input.targetSessionId }],\n metadata: {\n targetSessionId: input.targetSessionId,\n priority: input.priority,\n kind: input.kind,\n delivery: input.delivery,\n },\n });\n /** Why this session can't reach `row`, or null when it can: the rule for message targets and reply targets alike. */\n const unreachable = (row: SourceControlSession | null): 'not-found' | 'private' | null => {\n if (!row || row.orgId !== scope.orgId || row.factoryProjectId !== scope.factoryProjectId) return 'not-found';\n if (row.visibility === 'private' && row.userId !== scope.userId) return 'private';\n return null;\n };\n const overRateLimit = (records: NotificationRecord[]) => countedInWindow(records).length >= SIGNAL_RATE_LIMIT;\n /** This session's delivered messages on the thread that still sit inside the window, oldest first. */\n const countedInWindow = (records: NotificationRecord[]): NotificationRecord[] => {\n const since = Date.now() - SIGNAL_RATE_WINDOW_MS;\n return records\n .filter(\n record =>\n // Everything this session sends counts except coalescing progress updates.\n record.kind !== 'progress' &&\n record.kind !== 'stage' &&\n record.attributes?.['from-session'] === scope.sessionId &&\n record.createdAt.getTime() >= since,\n )\n .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n };\n /**\n * When the next message can be delivered: one window after the oldest message\n * that has to age out to free a slot, given how many are already waiting.\n */\n const nextDeliveryAt = (counted: NotificationRecord[], waiting: number): number => {\n const blocking = counted[counted.length + waiting - SIGNAL_RATE_LIMIT];\n return (blocking ? blocking.createdAt.getTime() : Date.now()) + SIGNAL_RATE_WINDOW_MS;\n };\n const queuedResult = (deliverAt: number) => ({\n delivered: 'queued' as const,\n deliverAt: new Date(deliverAt).toISOString(),\n note: `Queued; it will be delivered at about ${new Date(deliverAt).toISOString()}. Do not send it again.`,\n });\n const recent = (notifications: FactoryMessaging['notifications'], threadId: string) =>\n notifications.listNotifications({ threadId, source: FACTORY_SESSION_NOTIFICATION_SOURCE, limit: RECENT_SCAN });\n\n const actor = (): QueuedDelivery['actor'] => ({\n actorId: human ? scope.userId : `agent:${scope.threadId}`,\n actorType: human ? 'human' : 'agent',\n });\n\n /**\n * Deliver now, or hold until the window has a slot. The cap limits how fast\n * messages are delivered, not whether they are accepted.\n */\n const accept = async (\n messaging: FactoryMessaging,\n target: { sessionId: string; threadId: string },\n notification: Omit<SendNotificationSignalInput, 'source'>,\n ): Promise<\n { notificationId: string; delivered: FactoryDeliveryResult['delivered'] } | ReturnType<typeof queuedResult>\n > => {\n const counted = countedInWindow(await recent(messaging.notifications, target.threadId));\n if (counted.length < SIGNAL_RATE_LIMIT) {\n const result = await deliverFactorySessionNotification({\n controller: messaging.controller,\n sessions: deps.sessions,\n runs: deps.workItems,\n target,\n notification,\n });\n if (result.delivered === 'skipped') throw unavailable(result);\n await audit({\n targetSessionId: target.sessionId,\n priority: notification.priority ?? 'medium',\n kind: notification.kind,\n delivery: result.delivered,\n });\n return { notificationId: result.notificationId, delivered: result.delivered };\n }\n const key = queueKey(deps, scope.sessionId, target.threadId);\n const waiting = pendingQueues.get(key)?.length ?? 0;\n const deliverAt = nextDeliveryAt(counted, waiting);\n enqueue(key, {\n notification,\n acceptedAt: Date.now(),\n actor: actor(),\n deliverAt,\n target,\n });\n return queuedResult(deliverAt);\n };\n\n const enqueue = (key: string, delivery: QueuedDelivery): void => {\n const queue = pendingQueues.get(key) ?? [];\n queue.push(delivery);\n queue.sort((a, b) => a.acceptedAt - b.acceptedAt);\n pendingQueues.set(key, queue);\n scheduleFlush(key);\n };\n\n /**\n * Deliver the oldest waiting message once the window has a free slot, then\n * the next, in the order they were accepted. A target that is gone by then\n * is dropped and recorded, the same way an undeliverable live message is.\n */\n const flush = async (key: string): Promise<void> => {\n flushTimers.delete(key);\n const queue = pendingQueues.get(key);\n if (!queue || queue.length === 0) {\n pendingQueues.delete(key);\n return;\n }\n const messaging = await deps.messaging();\n if (!messaging) {\n scheduleFlush(key);\n return;\n }\n const head = queue[0];\n if (!head || head.deliverAt > Date.now()) {\n scheduleFlush(key);\n return;\n }\n const counted = countedInWindow(await recent(messaging.notifications, head.target.threadId));\n if (counted.length >= SIGNAL_RATE_LIMIT) {\n scheduleFlush(key);\n return;\n }\n queue.shift();\n if (queue.length === 0) pendingQueues.delete(key);\n const result = await deliverFactorySessionNotification({\n controller: messaging.controller,\n sessions: deps.sessions,\n runs: deps.workItems,\n target: head.target,\n notification: head.notification,\n });\n await deps.auditRecorder.record({\n orgId: scope.orgId,\n actorId: head.actor.actorId,\n actorType: head.actor.actorType,\n action: 'factory.agent.signaled',\n factoryProjectId: scope.factoryProjectId,\n targets: [{ type: 'session', id: head.target.sessionId }],\n metadata: {\n targetSessionId: head.target.sessionId,\n priority: head.notification.priority ?? 'medium',\n kind: head.notification.kind,\n delivery: result.delivered === 'skipped' ? 'dropped' : result.delivered,\n ...(result.delivered === 'skipped' ? { reason: result.reason } : {}),\n },\n });\n if (queue.length > 0) scheduleFlush(key);\n };\n\n function scheduleFlush(key: string): void {\n if (flushTimers.has(key)) return;\n if (!pendingQueues.get(key)?.[0]) return;\n const timer = setTimeout(() => {\n flushTimers.delete(key);\n void flush(key).catch(() => {\n // A failed flush leaves the rest of the queue waiting; the next tick retries the head.\n if ((pendingQueues.get(key)?.length ?? 0) > 0) scheduleFlush(key);\n });\n }, QUEUE_FLUSH_MS);\n // Keep the wait on the real clock. A fake clock that jumps the window would\n // skip a timer scheduled for the old deadline, and the message would sit.\n timer.unref?.();\n flushTimers.set(key, timer);\n }\n\n /** On turns nobody signed in started, only sessions working on items this session follows. */\n const followedWorkers = async () => {\n const project = { orgId: scope.orgId, factoryProjectId: scope.factoryProjectId };\n const follows = await deps.workItems.listSessionSubscriptions({ ...project, sessionId: scope.sessionId });\n const bindings = await Promise.all(\n follows.map(follow => deps.workItems.listRunBindings(scope.orgId, scope.factoryProjectId, follow.workItemId)),\n );\n const sideSessions = await deps.sessions.listByOrigin({ orgId: scope.orgId, originSessionId: scope.sessionId });\n return new Set([\n ...bindings\n .flat()\n .filter(binding => binding.status === 'active')\n .map(binding => binding.sessionId),\n ...sideSessions.map(session => session.sessionId),\n ]);\n };\n\n const workItemAttribute = async (sessionId: string, orgId: string, factoryProjectId: string) => {\n const binding = await deps.workItems.findActiveRunBindingForSession({ orgId, factoryProjectId, sessionId });\n if (!binding) return {};\n const item = await deps.workItems.getForProject(orgId, factoryProjectId, binding.workItemId);\n if (!item) return {};\n const number = workItemNumber(item);\n return { 'work-item': number === null ? item.id : `#${number}` };\n };\n\n const signal = createTool({\n id: 'factory_signal_session',\n description: [\n 'Send a message to another session in this Factory: a work item role session, or a teammate session you can see.',\n 'Replies come back to this session as `factory-session` notifications with kind `reply` and an `in-reply-to` attribute naming your message.',\n 'Priority `medium` (default) wakes an idle session and waits for a busy one to finish its step; use `urgent` only to make it stop and change course.',\n '`delivered` is `woken` when the session was idle and is now running, `mid-run` when it was already busy and will read the message when its run next checks in.',\n ...(scope.interactive\n ? []\n : ['On this turn you can only message your side sessions and sessions working on items this session follows.']),\n ].join(' '),\n inputSchema: z\n .object({\n sessionId: z.string().trim().min(1).max(200).describe('The session to message.'),\n message: message.describe('What to say. It arrives as-is, so make it self-contained.'),\n priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium'),\n })\n .strict(),\n execute: async ({ sessionId, message: text, priority }) => {\n if (sessionId === scope.sessionId) throw new Error(\"You can't message your own session.\");\n const row = await deps.sessions.getBySessionId(sessionId);\n const blocked = unreachable(row);\n if (blocked === 'not-found' || !row) throw new Error(`No session ${sessionId} was found in this Factory.`);\n if (blocked === 'private') throw new Error(`Session ${sessionId} is private to its owner.`);\n if (!scope.interactive && !(await followedWorkers()).has(sessionId)) {\n throw new Error(\n `On this turn you can only message this session's side sessions and sessions working on items it follows; ${sessionId} isn't one.`,\n );\n }\n\n const messaging = await requireMessaging();\n const threadId = await resolveSessionThread({\n controller: messaging.controller,\n sessions: deps.sessions,\n workItems: deps.workItems,\n sessionId,\n });\n if (!threadId) throw new Error('The session is not available (thread-not-found).');\n\n return accept(\n messaging,\n { sessionId, threadId },\n {\n kind: 'message',\n priority,\n summary: text,\n payload: { message: text },\n sourceId: scope.sessionId,\n attributes: {\n ...(await workItemAttribute(sessionId, row.orgId, scope.factoryProjectId)),\n ...senderAttributes(scope),\n },\n },\n );\n },\n });\n\n const reply = createTool({\n id: 'factory_message_origin',\n description: [\n 'Answer a message another Factory session sent you, or report on your work.',\n 'With `inReplyTo` (the id of a `factory-session` notification of kind `message`) the answer goes back to the session and thread that sent it; each message can be answered once.',\n 'Without it, the report goes to whoever follows your work: the session that started you, or everyone following your work item.',\n \"Set `status`: `progress` for an update, `blocked` when you need an answer to go on, `done` when finished, `failed` when you can't finish. Send one `done` or `failed` when you finish.\",\n ].join(' '),\n inputSchema: z\n .object({\n message: message.describe('Your answer.'),\n inReplyTo: z\n .string()\n .trim()\n .min(1)\n .max(200)\n .optional()\n .describe('Id of the message notification you are answering.'),\n status: z.enum(['progress', 'blocked', 'done', 'failed']).optional(),\n })\n .strict(),\n execute: async ({ message: text, inReplyTo, status }) => {\n if (!inReplyTo) return report(text, status);\n const messaging = await requireMessaging();\n // Only a message that reached this session's own thread can be answered,\n // and the answer goes where that message came from: never to an address\n // the model supplies.\n const original: NotificationRecord | null = await messaging.notifications.getNotification({\n threadId: scope.threadId,\n id: inReplyTo,\n });\n if (!original || original.source !== FACTORY_SESSION_NOTIFICATION_SOURCE) {\n throw new Error(`No message ${inReplyTo} was sent to this session.`);\n }\n if (original.kind !== 'message') {\n throw new Error(`Only messages can be answered; ${inReplyTo} is a ${original.kind}.`);\n }\n const fromSession = original.attributes?.['from-session'];\n const fromThread = original.attributes?.['from-thread'];\n if (typeof fromSession !== 'string' || typeof fromThread !== 'string' || original.sourceId !== fromSession) {\n throw new Error(`Message ${inReplyTo} doesn't say which session sent it.`);\n }\n // Notification records can be written outside these tools, so the\n // sender they name gets the same checks a message to it would.\n const blocked = unreachable(await deps.sessions.getBySessionId(fromSession));\n if (blocked === 'not-found') throw new Error(`Message ${inReplyTo} didn't come from a session in this Factory.`);\n if (blocked === 'private') {\n throw new Error(`Message ${inReplyTo} came from a private session this session can't answer.`);\n }\n const onSenderThread = await recent(messaging.notifications, fromThread);\n // Replies count toward the same cap as messages, so records written to\n // this session can't turn it into an unlimited high-priority sender.\n const answered = onSenderThread.some(\n record =>\n record.kind === 'reply' &&\n record.attributes?.['in-reply-to'] === inReplyTo &&\n record.attributes?.['from-session'] === scope.sessionId,\n );\n if (answered) throw new Error(`You already answered ${inReplyTo}.`);\n\n return accept(\n messaging,\n { sessionId: fromSession, threadId: fromThread },\n {\n kind: 'reply',\n priority: 'high',\n summary: text,\n payload: { message: text },\n sourceId: scope.sessionId,\n attributes: {\n ...senderAttributes(scope),\n 'in-reply-to': inReplyTo,\n ...(status ? { status } : {}),\n },\n },\n );\n },\n });\n\n /** A report with no message to answer: to the origin, else to the item's followers. */\n async function report(text: string, status: 'progress' | 'blocked' | 'done' | 'failed' | undefined) {\n const sender = { sessionId: scope.sessionId, threadId: scope.threadId, userId: scope.userId };\n let recipients: Array<{ sessionId: string; threadId: string }>;\n let attributes: Record<string, string> = {};\n if (scope.origin) {\n // The origin can mute this side session; the session keeps working, its reports just stop.\n // Blocked reports still go through, since the session cannot go on until the origin answers.\n const row = await deps.sessions.getBySessionId(scope.sessionId);\n if (row?.originMutedAt && status !== 'blocked') {\n return { delivered: 0, recipients: [], muted: true, note: 'Your origin muted your reports; keep working.' };\n }\n recipients = [scope.origin];\n } else {\n const binding = await deps.workItems.findActiveRunBindingForSession({\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n sessionId: scope.sessionId,\n });\n if (!binding) throw new Error('Nothing started this session; pass inReplyTo to answer a message.');\n recipients = await workItemReportRecipients(deps.workItems, {\n orgId: scope.orgId,\n workItemId: binding.workItemId,\n senderSessionId: scope.sessionId,\n });\n ({ attributes } = await workItemReportAttributes(deps.workItems, {\n orgId: scope.orgId,\n factoryProjectId: scope.factoryProjectId,\n workItemId: binding.workItemId,\n role: binding.role,\n }));\n }\n if (recipients.length === 0) return { delivered: 0, recipients: [] };\n\n const messaging = await requireMessaging();\n const notification = reportNotification({ sender, status, message: text, attributes });\n const results: Array<{\n sessionId: string;\n delivered: FactoryDeliveryResult['delivered'] | 'queued';\n reason?: string;\n deliverAt?: string;\n }> = [];\n for (const recipient of recipients) {\n // Progress replaces itself while unread; anything else can wake a follower, so it counts.\n if (status !== 'progress' && overRateLimit(await recent(messaging.notifications, recipient.threadId))) {\n const counted = countedInWindow(await recent(messaging.notifications, recipient.threadId));\n const key = queueKey(deps, scope.sessionId, recipient.threadId);\n const waiting = pendingQueues.get(key)?.length ?? 0;\n const at = nextDeliveryAt(counted, waiting);\n enqueue(key, {\n notification,\n acceptedAt: Date.now(),\n actor: actor(),\n deliverAt: at,\n target: recipient,\n });\n results.push({\n sessionId: recipient.sessionId,\n delivered: 'queued',\n deliverAt: new Date(at).toISOString(),\n });\n continue;\n }\n const result = await deliverFactorySessionNotification({\n controller: messaging.controller,\n sessions: deps.sessions,\n runs: deps.workItems,\n target: recipient,\n notification,\n });\n results.push({ sessionId: recipient.sessionId, delivered: result.delivered });\n if (result.delivered === 'skipped') continue;\n await audit({\n targetSessionId: recipient.sessionId,\n priority: notification.priority ?? 'high',\n kind: notification.kind,\n delivery: result.delivered,\n });\n }\n const delivered = results.filter(result => result.delivered !== 'skipped' && result.delivered !== 'queued').length;\n const queued = results.filter(result => result.delivered === 'queued').length;\n return {\n delivered,\n recipients: results,\n ...(queued > 0\n ? {\n queued,\n note: 'Queued past the delivery cap. It will be delivered when the window allows. Do not send it again.',\n }\n : {}),\n };\n }\n\n const listSide = createTool({\n id: 'factory_list_side_sessions',\n description: [\n 'List the side sessions this session started with factory_start_session: state (running, parked waiting on an answer, idle), last report, whether you muted it, its model, and the work item it works on.',\n 'Use the session ids with factory_signal_session and factory_switch_session_model. On a turn a person started, also with factory_answer_approval, factory_unsubscribe_session and factory_subscribe_session.',\n ].join(' '),\n inputSchema: z\n .object({\n state: z.enum(['running', 'parked', 'idle']).optional().describe('Only sessions in this state.'),\n muted: z.boolean().optional().describe('true for only muted sessions, false for only unmuted ones.'),\n })\n .strict(),\n execute: async ({ state, muted }) => {\n const messaging = await deps.messaging();\n const sessions = await listSideSessions(\n {\n sessions: deps.sessions,\n workItems: deps.workItems,\n notifications: messaging?.notifications ?? null,\n isRunning: async sessionId => (await messaging?.controller.getSession(sessionId))?.stream.isActive() ?? false,\n model: messaging\n ? sessionId => sessionModel(messaging.controller, deps.sessions, deps.workItems, sessionId)\n : undefined,\n },\n scope,\n );\n return {\n sessions: sessions.filter(\n row => (!state || row.state === state) && (muted === undefined || (row.mutedAt !== null) === muted),\n ),\n };\n },\n });\n\n /** A side session this session started, or an error naming why it isn't one. */\n const ownSideSession = async (sessionId: string) => {\n const row = await deps.sessions.getBySessionId(sessionId);\n if (\n !row ||\n row.orgId !== scope.orgId ||\n row.factoryProjectId !== scope.factoryProjectId ||\n row.originSessionId !== scope.sessionId\n ) {\n throw new Error(`Session ${sessionId} is not a side session you started; see factory_list_side_sessions.`);\n }\n return row;\n };\n const sideSessionId = z\n .string()\n .trim()\n .min(1)\n .max(200)\n .describe('The side session, from factory_list_side_sessions.');\n\n const unsubscribe = createTool({\n id: 'factory_unsubscribe_session',\n description:\n \"Mute a side session you started: its reports and run-end notices stop reaching you, but it keeps working. Approval requests and blocked reports still arrive, since the session can't go on without an answer. Undo with factory_subscribe_session. Only on a turn a person started.\",\n inputSchema: z.object({ sessionId: sideSessionId }).strict(),\n execute: async ({ sessionId }) => {\n requirePerson(scope, 'mute a side session');\n const row = await ownSideSession(sessionId);\n if (row.originMutedAt) return { sessionId, muted: true, mutedAt: row.originMutedAt.toISOString() };\n await deps.sessions.setOriginMuted({ sessionId, muted: true });\n const updated = await deps.sessions.getBySessionId(sessionId);\n return { sessionId, muted: true, mutedAt: updated?.originMutedAt?.toISOString() ?? null };\n },\n });\n\n const subscribe = createTool({\n id: 'factory_subscribe_session',\n description:\n 'Unmute a side session you muted with factory_unsubscribe_session, so its reports reach you again. Reports it sent while muted are not replayed. Only on a turn a person started.',\n inputSchema: z.object({ sessionId: sideSessionId }).strict(),\n execute: async ({ sessionId }) => {\n requirePerson(scope, 'unmute a side session');\n const row = await ownSideSession(sessionId);\n if (row.originMutedAt) await deps.sessions.setOriginMuted({ sessionId, muted: false });\n return { sessionId, muted: false };\n },\n });\n\n if (scope.kind === 'worker') return { factory_message_origin: reply };\n return {\n factory_signal_session: signal,\n factory_message_origin: reply,\n factory_list_side_sessions: listSide,\n factory_unsubscribe_session: unsubscribe,\n factory_subscribe_session: subscribe,\n };\n}\n"],"mappings":"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"}
@@ -8,7 +8,7 @@ import type { SourceControlSessionLookup } from '../session/factory-session.js';
8
8
  export declare const FACTORY_TOOLS_WITHOUT_APPROVAL: readonly string[];
9
9
  export interface FactoryToolPermissionsSession {
10
10
  readonly identity: {
11
- getResourceId(): string;
11
+ getId(): string;
12
12
  };
13
13
  readonly permissions: {
14
14
  getRules(): PermissionRules;
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/tools/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEvF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,EAAE,SAAS,MAAM,EAoB3D,CAAC;AAEF,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,QAAQ,EAAE;QAAE,aAAa,IAAI,MAAM,CAAA;KAAE,CAAC;IAC/C,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,IAAI,eAAe,CAAC;QAC5B,UAAU,CAAC,IAAI,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,gBAAgB,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACjF,CAAC;CACH;AAED,MAAM,WAAW,kCAAkC;IACjD,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;CAC9D;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,6BAA6B,EACtC,EAAE,QAAQ,EAAE,EAAE,kCAAkC,EAChD,SAAS,GAAE,SAAS,MAAM,EAAmC,GAC5D,OAAO,CAAC,IAAI,CAAC,CASf"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/tools/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEvF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,EAAE,SAAS,MAAM,EAqB3D,CAAC;AAEF,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,QAAQ,EAAE;QAAE,KAAK,IAAI,MAAM,CAAA;KAAE,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,IAAI,eAAe,CAAC;QAC5B,UAAU,CAAC,IAAI,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,gBAAgB,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACjF,CAAC;CACH;AAED,MAAM,WAAW,kCAAkC;IACjD,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;CAC9D;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,6BAA6B,EACtC,EAAE,QAAQ,EAAE,EAAE,kCAAkC,EAChD,SAAS,GAAE,SAAS,MAAM,EAAmC,GAC5D,OAAO,CAAC,IAAI,CAAC,CASf"}
@@ -23,7 +23,8 @@ const FACTORY_TOOLS_WITHOUT_APPROVAL = [
23
23
  "factory_unsubscribe_session",
24
24
  "factory_start_session",
25
25
  "factory_answer_approval",
26
- "factory_switch_session_model"
26
+ "factory_switch_session_model",
27
+ "factory_recreate_sandbox"
27
28
  ];
28
29
  /**
29
30
  * Allow the no-approval Factory tools on a newly created Factory session.
@@ -32,7 +33,7 @@ const FACTORY_TOOLS_WITHOUT_APPROVAL = [
32
33
  * overwritten, and an explicit `mcp` category policy leaves everything alone.
33
34
  */
34
35
  async function applyFactoryToolPermissions(session, { sessions }, toolNames = FACTORY_TOOLS_WITHOUT_APPROVAL) {
35
- if (!(await sessions.getBySessionId(session.identity.getResourceId()))?.factoryProjectId) return;
36
+ if (!(await sessions.getBySessionId(session.identity.getId()))?.factoryProjectId) return;
36
37
  if (session.permissions.getRules().categories.mcp !== void 0) return;
37
38
  for (const toolName of toolNames) {
38
39
  if (session.permissions.getRules().tools[toolName] !== void 0) continue;
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","names":[],"sources":["../../src/tools/permissions.ts"],"sourcesContent":["import type { PermissionPolicy, PermissionRules } from '@mastra/core/agent-controller';\n\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\n\n/**\n * Factory tools that run without asking. Factory tools fall into the SDK's\n * `mcp` category, whose default policy is `ask`, so each of these needs an\n * explicit per-session `allow`.\n */\nexport const FACTORY_TOOLS_WITHOUT_APPROVAL: readonly string[] = [\n 'factory_list_work_items',\n 'factory_inspect_work_item',\n 'factory_list_attention',\n 'factory_create_work_item',\n 'factory_update_work_item',\n 'factory_transition_work_item',\n 'factory_retry_decision',\n 'factory_dismiss_decision',\n 'factory_resolve_proposal',\n 'factory_subscribe_work_item',\n 'factory_unsubscribe_work_item',\n 'factory_signal_session',\n 'factory_message_origin',\n 'factory_list_side_sessions',\n 'factory_subscribe_session',\n 'factory_unsubscribe_session',\n 'factory_start_session',\n 'factory_answer_approval',\n 'factory_switch_session_model',\n];\n\nexport interface FactoryToolPermissionsSession {\n readonly identity: { getResourceId(): string };\n readonly permissions: {\n getRules(): PermissionRules;\n setForTool(args: { toolName: string; policy: PermissionPolicy }): Promise<void>;\n };\n}\n\nexport interface FactoryToolPermissionsDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n}\n\n/**\n * Allow the no-approval Factory tools on a newly created Factory session.\n * This only sets policy; which tools a turn actually gets is decided by the\n * tool scope. A person's own choices win: a per-tool policy is never\n * overwritten, and an explicit `mcp` category policy leaves everything alone.\n */\nexport async function applyFactoryToolPermissions(\n session: FactoryToolPermissionsSession,\n { sessions }: FactoryToolPermissionsDependencies,\n toolNames: readonly string[] = FACTORY_TOOLS_WITHOUT_APPROVAL,\n): Promise<void> {\n const row = await sessions.getBySessionId(session.identity.getResourceId());\n if (!row?.factoryProjectId) return;\n const rules = session.permissions.getRules();\n if (rules.categories.mcp !== undefined) return;\n for (const toolName of toolNames) {\n if (session.permissions.getRules().tools[toolName] !== undefined) continue;\n await session.permissions.setForTool({ toolName, policy: 'allow' });\n }\n}\n"],"mappings":";;;;;;AASA,MAAa,iCAAoD;CAC/D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;AAoBA,eAAsB,4BACpB,SACA,EAAE,YACF,YAA+B,gCAChB;CAEf,IAAI,EAAC,MADa,SAAS,eAAe,QAAQ,SAAS,cAAc,CAAC,EAAA,EAChE,kBAAkB;CAE5B,IADc,QAAQ,YAAY,SAC1B,CAAC,CAAC,WAAW,QAAQ,KAAA,GAAW;CACxC,KAAK,MAAM,YAAY,WAAW;EAChC,IAAI,QAAQ,YAAY,SAAS,CAAC,CAAC,MAAM,cAAc,KAAA,GAAW;EAClE,MAAM,QAAQ,YAAY,WAAW;GAAE;GAAU,QAAQ;EAAQ,CAAC;CACpE;AACF"}
1
+ {"version":3,"file":"permissions.js","names":[],"sources":["../../src/tools/permissions.ts"],"sourcesContent":["import type { PermissionPolicy, PermissionRules } from '@mastra/core/agent-controller';\n\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\n\n/**\n * Factory tools that run without asking. Factory tools fall into the SDK's\n * `mcp` category, whose default policy is `ask`, so each of these needs an\n * explicit per-session `allow`.\n */\nexport const FACTORY_TOOLS_WITHOUT_APPROVAL: readonly string[] = [\n 'factory_list_work_items',\n 'factory_inspect_work_item',\n 'factory_list_attention',\n 'factory_create_work_item',\n 'factory_update_work_item',\n 'factory_transition_work_item',\n 'factory_retry_decision',\n 'factory_dismiss_decision',\n 'factory_resolve_proposal',\n 'factory_subscribe_work_item',\n 'factory_unsubscribe_work_item',\n 'factory_signal_session',\n 'factory_message_origin',\n 'factory_list_side_sessions',\n 'factory_subscribe_session',\n 'factory_unsubscribe_session',\n 'factory_start_session',\n 'factory_answer_approval',\n 'factory_switch_session_model',\n 'factory_recreate_sandbox',\n];\n\nexport interface FactoryToolPermissionsSession {\n readonly identity: { getId(): string };\n readonly permissions: {\n getRules(): PermissionRules;\n setForTool(args: { toolName: string; policy: PermissionPolicy }): Promise<void>;\n };\n}\n\nexport interface FactoryToolPermissionsDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n}\n\n/**\n * Allow the no-approval Factory tools on a newly created Factory session.\n * This only sets policy; which tools a turn actually gets is decided by the\n * tool scope. A person's own choices win: a per-tool policy is never\n * overwritten, and an explicit `mcp` category policy leaves everything alone.\n */\nexport async function applyFactoryToolPermissions(\n session: FactoryToolPermissionsSession,\n { sessions }: FactoryToolPermissionsDependencies,\n toolNames: readonly string[] = FACTORY_TOOLS_WITHOUT_APPROVAL,\n): Promise<void> {\n const row = await sessions.getBySessionId(session.identity.getId());\n if (!row?.factoryProjectId) return;\n const rules = session.permissions.getRules();\n if (rules.categories.mcp !== undefined) return;\n for (const toolName of toolNames) {\n if (session.permissions.getRules().tools[toolName] !== undefined) continue;\n await session.permissions.setForTool({ toolName, policy: 'allow' });\n }\n}\n"],"mappings":";;;;;;AASA,MAAa,iCAAoD;CAC/D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;AAoBA,eAAsB,4BACpB,SACA,EAAE,YACF,YAA+B,gCAChB;CAEf,IAAI,EAAC,MADa,SAAS,eAAe,QAAQ,SAAS,MAAM,CAAC,EAAA,EACxD,kBAAkB;CAE5B,IADc,QAAQ,YAAY,SAC1B,CAAC,CAAC,WAAW,QAAQ,KAAA,GAAW;CACxC,KAAK,MAAM,YAAY,WAAW;EAChC,IAAI,QAAQ,YAAY,SAAS,CAAC,CAAC,MAAM,cAAc,KAAA,GAAW;EAClE,MAAM,QAAQ,YAAY,WAAW;GAAE;GAAU,QAAQ;EAAQ,CAAC;CACpE;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../src/tools/reports.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC5B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAGjF,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE7E,4GAA4G;AAC5G,eAAO,MAAM,oBAAoB,EAAE,MAAM,CACvC,mBAAmB,GAAG,QAAQ,EAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAQpE,CAAC;AAKF,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,oBAAoB,GAAG,CACjC,MAAM,EAAE,qBAAqB,EAC7B,YAAY,EAAE,IAAI,CAAC,2BAA2B,EAAE,QAAQ,CAAC,EACzD,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,KAC/B,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAEpC,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,aAAa,EAAE,IAAI,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,CAAC;CAChE;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,EAChD,EACE,eAAe,EACf,iBAAiB,EACjB,KAAK,EACL,KAAK,EACL,SAAiB,GAClB,EAAE;IACD,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,IAAI,CAAC;IACZ,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,GACA,OAAO,CAAC,OAAO,CAAC,CAsBlB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,EAChD,EACE,eAAe,EACf,iBAAiB,EACjB,OAAO,GACR,EAAE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACzE,OAAO,CAAC,OAAO,CAAC,CAelB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,MAAM,EACN,OAAO,EACP,UAAe,GAChB,EAAE;IACD,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC,GAAG,IAAI,CAAC,2BAA2B,EAAE,QAAQ,CAAC,CAoB9C;AAED,8EAA8E;AAC9E,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,EAC5D,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GACrG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAKlC;AAaD,0EAA0E;AAC1E,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,EAClD,EACE,KAAK,EACL,gBAAgB,EAChB,UAAU,EACV,IAAI,GACL,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/E,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAGhE;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEjE,6FAA6F;AAC7F,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,qBAAqB,EAAE,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,gBAAgB,EAC3B,EACE,MAAM,EACN,UAAU,EACV,KAAK,EACL,OAAO,EACP,OAAO,EACP,UAAe,GAChB,EAAE;IACD,MAAM,EAAE,mBAAmB,CAAC;IAC5B,UAAU,EAAE,qBAAqB,EAAE,CAAC;IACpC,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,EAAE,iBAAiB,CAAC;IAC3B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC,GACA,OAAO,CAAC,mBAAmB,CAAC,CAsD9B;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,IAAI,CAAC;IACZ,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAEhG,8GAA8G;AAC9G,wBAAgB,4BAA4B,CAAC,EAC3C,SAAS,EACT,SAAS,GACV,EAAE;IACD,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,GAAG,eAAe,CAAC,CAAC;IAC/E,4EAA4E;IAC5E,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CACnD,GAAG,qBAAqB,CAyBxB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,SAAS,EACT,SAAS,GACV,EAAE;IACD,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,GAAG,eAAe,CAAC,CAAC;IAC/E,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CACnD,GAAG,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAoChD;AAED,4EAA4E;AAC5E,MAAM,MAAM,4BAA4B,GAAG,CAAC,KAAK,EAAE;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,KAAK,EAAE,IAAI,CAAC;CACb,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpB,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEzF,UAAU,yBAAyB;IACjC,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAC7D,SAAS,EAAE,IAAI,CACb,gBAAgB,EAChB,gCAAgC,GAAG,yBAAyB,GAAG,yBAAyB,GAAG,eAAe,CAC3G,CAAC;IACF,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;CACnD;AAiED,4EAA4E;AAC5E,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,yBAAyB,GAAG,4BAA4B,CA8BzG;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,yBAAyB,GAAG,uBAAuB,CA2CtG"}
1
+ {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../src/tools/reports.ts"],"names":[],"mappings":"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"}
@@ -251,14 +251,21 @@ async function sessionAudience({ sessions, workItems }, sessionId, threadId, sin
251
251
  factoryProjectId: row.factoryProjectId,
252
252
  sessionId: row.sessionId
253
253
  });
254
- if (!binding && since && threadId) {
254
+ if (!binding && since && threadId) for (const resourceId of /* @__PURE__ */ new Set([
255
+ row.factoryProjectId,
256
+ row.sessionId,
257
+ row.userId
258
+ ])) {
255
259
  const latest = await workItems.findRunBindingBySession({
256
260
  factoryProjectId: row.factoryProjectId,
257
261
  threadId,
258
- resourceId: row.sessionId,
262
+ resourceId,
259
263
  sessionId: row.sessionId
260
264
  });
261
- if (latest?.orgId === row.orgId && latest.revokedAt && latest.revokedAt >= since) binding = latest;
265
+ if (latest?.orgId === row.orgId && latest.revokedAt && latest.revokedAt >= since) {
266
+ binding = latest;
267
+ break;
268
+ }
262
269
  }
263
270
  if (binding) return {
264
271
  kind: "work-item",