@mastra/factory 0.25.0-alpha.4 → 0.25.0-alpha.6

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 (358) hide show
  1. package/README.md +3 -1
  2. package/dist/auth.js +1 -2
  3. package/dist/boards/define-board.js +1 -2
  4. package/dist/boards/index.js +1 -2
  5. package/dist/boards/registry.js +1 -2
  6. package/dist/boards/relocate.js +1 -2
  7. package/dist/boards/review.js +1 -2
  8. package/dist/boards/semantics.js +1 -2
  9. package/dist/boards/transition-policy.js +1 -2
  10. package/dist/boards/work-tool-rules.js +1 -2
  11. package/dist/boards/work-transition-policy.js +1 -2
  12. package/dist/boards/work.js +1 -2
  13. package/dist/default-thinking-level.d.ts +3 -0
  14. package/dist/default-thinking-level.d.ts.map +1 -0
  15. package/dist/default-thinking-level.js +5 -0
  16. package/dist/default-thinking-level.js.map +1 -0
  17. package/dist/environment/build-runner.js +1 -2
  18. package/dist/environment/build-schedule.js +1 -2
  19. package/dist/environment/build-workflow.js +1 -2
  20. package/dist/environment/build.js +1 -2
  21. package/dist/environment/heads.js +1 -2
  22. package/dist/factory.d.ts +4 -2
  23. package/dist/factory.d.ts.map +1 -1
  24. package/dist/factory.js +62 -21
  25. package/dist/factory.js.map +1 -1
  26. package/dist/feed-events.js +1 -2
  27. package/dist/index.js +2 -2
  28. package/dist/integrations/channel-thread-announcements.d.ts +8 -0
  29. package/dist/integrations/channel-thread-announcements.d.ts.map +1 -0
  30. package/dist/integrations/channel-thread-announcements.js +16 -0
  31. package/dist/integrations/channel-thread-announcements.js.map +1 -0
  32. package/dist/integrations/github/acceptance-labels.js +1 -2
  33. package/dist/integrations/github/app-identity.js +1 -2
  34. package/dist/integrations/github/commits.js +1 -2
  35. package/dist/integrations/github/config.js +1 -2
  36. package/dist/integrations/github/default-rules.js +2 -3
  37. package/dist/integrations/github/integration.js +3 -4
  38. package/dist/integrations/github/issue-reconciler.js +1 -2
  39. package/dist/integrations/github/pat.js +1 -2
  40. package/dist/integrations/github/provenance.d.ts.map +1 -1
  41. package/dist/integrations/github/provenance.js +2 -5
  42. package/dist/integrations/github/provenance.js.map +1 -1
  43. package/dist/integrations/github/reconcile-worker.js +1 -2
  44. package/dist/integrations/github/reconciliation-config.js +1 -2
  45. package/dist/integrations/github/routes.js +4 -5
  46. package/dist/integrations/github/rules.d.ts.map +1 -1
  47. package/dist/integrations/github/rules.js +14 -3
  48. package/dist/integrations/github/rules.js.map +1 -1
  49. package/dist/integrations/github/sandbox-release.js +1 -2
  50. package/dist/integrations/github/sandbox.js +1 -2
  51. package/dist/integrations/github/session-subscriptions.js +1 -2
  52. package/dist/integrations/github/settle-or-abort.js +1 -2
  53. package/dist/integrations/github/stale-reviews.js +1 -2
  54. package/dist/integrations/github/subscriptions.js +1 -2
  55. package/dist/integrations/github/token-refresh.js +1 -2
  56. package/dist/integrations/github/webhook.d.ts.map +1 -1
  57. package/dist/integrations/github/webhook.js +17 -3
  58. package/dist/integrations/github/webhook.js.map +1 -1
  59. package/dist/integrations/gitlab/agent-tools.js +1 -2
  60. package/dist/integrations/gitlab/api.js +1 -2
  61. package/dist/integrations/gitlab/default-rules.js +1 -2
  62. package/dist/integrations/gitlab/integration.d.ts +4 -0
  63. package/dist/integrations/gitlab/integration.d.ts.map +1 -1
  64. package/dist/integrations/gitlab/integration.js +12 -4
  65. package/dist/integrations/gitlab/integration.js.map +1 -1
  66. package/dist/integrations/gitlab/issue-reconciler.js +1 -2
  67. package/dist/integrations/gitlab/merge-request-reconciler.js +1 -2
  68. package/dist/integrations/gitlab/reconciler.js +1 -2
  69. package/dist/integrations/gitlab/reconciliation-config.js +1 -2
  70. package/dist/integrations/gitlab/routes.d.ts +6 -0
  71. package/dist/integrations/gitlab/routes.d.ts.map +1 -1
  72. package/dist/integrations/gitlab/routes.js +154 -3
  73. package/dist/integrations/gitlab/routes.js.map +1 -1
  74. package/dist/integrations/gitlab/rules.d.ts.map +1 -1
  75. package/dist/integrations/gitlab/rules.js +5 -3
  76. package/dist/integrations/gitlab/rules.js.map +1 -1
  77. package/dist/integrations/gitlab/session-subscriptions.js +1 -2
  78. package/dist/integrations/gitlab/subscriptions.js +1 -2
  79. package/dist/integrations/gitlab/version-control.js +1 -2
  80. package/dist/integrations/gitlab/webhook-dispatch.js +1 -2
  81. package/dist/integrations/gitlab/webhook.js +1 -2
  82. package/dist/integrations/incidentio/agent-tools.js +1 -2
  83. package/dist/integrations/incidentio/api.js +1 -2
  84. package/dist/integrations/incidentio/claim.js +1 -2
  85. package/dist/integrations/incidentio/default-rules.js +1 -2
  86. package/dist/integrations/incidentio/intake.js +1 -2
  87. package/dist/integrations/incidentio/integration.js +1 -2
  88. package/dist/integrations/incidentio/issue-reconciler.js +1 -2
  89. package/dist/integrations/incidentio/reconciliation-config.js +1 -2
  90. package/dist/integrations/incidentio/routes.js +1 -2
  91. package/dist/integrations/incidentio/rules.js +1 -2
  92. package/dist/integrations/issue-reconcile-worker.js +1 -2
  93. package/dist/integrations/issue-reconciler.js +1 -2
  94. package/dist/integrations/jira/adf.js +1 -2
  95. package/dist/integrations/jira/agent-tools.js +1 -2
  96. package/dist/integrations/jira/api.d.ts +1 -1
  97. package/dist/integrations/jira/api.d.ts.map +1 -1
  98. package/dist/integrations/jira/api.js +2 -3
  99. package/dist/integrations/jira/api.js.map +1 -1
  100. package/dist/integrations/jira/claim.js +1 -2
  101. package/dist/integrations/jira/default-rules.js +1 -2
  102. package/dist/integrations/jira/integration.d.ts +3 -1
  103. package/dist/integrations/jira/integration.d.ts.map +1 -1
  104. package/dist/integrations/jira/integration.js +18 -4
  105. package/dist/integrations/jira/integration.js.map +1 -1
  106. package/dist/integrations/jira/issue-reconciler.js +1 -2
  107. package/dist/integrations/jira/reconciliation-config.js +1 -2
  108. package/dist/integrations/jira/routes.js +1 -2
  109. package/dist/integrations/jira/rules.js +1 -2
  110. package/dist/integrations/linear/agent-tools.d.ts +1 -1
  111. package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
  112. package/dist/integrations/linear/agent-tools.js +5 -5
  113. package/dist/integrations/linear/agent-tools.js.map +1 -1
  114. package/dist/integrations/linear/claim.js +1 -2
  115. package/dist/integrations/linear/default-rules.js +1 -2
  116. package/dist/integrations/linear/integration.d.ts +2 -0
  117. package/dist/integrations/linear/integration.d.ts.map +1 -1
  118. package/dist/integrations/linear/integration.js +2 -2
  119. package/dist/integrations/linear/integration.js.map +1 -1
  120. package/dist/integrations/linear/issue-reconciler.js +1 -2
  121. package/dist/integrations/linear/reconciliation-config.js +1 -2
  122. package/dist/integrations/linear/routes.js +1 -2
  123. package/dist/integrations/linear/rules.js +1 -2
  124. package/dist/integrations/platform/api-client.js +1 -2
  125. package/dist/integrations/platform/connect/routes.js +1 -2
  126. package/dist/integrations/platform/github/event-worker.js +6 -2
  127. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  128. package/dist/integrations/platform/github/integration.js +1 -2
  129. package/dist/integrations/platform/gitlab/event-worker.js +1 -2
  130. package/dist/integrations/platform/gitlab/integration.js +1 -2
  131. package/dist/integrations/platform/incidentio/integration.js +1 -2
  132. package/dist/integrations/platform/jira/agent-tools.js +1 -2
  133. package/dist/integrations/platform/jira/integration.js +1 -2
  134. package/dist/integrations/platform/jira/routes.js +1 -2
  135. package/dist/integrations/platform/linear/event-worker.js +1 -2
  136. package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
  137. package/dist/integrations/platform/linear/integration.js +2 -2
  138. package/dist/integrations/platform/linear/integration.js.map +1 -1
  139. package/dist/integrations/pull-request-head.d.ts +1 -0
  140. package/dist/integrations/pull-request-head.d.ts.map +1 -1
  141. package/dist/integrations/pull-request-head.js +3 -3
  142. package/dist/integrations/pull-request-head.js.map +1 -1
  143. package/dist/integrations/reconciliation-config.js +1 -2
  144. package/dist/integrations/slack/channel-reply-processor.d.ts +1 -0
  145. package/dist/integrations/slack/channel-reply-processor.d.ts.map +1 -1
  146. package/dist/integrations/slack/channel-reply-processor.js +16 -6
  147. package/dist/integrations/slack/channel-reply-processor.js.map +1 -1
  148. package/dist/integrations/slack/channel-session-link-processor.d.ts +1 -0
  149. package/dist/integrations/slack/channel-session-link-processor.d.ts.map +1 -1
  150. package/dist/integrations/slack/channel-session-link-processor.js +2 -3
  151. package/dist/integrations/slack/channel-session-link-processor.js.map +1 -1
  152. package/dist/integrations/slack/connect-route.js +1 -2
  153. package/dist/integrations/slack/emoji-shortcodes.generated.js +1 -2
  154. package/dist/integrations/slack/emoji.js +1 -2
  155. package/dist/integrations/slack/feed-publisher.js +1 -2
  156. package/dist/integrations/slack/integration.d.ts +5 -0
  157. package/dist/integrations/slack/integration.d.ts.map +1 -1
  158. package/dist/integrations/slack/integration.js +13 -2
  159. package/dist/integrations/slack/integration.js.map +1 -1
  160. package/dist/integrations/slack/reply-reminder-processor.js +1 -2
  161. package/dist/integrations/slack/reply-tool.d.ts.map +1 -1
  162. package/dist/integrations/slack/reply-tool.js +4 -13
  163. package/dist/integrations/slack/reply-tool.js.map +1 -1
  164. package/dist/integrations/slack/run-error-card.d.ts +3 -0
  165. package/dist/integrations/slack/run-error-card.d.ts.map +1 -0
  166. package/dist/integrations/slack/run-error-card.js +44 -0
  167. package/dist/integrations/slack/run-error-card.js.map +1 -0
  168. package/dist/integrations/slack/share-file-tool.js +1 -2
  169. package/dist/integrations/slack/slack.d.ts.map +1 -1
  170. package/dist/integrations/slack/slack.js +7 -4
  171. package/dist/integrations/slack/slack.js.map +1 -1
  172. package/dist/integrations/subscription-session.js +1 -2
  173. package/dist/integrations/workos/integration.js +1 -2
  174. package/dist/mcp/disconnect.js +1 -2
  175. package/dist/mcp/events.js +2 -3
  176. package/dist/mcp/routes.js +1 -2
  177. package/dist/packages/_internals/workspace/dist/index.js +1 -2
  178. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  179. package/dist/platform-auth-host.js +1 -2
  180. package/dist/provider-display-name.js +1 -2
  181. package/dist/review-verdict.d.ts +1 -0
  182. package/dist/review-verdict.d.ts.map +1 -1
  183. package/dist/review-verdict.js +9 -3
  184. package/dist/review-verdict.js.map +1 -1
  185. package/dist/routes/attention-activity.js +1 -2
  186. package/dist/routes/attention-parked.js +1 -2
  187. package/dist/routes/attention-proposed.js +1 -2
  188. package/dist/routes/attention-providers.js +1 -2
  189. package/dist/routes/attention.js +1 -2
  190. package/dist/routes/automation-runs.js +1 -2
  191. package/dist/routes/config.d.ts +7 -0
  192. package/dist/routes/config.d.ts.map +1 -1
  193. package/dist/routes/config.js +29 -3
  194. package/dist/routes/config.js.map +1 -1
  195. package/dist/routes/contracts.js +1 -2
  196. package/dist/routes/custom-provider-source.js +1 -2
  197. package/dist/routes/fs.js +1 -2
  198. package/dist/routes/intake.js +6 -4
  199. package/dist/routes/intake.js.map +1 -1
  200. package/dist/routes/knowledge.js +1 -2
  201. package/dist/routes/oauth.js +1 -2
  202. package/dist/routes/projects.js +1 -2
  203. package/dist/routes/provider-credentials.js +1 -2
  204. package/dist/routes/route.js +1 -2
  205. package/dist/routes/session-access.js +1 -2
  206. package/dist/routes/skills.js +1 -2
  207. package/dist/routes/source-control-sessions.js +5 -6
  208. package/dist/routes/source-control-settings.js +1 -2
  209. package/dist/routes/surface.js +3 -4
  210. package/dist/routes/surface.js.map +1 -1
  211. package/dist/routes/telemetry.js +1 -2
  212. package/dist/routes/tenant-credentials.js +1 -2
  213. package/dist/routes/work-items.js +2 -3
  214. package/dist/rules/binding-context.js +1 -2
  215. package/dist/rules/decision-applicability.js +1 -2
  216. package/dist/rules/dispatch-errors.js +1 -2
  217. package/dist/rules/dispatcher.d.ts.map +1 -1
  218. package/dist/rules/dispatcher.js +9 -3
  219. package/dist/rules/dispatcher.js.map +1 -1
  220. package/dist/rules/event-rules.js +1 -2
  221. package/dist/rules/processor.js +1 -2
  222. package/dist/rules/resolve.js +1 -2
  223. package/dist/rules/review-source-tool.js +1 -2
  224. package/dist/rules/start-coordinator.js +1 -2
  225. package/dist/rules/terminal-cleanup.js +1 -2
  226. package/dist/rules/tools.js +1 -2
  227. package/dist/rules/transition-service.js +1 -2
  228. package/dist/rules/types.d.ts +4 -1
  229. package/dist/rules/types.d.ts.map +1 -1
  230. package/dist/rules/types.js +1 -2
  231. package/dist/rules/types.js.map +1 -1
  232. package/dist/rules/validation.js +1 -2
  233. package/dist/run-error.d.ts +13 -0
  234. package/dist/run-error.d.ts.map +1 -0
  235. package/dist/run-error.js +81 -0
  236. package/dist/run-error.js.map +1 -0
  237. package/dist/sandbox/callback-factory-sandbox.js +1 -2
  238. package/dist/sandbox/git-ref.js +1 -2
  239. package/dist/sandbox/local-factory-sandbox.d.ts +12 -4
  240. package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
  241. package/dist/sandbox/local-factory-sandbox.js +32 -7
  242. package/dist/sandbox/local-factory-sandbox.js.map +1 -1
  243. package/dist/sandbox/materialization.js +1 -2
  244. package/dist/sandbox/session-retirement.js +1 -2
  245. package/dist/sandbox/session-sandbox.js +1 -2
  246. package/dist/sandbox/workdir.js +1 -2
  247. package/dist/secret-encryption.js +1 -2
  248. package/dist/server-error.js +1 -2
  249. package/dist/session/credential-policy.js +1 -2
  250. package/dist/session/default-model-hydration.js +1 -2
  251. package/dist/session/environment-repositories.js +1 -2
  252. package/dist/session/environment-state-processor.d.ts +0 -1
  253. package/dist/session/environment-state-processor.d.ts.map +1 -1
  254. package/dist/session/environment-state-processor.js +2 -3
  255. package/dist/session/environment-state-processor.js.map +1 -1
  256. package/dist/session/factory-session.js +1 -2
  257. package/dist/session/filesystem-capture.js +2 -3
  258. package/dist/session/filesystem-capture.js.map +1 -1
  259. package/dist/session/first-exec-capture.js +2 -3
  260. package/dist/session/first-exec-capture.js.map +1 -1
  261. package/dist/session/first-message-capture.js +1 -2
  262. package/dist/session/live-sessions.js +1 -2
  263. package/dist/session/memory-settings-hydration.js +1 -2
  264. package/dist/session/org-seed.js +1 -2
  265. package/dist/session/reply-credential-processor.js +1 -2
  266. package/dist/session/resource-migration.js +1 -2
  267. package/dist/session/run-audit.js +1 -2
  268. package/dist/session/session-threads.js +1 -2
  269. package/dist/session/session-title.js +1 -2
  270. package/dist/session/shell-commands.js +1 -2
  271. package/dist/session/source-control-tools.js +2 -3
  272. package/dist/session/thinking-default.d.ts +1 -1
  273. package/dist/session/thinking-default.d.ts.map +1 -1
  274. package/dist/session/thinking-default.js +11 -13
  275. package/dist/session/thinking-default.js.map +1 -1
  276. package/dist/session/thread-session-stamp.js +1 -2
  277. package/dist/session/thread-title-mirror.js +1 -2
  278. package/dist/session/thread-title-mirror.js.map +1 -1
  279. package/dist/session/work-item-repository.js +1 -2
  280. package/dist/session-owner-signal.js +1 -2
  281. package/dist/skills/catalog.js +1 -2
  282. package/dist/skills/service.js +1 -2
  283. package/dist/spa-static.js +1 -2
  284. package/dist/state-signing.js +1 -2
  285. package/dist/storage/domains/audit/actions.js +1 -2
  286. package/dist/storage/domains/audit/actors.js +1 -2
  287. package/dist/storage/domains/audit/agent-audit.js +1 -2
  288. package/dist/storage/domains/audit/base.js +1 -2
  289. package/dist/storage/domains/audit/domain.js +1 -2
  290. package/dist/storage/domains/audit/wire.js +1 -2
  291. package/dist/storage/domains/channel-identity/base.js +1 -2
  292. package/dist/storage/domains/comments/activity.js +1 -2
  293. package/dist/storage/domains/comments/actor.js +1 -2
  294. package/dist/storage/domains/comments/base.js +1 -2
  295. package/dist/storage/domains/comments/domain.js +1 -2
  296. package/dist/storage/domains/comments/feed-context.js +1 -2
  297. package/dist/storage/domains/comments/parse.js +1 -2
  298. package/dist/storage/domains/comments/routes.js +1 -2
  299. package/dist/storage/domains/comments/schema.js +1 -2
  300. package/dist/storage/domains/comments/wire.js +1 -2
  301. package/dist/storage/domains/credentials/base.d.ts +4 -1
  302. package/dist/storage/domains/credentials/base.d.ts.map +1 -1
  303. package/dist/storage/domains/credentials/base.js +40 -44
  304. package/dist/storage/domains/credentials/base.js.map +1 -1
  305. package/dist/storage/domains/custom-providers/base.js +1 -2
  306. package/dist/storage/domains/filesystem/base.js +1 -2
  307. package/dist/storage/domains/intake/base.d.ts +5 -0
  308. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  309. package/dist/storage/domains/intake/base.js +10 -2
  310. package/dist/storage/domains/intake/base.js.map +1 -1
  311. package/dist/storage/domains/integrations/base.js +1 -2
  312. package/dist/storage/domains/memory-settings/base.js +1 -2
  313. package/dist/storage/domains/model-defaults/base.js +1 -2
  314. package/dist/storage/domains/projects/base.d.ts +0 -1
  315. package/dist/storage/domains/projects/base.d.ts.map +1 -1
  316. package/dist/storage/domains/projects/base.js +5 -6
  317. package/dist/storage/domains/projects/base.js.map +1 -1
  318. package/dist/storage/domains/queue-health/base.js +1 -2
  319. package/dist/storage/domains/source-control/base.js +1 -2
  320. package/dist/storage/domains/source-control/inmemory.js +1 -2
  321. package/dist/storage/domains/work-items/base.js +1 -2
  322. package/dist/storage/domains/work-items/board-version.js +1 -2
  323. package/dist/storage/domains/work-items/metrics.js +1 -2
  324. package/dist/telemetry-types.js +1 -2
  325. package/dist/telemetry.js +1 -2
  326. package/dist/timing.js +1 -2
  327. package/dist/tools/annotations.js +1 -2
  328. package/dist/tools/approval-tools.js +1 -2
  329. package/dist/tools/deliver.js +2 -3
  330. package/dist/tools/index.js +1 -2
  331. package/dist/tools/messaging-tools.js +1 -2
  332. package/dist/tools/permissions.js +1 -2
  333. package/dist/tools/read-tools.js +1 -2
  334. package/dist/tools/reports.js +1 -2
  335. package/dist/tools/run-watch.d.ts +0 -2
  336. package/dist/tools/run-watch.d.ts.map +1 -1
  337. package/dist/tools/run-watch.js +4 -44
  338. package/dist/tools/run-watch.js.map +1 -1
  339. package/dist/tools/sandbox-tools.js +1 -2
  340. package/dist/tools/scope.js +2 -3
  341. package/dist/tools/session-tools.js +1 -2
  342. package/dist/tools/sessions-state-processor.js +1 -2
  343. package/dist/tools/side-sessions.js +1 -2
  344. package/dist/tools/work-item-tools.js +1 -2
  345. package/dist/work-item-branch.js +1 -2
  346. package/dist/work-item-branch.js.map +1 -1
  347. package/dist/work-items/create-work-item.js +1 -2
  348. package/dist/work-items/decision-actions.js +1 -2
  349. package/dist/workspace.d.ts.map +1 -1
  350. package/dist/workspace.js +1 -3
  351. package/dist/workspace.js.map +1 -1
  352. package/factory-skills/factory-gitlab-review/SKILL.md +1 -1
  353. package/factory-skills/factory-plan/SKILL.md +1 -1
  354. package/factory-skills/factory-rereview/SKILL.md +14 -3
  355. package/factory-skills/factory-review/SKILL.md +14 -3
  356. package/factory-skills/factory-self-review/SKILL.md +1 -1
  357. package/factory-skills/factory-triage/SKILL.md +52 -29
  358. package/package.json +19 -17
@@ -1 +1 @@
1
- {"version":3,"file":"slack.js","names":[],"sources":["../../../src/integrations/slack/slack.ts"],"sourcesContent":["import { migratePersistedModelSelection } from '@mastra/core/agent-controller';\nimport { ChannelSessionRejectedError } from '@mastra/core/channels';\nimport type {\n ChannelHandler,\n ChannelHandlerContext,\n ChannelHandlers,\n ChannelSessionResolve,\n ChannelSessionStart,\n ResolveResourceId,\n ResolveThreadId,\n} from '@mastra/core/channels';\nimport type { Mastra } from '@mastra/core/mastra';\nimport { createSlackAdapter } from '@mastra/slack';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\nimport { Card, CardText, Actions, LinkButton } from 'chat';\n\nimport { FACTORY_INTERACTIVE_KEY } from '../../auth.js';\nimport {\n primeTenantCredentialsForRequestContext,\n primeTenantCredentialsForTenant,\n} from '../../routes/tenant-credentials.js';\nimport {\n createSourceControlSessionLookup,\n FactorySourceControlConflictError,\n hydrateFactorySession,\n resolveFactorySessionModel,\n resolveFactorySourceControl,\n resolvePrimaryEnvironmentRepository,\n} from '../../session/factory-session.js';\nimport { readRequestContextOrgId, seedSessionOrg } from '../../session/org-seed.js';\nimport { isRunBound, memoryResourceOf, sessionStampCounts } from '../../session/session-threads.js';\nimport type { SessionRunBindings } from '../../session/session-threads.js';\nimport type {\n ChannelAccountLink,\n ChannelAccountLinkKey,\n ChannelIdentityStorage,\n} from '../../storage/domains/channel-identity/base.js';\nimport type { FactoryActorExternalIdentity } from '../../storage/domains/comments/actor.js';\nimport { actorFromChannelAuthor } from '../../storage/domains/comments/actor.js';\nimport type { CommentsDomain } from '../../storage/domains/comments/domain.js';\nimport type { ModelDefaultsStorage } from '../../storage/domains/model-defaults/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport type { ExternalWorkItemSource, WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport { FACTORY_SLACK_SENDER_KEY } from '../../tools/scope.js';\nimport type { FactoryChannelsConfig } from '../base.js';\nimport { prepareSessionRunContext } from '../subscription-session.js';\nimport { markNewChannelSession } from './channel-session-link-processor.js';\n\nimport { resolveEmojiShortcodes } from './emoji.js';\nimport { slackCommentSource } from './feed-publisher.js';\n\n// Derive the thread/message types from the core handler signature rather than\n// importing them from `chat` directly: mc-web can resolve a different `chat`\n// version than @mastra/core, and the two `Thread`/`Message` declarations are\n// structurally incompatible (private fields). Using the handler's own types\n// keeps everything on one version.\ntype HandlerThread = Parameters<ChannelHandler>[0];\ntype HandlerMessage = Parameters<ChannelHandler>[1];\n\nconst SLACK_REQUEST_TIMEOUT_MS = 15_000;\n\nclass SlackSessionStartError extends Error {}\n\n/** Dependencies the Slack channel handlers close over, injected from the web entry. */\ninterface SlackChannelDeps {\n /**\n * The factory's reverse-index store mapping a Slack sender to a Mastra\n * tenant. When provided, inbound messages from an unlinked sender are not\n * dispatched — the run only proceeds (with the sender's tenant stamped on\n * the request context) once they've linked their account. Unlinked senders\n * get an ephemeral \"connect your account\" card instead.\n */\n accountLinks?: ChannelIdentityStorage;\n /**\n * Factory projects domain. When provided (alongside `accountLinks`), a\n * linked sender's run must also resolve to a Factory project before it\n * dispatches: their link's default factory, else their tenant's only\n * factory (stamped back onto the link), else a \"pick a default factory\"\n * card posted in the thread and no run. Unset → no factory routing (runs\n * dispatch as before).\n */\n projects?: FactoryProjectsStorage;\n /**\n * Storage handle of the integration that owns source control\n * (`IntegrationContext.storage.sourceControlOwner`). Used to make new Slack\n * threads repo-backed: when the sender is linked and their factory has a\n * repository, the thread's resourceId becomes a Factory user-session id (repo\n * cloned on a `slack/{threadTs}` branch) instead of the chat-only\n * `channel:...` id. It also lets a started session read back the project it\n * belongs to. Nothing here is provider-specific — the connection is matched\n * by the handle's own `integrationId`. Absent (no source-control integration\n * registered) → chat-only sessions as before.\n */\n sourceControl?: SourceControlStorageHandle;\n /** Registered source-control partitions used to select the provider linked to each Factory project. */\n sourceControls?: readonly SourceControlStorageHandle[];\n /**\n * Model-defaults domain. When provided, a new repo-backed session starts on\n * the linked sender's default model before the factory's shared default. Read\n * once, when the thread's build model is first chosen; the choice is persisted\n * on the thread and never re-resolved. Unset → the factory default, as before.\n */\n modelDefaults?: ModelDefaultsStorage;\n /**\n * Factory work-items domain, used to find the card a Slack thread already\n * has so an `aside` can land on it. New threads never create cards.\n */\n workItems?: WorkItemsStorage;\n /**\n * Work-item feed. With it (and `workItems`), an `aside` lands as a comment on\n * the card the thread already has. Unset → asides stay ignored, as before.\n */\n feed?: Pick<CommentsDomain, 'createComment'>;\n /** Overrides applied to the Slack channel adapter entry. */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nfunction configuredSourceControls(deps: SlackChannelDeps): readonly SourceControlStorageHandle[] {\n return deps.sourceControls ?? (deps.sourceControl ? [deps.sourceControl] : []);\n}\n\n/**\n * Read the Slack team id off a raw platform payload (Events API envelope or\n * slash-command body — both carry `team_id`), duck-typed to build the\n * workspace-scoped account-link key.\n */\nfunction rawTeamId(rawPayload: unknown): string | undefined {\n if (!rawPayload || typeof rawPayload !== 'object') return undefined;\n const raw = rawPayload as { team_id?: unknown; team?: unknown };\n if (typeof raw.team_id === 'string' && raw.team_id) return raw.team_id;\n if (typeof raw.team === 'string' && raw.team) return raw.team;\n if (raw.team && typeof raw.team === 'object') {\n const id = (raw.team as { id?: unknown }).id;\n if (typeof id === 'string' && id) return id;\n }\n return undefined;\n}\n\n/**\n * The Slack team id survives onto a normalized chat Message only on\n * `message.raw` (the Slack Events API envelope).\n */\nfunction slackTeamId(message: HandlerMessage): string | undefined {\n return rawTeamId(message.raw);\n}\n\n/**\n * Resolve the web-UI origin for links humans open in a browser (Connect card,\n * session deep links). Prefers `MASTRACODE_PUBLIC_URL` — the origin auth\n * cookies and OAuth redirect allow-lists are registered against — over the\n * channels tunnel, which only Slack's servers need to reach.\n */\nfunction webPublicUrl(): string | undefined {\n return process.env.MASTRACODE_PUBLIC_URL ?? process.env.MASTRACODE_CHANNELS_PUBLIC_URL;\n}\n\n/** Outcome of the sender-link gate for one inbound message. */\ntype LinkedSenderResult =\n /** Gating not configured — dispatch as before account linking existed. */\n | { status: 'ungated' }\n /** Sender unlinked — Connect card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** Sender linked — their tenant plus the sender key the link lives under. */\n | { status: 'linked'; link: ChannelAccountLink; key: ChannelAccountLinkKey };\n\n/**\n * Resolve the sender's account link, posting an ephemeral \"connect your\n * account\" card (visible only to the sender) linking into the web UI's\n * Slack-connect flow when they're unlinked.\n */\nexport async function resolveLinkedSender({\n thread,\n message,\n accountLinks,\n}: {\n thread: HandlerThread;\n message: HandlerMessage;\n accountLinks?: ChannelIdentityStorage;\n}): Promise<LinkedSenderResult> {\n if (!accountLinks) return { status: 'ungated' };\n const platform = thread.adapter.name;\n const externalUserId = message.author.userId;\n const externalTeamId = slackTeamId(message);\n // Without a team id we can't identify the workspace-scoped link; treat as\n // unlinked so a run never proceeds tenant-less.\n const key = externalTeamId ? { platform, externalTeamId, externalUserId } : undefined;\n const link = key ? await accountLinks.getAccountLink(key) : null;\n if (link && key) return { status: 'linked', link, key };\n\n const publicUrl = webPublicUrl();\n // A public origin is all the card needs. The link carries no identity: the\n // web app authenticates the visitor, then Slack's OIDC flow proves which\n // Slack account they control. Without an origin, still block, just no card.\n if (publicUrl) {\n await thread.postEphemeral(message.author, buildConnectCard(publicUrl, thread), { fallbackToDM: true });\n }\n return { status: 'blocked' };\n}\n\nconst retryHint = (thread: HandlerThread) => (thread.isDM ? 'message me again' : 'mention me again');\n\n/**\n * The \"connect your account\" card. The link is deliberately identity-free —\n * `/connect/slack` sends the visitor to Connections, where \"Connect Slack\"\n * runs the OIDC flow and Slack itself asserts the (team, user) pair.\n */\nfunction buildConnectCard(publicUrl: string, thread: HandlerThread) {\n return Card({\n title: 'Connect your account',\n children: [\n CardText(`Connect your account to use this agent, then ${retryHint(thread)}.`),\n Actions([\n LinkButton({\n url: `${publicUrl}/connect/slack`,\n label: 'Connect account',\n }),\n ]),\n ],\n });\n}\n\n/** Outcome of factory routing for one linked sender's inbound message. */\ntype FactoryRouteResult =\n /** Factory routing not configured — dispatch without a factory. */\n | { status: 'ungated' }\n /** No factory resolved — prompt card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** The Factory project this sender's runs route to. */\n | { status: 'resolved'; factoryProjectId: string };\n\n/**\n * Decide which Factory project a linked sender's run belongs to:\n *\n * 1. The link's `defaultFactoryProjectId`, when it still exists (a stale id —\n * deleted factory — falls through as if unset).\n * 2. Else, the tenant's only factory, stamped back onto the link so it shows\n * up (and stays editable) in Connected Accounts settings.\n * 3. Else — zero or several factories — a \"pick a default factory\" card\n * posted publicly in the thread so Slack notifies the sender, and the run is\n * blocked.\n */\nexport async function resolveFactoryForLink({\n thread,\n link,\n key,\n accountLinks,\n projects,\n}: {\n thread: HandlerThread;\n link: ChannelAccountLink;\n key: ChannelAccountLinkKey;\n accountLinks: ChannelIdentityStorage;\n projects?: FactoryProjectsStorage;\n}): Promise<FactoryRouteResult> {\n if (!projects) return { status: 'ungated' };\n // Factories are org-scoped; a personal account (no org) has none and lands\n // on the prompt below.\n const orgId = link.orgId ?? '';\n\n if (link.defaultFactoryProjectId) {\n const existing = await projects.get({ orgId, id: link.defaultFactoryProjectId });\n if (existing) {\n return { status: 'resolved', factoryProjectId: existing.id };\n }\n }\n\n const factories = orgId ? await projects.list({ orgId }) : [];\n if (factories.length === 1) {\n const only = factories[0]!;\n await accountLinks.setDefaultFactory({ ...key, userId: link.userId, factoryProjectId: only.id });\n return { status: 'resolved', factoryProjectId: only.id };\n }\n\n const publicUrl = webPublicUrl();\n if (publicUrl) {\n await thread.post(\n Card({\n title: 'Pick a default factory',\n children: [\n CardText(\n factories.length === 0\n ? `Your account has no factory yet. Create one in the web app, then ${retryHint(thread)}.`\n : `Your account has several factories. Pick which one Slack sessions should go to, then ${retryHint(thread)}.`,\n ),\n Actions([\n LinkButton({\n url: `${publicUrl}/settings/connections`,\n label: 'Open settings',\n }),\n ]),\n ],\n }),\n );\n }\n return { status: 'blocked' };\n}\n\n/**\n * Deterministic per-thread branch name: `slack/{threadTs}` with characters\n * outside the sandbox git-ref allow-list (`[A-Za-z0-9_./-]`, and `.` for\n * readability) mapped to `-`. `thread.id` is `{channelId}:{threadTs}`\n * (platform-prefixed on handler threads) — the trailing segment is the ts.\n *\n * Top-level DM and channel conversations use the empty-threadTs thread form,\n * so the trailing segment can be empty; a bare `slack/` is not a valid git\n * ref. Fall back to the last non-empty segment (the channel id): one\n * deterministic branch per top-level conversation.\n */\nfunction threadBranch(threadId: string): string {\n const segments = threadId.split(':');\n const tail = segments.findLast(segment => segment.length > 0) ?? threadId;\n return `slack/${tail.replace(/[^A-Za-z0-9_/-]/g, '-')}`;\n}\n\n/**\n * Session ids of repo-backed Slack threads, keyed by the message that started\n * them. The resourceId resolver records the session it found or created; the\n * thread-id resolver, which core runs next for the same message, names the new\n * thread after it.\n */\nexport type SlackStartedSessions = WeakMap<object, string>;\n\n/**\n * Resolve the resourceId for a NEW Slack channel thread. A linked sender whose\n * factory has a repository gets a Factory session. A channel thread belongs to\n * the Factory: its resourceId is the Factory id, so the thread shares the\n * Factory's memory. A DM is private to the sender, so it runs under the\n * sender's user id and shares memory with their own sessions. The session id is recorded in `startedSessions` for the\n * thread-id resolver. The controller\n * session then materializes the repo sandbox via the factory's dynamic\n * workspace (clone + PAT), the session shows up in the web Sessions list, and\n * View Session deep-links land on the normal workspace route.\n *\n * A gated deployment refuses a thread it cannot place, rather than falling back\n * to a chat-only session: a repo-backed thread is the whole point of the\n * integration, and answering in a chat-only one would run the sender's request\n * in no project, on the SDK's built-in defaults. A linked sender whose project\n * cannot start a session gets an actionable error in Slack; the dispatch gate\n * handles unlinked or unrouted senders before this hook runs.\n * The hook only runs when a NEW thread is actually created (`getOrCreateThread`\n * resolves the owner lazily), so an established conversation is never touched:\n * it keeps its session, its model, and its history.\n *\n * The chat-only `channel:...` id survives for two cases where refusing would be\n * wrong: a deployment that cannot produce a repo-backed thread at all (no\n * account linking, no projects, or no source-control integration registered),\n * and a message carrying no sender id. In both, a `channel:` thread is the only\n * shape a session can take.\n *\n * Pure lookups only — cards for unlinked/unrouted senders are the dispatch\n * gate's job; this hook must never post.\n */\nexport function createChannelResourceIdResolver(\n deps: SlackChannelDeps,\n startedSessions: SlackStartedSessions = new WeakMap(),\n): ResolveResourceId {\n const { accountLinks, projects } = deps;\n const sourceControls = configuredSourceControls(deps);\n return async ({ platform, thread, message }) => {\n // NOT the hook's `defaultResourceId`: configuring a custom resolver\n // bypasses AgentControllerChannels' own `channel:{thread.id}` derivation\n // (agent-controller-channels.ts `resolveChannelResourceId`), and the base\n // default is the per-USER memory key. Chat-only fallbacks must stay\n // per-thread, so reproduce the controller default here.\n const chatOnlyResourceId = `channel:${thread.id}`;\n // A deployment with no account linking, no projects, or no source-control\n // integration cannot produce a repo-backed thread at all, so a `channel:`\n // thread is the only shape it has. Every refusal below is a different thing\n // entirely: a sender the bot cannot place, in a deployment that can.\n if (!accountLinks || !projects || sourceControls.length === 0) return chatOnlyResourceId;\n // No sender id at all — a malformed message, not a sender the host turned\n // away. There is nothing to place, so give it the per-thread memory key.\n if (!message.author.userId) return chatOnlyResourceId;\n\n const externalTeamId = rawTeamId(message.raw);\n if (!externalTeamId) {\n throw new ChannelSessionRejectedError('No Slack workspace id on the message — the sender cannot be identified');\n }\n try {\n const link = await accountLinks.getAccountLink({\n platform,\n externalTeamId,\n externalUserId: message.author.userId,\n });\n if (!link) {\n throw new ChannelSessionRejectedError(\n `Slack sender ${message.author.userId} is not linked to a Factory account`,\n );\n }\n\n // Same chain as `resolveFactoryForLink`, minus prompts/stamping: the\n // dispatch gate has already run (and stamped a lone factory) by the\n // time a new thread is created, so this is a read-only re-resolve.\n const orgId = link.orgId ?? '';\n let factoryProjectId: string | undefined;\n if (link.defaultFactoryProjectId && (await projects.get({ orgId, id: link.defaultFactoryProjectId }))) {\n factoryProjectId = link.defaultFactoryProjectId;\n } else if (orgId) {\n const factories = await projects.list({ orgId });\n if (factories.length === 1) factoryProjectId = factories[0]!.id;\n }\n if (!factoryProjectId) {\n throw new ChannelSessionRejectedError(`Linked Slack sender ${message.author.userId} has no Factory project`);\n }\n\n // Linked and routed, so a repo-backed session is not a preference: with no\n // repository to work in there is no thread to start, and starting one\n // anyway would answer in a project the sender never picked, on the SDK's\n // built-in defaults.\n const sourceControl = await resolveFactorySourceControl({ sourceControls, orgId, factoryProjectId });\n if (!sourceControl) {\n const connections = await Promise.all(\n sourceControls.map(sourceControl => sourceControl.connections.list({ orgId, factoryProjectId })),\n );\n throw new SlackSessionStartError(\n connections.some(rows => rows.length > 0)\n ? 'Could not start a session: link a repository to this Factory project.'\n : 'Could not start a session: connect source control to this Factory project.',\n );\n }\n // The session's link is the factory's position-1 environment\n // repository (D1); the sandbox boots every environment repository.\n const repo = await resolvePrimaryEnvironmentRepository({ sourceControl, orgId, factoryProjectId });\n if (!repo.found) {\n throw new SlackSessionStartError(\n repo.reason === 'connection'\n ? 'Could not start a session: connect source control to this Factory project.'\n : 'Could not start a session: link a repository to this Factory project.',\n );\n }\n\n const branch = threadBranch(thread.id);\n const memoryResourceId = thread.isDM ? link.userId : factoryProjectId;\n // Attributed to the Slack sender, not to whoever connected the repository:\n // unlike an autonomous rule run, a Slack thread has a real interactive user.\n // Keyed by factory, not by link: the thread's session stays found when the\n // position-1 repository changes between two messages.\n const existing = await sourceControl.sessions.getForBranch({\n factoryProjectId,\n userId: link.userId,\n branch,\n });\n if (existing) {\n startedSessions.set(message, existing.sessionId);\n return memoryResourceId;\n }\n const session = await sourceControl.sessions.create({\n sessionId: globalThis.crypto.randomUUID(),\n projectRepositoryId: repo.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId: link.userId,\n branch,\n baseBranch: repo.baseBranch,\n // DMs are the only private origin; channel threads are org-visible.\n visibility: thread.isDM ? 'private' : 'org',\n });\n startedSessions.set(message, session.sessionId);\n return memoryResourceId;\n } catch (error) {\n if (error instanceof ChannelSessionRejectedError || error instanceof SlackSessionStartError) throw error;\n if (error instanceof FactorySourceControlConflictError) {\n throw new SlackSessionStartError(\n 'Could not start a session: this Factory project has repositories from more than one source-control provider.',\n { cause: error },\n );\n }\n console.error('[slack] failed to start repo-backed session for thread', thread.id, error);\n throw new SlackSessionStartError('Could not start a session right now. Please try again later.', {\n cause: error,\n });\n }\n };\n}\n\n/**\n * Thread id for a NEW Slack channel thread. Repo-backed threads take the\n * session id AS their thread id, matching the web convention\n * (FactoryStartCoordinator seeds threads with threadId = sessionId) so\n * `/workspaces/{sessionId}/threads/{sessionId}` resolves Slack-created\n * sessions exactly like web-created ones — no `?sessionId=` override needed.\n * Chat-only threads keep the default random id: their `channel:...`\n * resourceId is a memory key, not a unique thread id.\n */\nexport function createChannelThreadIdResolver(startedSessions: SlackStartedSessions): ResolveThreadId {\n return ({ message, defaultThreadId }) => startedSessions.get(message) ?? defaultThreadId;\n}\n\n/**\n * The Factory session a repo-backed Slack thread belongs to. A thread under\n * the Factory resource is stamped with its session id (the startup migration\n * stamps threads it moved there). An unmigrated thread runs under its session\n * id as its resource, and a newer thread is named after its session. A stamp\n * or thread id only counts under that session's memory resource; a thread\n * named after a session it can't claim is refused. With no session row for\n * either, the thread is addressed by its own id and fails authorization like\n * any session without a row.\n */\nconst UNCLAIMED_SESSION = Symbol('unclaimed-session');\n\nasync function slackThreadSessionId(\n sessions: ReturnType<typeof createSourceControlSessionLookup>,\n thread: { id: string; resourceId: string; metadata?: Record<string, unknown> },\n loadThread: (threadId: string) => Promise<{ resourceId: string } | null | undefined>,\n runs: SessionRunBindings | undefined,\n): Promise<string | null | typeof UNCLAIMED_SESSION> {\n if (thread.resourceId.startsWith('channel:')) return null;\n // Stamps and thread ids are client-writable: a session only claims a thread\n // under its own memory resource.\n const claims = async (sessionId: string) => {\n const row = await sessions.getBySessionId(sessionId);\n if (!row) return false;\n // A stamp only counts where the session's stamps are trusted.\n if (sessionId !== thread.id && !sessionStampCounts(row, thread.resourceId)) return false;\n const own = sessionId === thread.id ? thread : await loadThread(sessionId);\n return thread.resourceId === memoryResourceOf(row, own, await isRunBound(row, runs));\n };\n const stamped = thread.metadata?.factorySessionId;\n if (typeof stamped === 'string' && stamped && (await claims(stamped))) return stamped;\n if (await sessions.getSourceControlBySessionId(thread.resourceId)) return thread.resourceId;\n if (await claims(thread.id)) return thread.id;\n if (await sessions.getBySessionId(thread.id)) return UNCLAIMED_SESSION;\n // With no session row the thread is its own session: falling back to the\n // resource would put every such thread under the Factory (or a sender) into\n // one shared session.\n return thread.id;\n}\n\n/** Create channel sessions with their Factory ownership present in initial controller state. */\nexport function createChannelSessionResolver(deps: SlackChannelDeps): ChannelSessionResolve {\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ controller, thread, requestContext }) => {\n // A session-bound Slack thread is addressed by its Factory session id and\n // runs under the thread's resource (the Factory). A chat-only thread is its\n // own session, addressed by its `channel:` resource.\n // Approval clicks pass the thread without its metadata; load the stored\n // thread whenever the session stamp is missing so a migrated thread's\n // stamp is read.\n const stamped = (thread as { metadata?: Record<string, unknown> }).metadata?.factorySessionId;\n const stored =\n stamped || thread.resourceId.startsWith('channel:')\n ? thread\n : ((await controller.queryThreadById({ threadId: thread.id })) ?? thread);\n const resolved = await slackThreadSessionId(\n sourceControlSessions,\n stored,\n threadId => controller.queryThreadById({ threadId }),\n deps.workItems,\n );\n if (resolved === UNCLAIMED_SESSION) {\n throw new Error(`Slack thread ${thread.id} is named after a Factory session it doesn't belong to.`);\n }\n const sessionId = resolved ?? thread.resourceId;\n const sourceControl = sessionId.startsWith('channel:')\n ? null\n : await sourceControlSessions.getSourceControlBySessionId(sessionId);\n const session = sourceControl ? await sourceControl.sessions.getBySessionId(sessionId) : null;\n return controller.createSession({\n id: sessionId,\n ownerId: controller.id,\n resourceId: thread.resourceId,\n // Bind the Slack-created thread itself. Without it the session\n // auto-selects (or creates) another thread and is switched onto this one later.\n threadId: thread.id,\n requestContext,\n ...(session?.factoryProjectId ? { tags: { factoryProjectId: session.factoryProjectId } } : {}),\n });\n };\n}\n\n/**\n * Apply the factory's configuration to a Slack-created session the first time\n * its thread reaches the controller.\n *\n * Without this a Slack session runs on the SDK's built-in mode default\n * (`openai/gpt-5.5`), so a factory configured for any other provider fails every\n * message with a missing-credentials error. The web kickoff has always applied\n * the factory default; this brings Slack to the same footing.\n *\n * Only repo-backed threads are configured. Their resourceId IS the Factory\n * session id, which the source-control rows turn back into a project — a\n * chat-only `channel:...` id names no project, so there is nothing to read.\n *\n * The model this session starts on is picked here, once, from the project's\n * effective model scope for a session: the Factory default or the durable session owner's\n * default, else the SDK's built-in model. The choice is persisted on the thread\n * as `currentModelId`, so it outlives the process that made it.\n *\n * The model resolution is skipped on a session that already has a model\n * persisted on the thread. That is the durable record of a deliberate choice —\n * either an earlier start or a user's own switch — and re-applying a preference\n * over it would undo the user's selection every time the process restarts.\n */\nexport function createChannelSessionStartHook(deps: SlackChannelDeps): ChannelSessionStart {\n const { projects, modelDefaults } = deps;\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ session, thread, requestContext }) => {\n // Seed the tenant org above every guard below. `gateDispatch` stamps it on\n // the message's request context before the session exists, so this needs no\n // storage read — which matters, because the model lookups below deliberately\n // skip storage on a restarted session. A channel-only thread and a thread\n // whose dispatch was ungated both land here with no org and are marked\n // unresolved rather than being left to look like a local session.\n await seedSessionOrg(session, readRequestContextOrgId(requestContext));\n\n if (!projects) return;\n if (thread.resourceId.startsWith('channel:')) return;\n\n const sessionId = session.identity.getId();\n const sourceControl = await sourceControlSessions.getSourceControlBySessionId(sessionId);\n if (!sourceControl) return;\n const sourceSession = await sourceControl.sessions.getBySessionId(sessionId);\n if (!sourceSession?.factoryProjectId) return;\n const threadId = session.thread.getId();\n const binding =\n threadId && deps.workItems\n ? await deps.workItems.findRunBindingByThread({\n orgId: sourceSession.orgId,\n resourceId: session.identity.getResourceId(),\n sessionId,\n threadId,\n rejectAmbiguous: true,\n })\n : null;\n const owner = {\n factoryProjectId: binding?.factoryProjectId ?? sourceSession.factoryProjectId,\n orgId: sourceSession.orgId,\n userId: sourceSession.userId,\n };\n\n const sessionModel = await resolveFactorySessionModel({\n projects,\n modelDefaults,\n factoryProjectId: owner.factoryProjectId,\n orgId: owner.orgId,\n ownerUserId: owner.userId,\n context: binding ? 'run' : 'session',\n });\n\n // Repo-backed Slack sessions use the source-control row's durable owner,\n // never the sender, for both model and credential scope.\n await session.state.set({\n factoryProjectId: owner.factoryProjectId,\n factoryModelScope: sessionModel.modelScope,\n factoryOwnerUserId: owner.userId,\n });\n // Route the org through the seed helper rather than stamping it directly:\n // an ungated dispatch marked this session unresolved above, and owner\n // recovery is the resolution that has to clear that marker with it.\n await seedSessionOrg(session, owner.orgId);\n await primeTenantCredentialsForTenant({\n orgId: owner.orgId,\n userId: owner.userId,\n credentialScope: sessionModel.modelScope,\n });\n\n const activeThreadId = session.thread.getId();\n // Threads of one Factory share its resource; the stamp ties this thread to its session.\n if (activeThreadId) {\n await session.thread.setSettingOn({ threadId: activeThreadId, key: 'factorySessionId', value: sessionId });\n }\n const modeId = session.mode.get();\n const persistedModelId = await migratePersistedModelSelection({\n getMetadata: async () =>\n activeThreadId\n ? (((await session.thread.getById({ threadId: activeThreadId }))?.metadata as\n Record<string, unknown> | undefined) ?? {})\n : {},\n modeId,\n set: (key: string, value: unknown) =>\n activeThreadId ? session.thread.setSettingOn({ threadId: activeThreadId, key, value }) : Promise.resolve(),\n threadId: activeThreadId ?? undefined,\n });\n if (persistedModelId) {\n // Subagent models live in session state only, so restore the ones this\n // thread pinned at its first start instead of re-resolving them.\n for (const agentType of SUBAGENT_TYPES) {\n const modelId = await session.thread.getSetting({ key: pinnedSubagentModelKey(agentType) });\n if (typeof modelId === 'string') await applySubagentModel(session, agentType, modelId);\n }\n } else {\n // `hydrateFactorySession` persists the scoped model, a connected provider's\n // provider default, or the SDK mode default as this thread's model of record.\n const selectedModelId =\n (await hydrateFactorySession(session, {\n orgId: owner.orgId,\n modelScope: sessionModel.modelScope,\n ownerUserId: owner.userId,\n defaultModelId: sessionModel.modelId,\n })) ?? session.model.get();\n\n // Pin subagent models on the thread like the main model so a restart\n // restores the selected scope's choice rather than re-resolving it.\n for (const agentType of SUBAGENT_TYPES) {\n if (!selectedModelId) continue;\n if (await applySubagentModel(session, agentType, selectedModelId)) {\n await session.thread.setSetting({ key: pinnedSubagentModelKey(agentType), value: selectedModelId });\n }\n }\n }\n };\n}\n\nconst SUBAGENT_TYPES = ['explore', 'plan', 'execute'] as const;\nconst pinnedSubagentModelKey = (agentType: string) => `slackSubagentModelId_${agentType}`;\n\n/** Best-effort: a subagent model that can't be applied must not fail the message. */\nasync function applySubagentModel(\n session: Parameters<ChannelSessionStart>[0]['session'],\n agentType: (typeof SUBAGENT_TYPES)[number],\n modelId: string,\n): Promise<boolean> {\n try {\n await session.subagents.model.set({ modelId, agentType });\n return true;\n } catch (error) {\n console.warn('[slack] Failed to apply the subagent model', {\n agentType,\n modelId,\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n}\n\n/**\n * The internal Mastra thread the framework created for a channel conversation.\n * The handler's `thread.id` is the platform thread id (e.g. `slack:C123:ts`),\n * NOT the internal UUID — the mapping lives in the stored thread's channel\n * metadata.\n */\nasync function findInternalThread(mastra: Mastra | undefined, thread: HandlerThread) {\n const store = await mastra?.getStorage()?.getStore('memory');\n const { threads } = (await store?.listThreads({\n filter: {\n metadata: {\n channel_platform: thread.adapter.name,\n channel_externalThreadId: thread.id,\n channel_externalChannelId: thread.channelId,\n },\n },\n perPage: 1,\n })) ?? { threads: [] };\n return threads[0] ?? null;\n}\n\ntype InternalThread = Awaited<ReturnType<typeof findInternalThread>>;\n\nasync function prepareExistingSessionOwnerContext(\n thread: HandlerThread,\n deps: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n options: {\n expectedOrgId?: string;\n senderUserId?: string;\n requireInternalThread: boolean;\n /** Already looked up by the caller; `null` means it doesn't exist. */\n internalThread?: InternalThread;\n },\n): Promise<'ready' | 'organization-mismatch' | 'access-denied'> {\n const sourceControls = configuredSourceControls(deps);\n if (sourceControls.length === 0 && !options.requireInternalThread) return 'ready';\n\n const internalThread =\n options.internalThread !== undefined ? options.internalThread : await findInternalThread(ctx.mastra, thread);\n if (!internalThread) {\n if (options.requireInternalThread) {\n throw new Error(`Could not resolve the internal Slack thread for ${thread.id}.`);\n }\n return 'ready';\n }\n if (sourceControls.length === 0 || internalThread.resourceId.startsWith('channel:')) return 'ready';\n const sessions = createSourceControlSessionLookup(sourceControls);\n const memory = await ctx.mastra?.getStorage()?.getStore('memory');\n const resolved = await slackThreadSessionId(\n sessions,\n internalThread,\n async threadId => memory?.getThreadById({ threadId }),\n deps.workItems,\n );\n if (resolved === UNCLAIMED_SESSION) return 'access-denied';\n const sessionId = resolved ?? internalThread.resourceId;\n const sessionRow = await sessions.getBySessionId(sessionId);\n if (\n sessionRow &&\n sessionRow.orgId === options.expectedOrgId &&\n sessionRow.visibility === 'private' &&\n sessionRow.userId !== options.senderUserId\n ) {\n return 'access-denied';\n }\n if (!options.expectedOrgId) {\n throw new Error(\n `Could not authorize the owner of Slack Factory session ${sessionId}: the sender's account link has no organization.`,\n );\n }\n\n const preparation = await prepareSessionRunContext(\n ctx.requestContext,\n sessionId,\n { sessions },\n { expectedOrgId: options.expectedOrgId },\n );\n if (preparation === 'organization-mismatch') return preparation;\n if (preparation !== 'prepared') {\n const reason =\n preparation === 'session-not-found'\n ? 'no source-control session row exists for it, or its project repository row is missing'\n : 'its source-control session row has no organization';\n throw new Error(`Could not authorize the owner of Slack Factory session ${sessionId}: ${reason}.`);\n }\n return 'ready';\n}\n\n/**\n * Build the \"new session\" handler for mention / direct-message events. A mention or\n * DM on a not-yet-subscribed thread starts a NEW session; once subscribed, later\n * events are follow-ups and don't re-announce.\n */\n/**\n * Run the account-link + factory-routing gates for one inbound message.\n * Returns `null` when the run must not dispatch (a prompt card was posted\n * where possible); otherwise the dispatch context — with `routed` present\n * only when a linked sender resolved to a factory.\n */\nasync function gateDispatch(\n thread: HandlerThread,\n message: HandlerMessage,\n deps: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n options: { requireInternalThread: boolean; internalThread?: InternalThread } = { requireInternalThread: false },\n): Promise<{\n routed?: { link: ChannelAccountLink; factoryProjectId: string };\n} | null> {\n const { accountLinks, projects } = deps;\n const sender = await resolveLinkedSender({ thread, message, accountLinks });\n if (sender.status === 'blocked') return null;\n // Linked senders must also route to a Factory project before a run starts.\n if (sender.status === 'linked' && accountLinks) {\n // Stamp the tenant on the run's request context — the single seam\n // `resolveCredentialStore` reads to load this sender's model credentials.\n // This belongs to the link, not to the routing: a linked sender whose\n // factory routing comes back `ungated` still exits below and dispatches, so\n // stamping only in the routed branch would silently run them on default\n // credentials.\n ctx.requestContext.set('user', { id: sender.link.userId, organizationId: sender.link.orgId });\n const ownerContext = await prepareExistingSessionOwnerContext(thread, deps, ctx, {\n expectedOrgId: sender.link.orgId,\n senderUserId: sender.link.userId,\n requireInternalThread: options.requireInternalThread,\n internalThread: options.internalThread,\n });\n if (ownerContext === 'access-denied') {\n await thread.post('This private Factory session belongs to another user.');\n return null;\n }\n if (ownerContext === 'organization-mismatch') {\n await thread.post('This thread belongs to a Factory session in another organization.');\n return null;\n }\n // Credential resolution reads an in-memory snapshot that only priming\n // fills, so prime whichever tenant now owns the run (the sender, or the\n // existing session's owner) — otherwise a cold snapshot after a restart\n // fails the run until that user happens to hit the web UI.\n await primeTenantCredentialsForRequestContext(ctx.requestContext);\n\n const route = await resolveFactoryForLink({ thread, ...sender, accountLinks, projects });\n if (route.status === 'blocked') return null;\n if (route.status === 'resolved') {\n // The model runs with the session owner's credentials, but Factory tools\n // must act as the authenticated Slack sender, not the session owner.\n if (sender.link.orgId) {\n ctx.requestContext.set(FACTORY_SLACK_SENDER_KEY, { userId: sender.link.userId, orgId: sender.link.orgId });\n ctx.requestContext.set(FACTORY_INTERACTIVE_KEY, true);\n }\n return { routed: { link: sender.link, factoryProjectId: route.factoryProjectId } };\n }\n }\n return {};\n}\n\n/**\n * A channel id and a `ts` name a thread only inside the workspace that issued\n * them, so the team scopes the card's key — without it two workspaces could\n * hold one key and an aside would land on another tenant's card.\n */\nexport function slackThreadSource(thread: HandlerThread, teamId?: string): ExternalWorkItemSource {\n return {\n integrationId: thread.adapter.name,\n type: 'slack-thread',\n ...(teamId ? { workspaceId: teamId } : {}),\n externalId: thread.id,\n };\n}\n\n/** Cards written before the key carried a team are still keyed bare. */\nasync function findThreadWorkItem(\n workItems: WorkItemsStorage,\n thread: HandlerThread,\n teamId?: string,\n): Promise<WorkItemRow | null> {\n const scoped = await workItems.getBySource(slackThreadSource(thread, teamId));\n if (scoped || !teamId) return scoped;\n return workItems.getBySource(slackThreadSource(thread));\n}\n\nfunction preDispatchErrorDetails(error: unknown): { message: string; cause?: { message: string } } {\n const readMessage = (value: unknown) => {\n const message =\n typeof value === 'string'\n ? value\n : value && typeof value === 'object' && 'message' in value\n ? value.message\n : undefined;\n return typeof message === 'string' && message.length > 0 ? message : undefined;\n };\n try {\n const message = readMessage(error) ?? 'Unknown error';\n const cause = error && typeof error === 'object' && 'cause' in error ? readMessage(error.cause) : undefined;\n return { message, ...(cause ? { cause: { message: cause } } : {}) };\n } catch {\n return { message: 'Error details unavailable' };\n }\n}\n\nasync function reportPreDispatchError(\n thread: HandlerThread,\n message: HandlerMessage,\n ctx: ChannelHandlerContext,\n error: unknown,\n): Promise<void> {\n try {\n if (error instanceof ChannelSessionRejectedError) return;\n } catch {\n // A thrown proxy can fail even the refusal check; still report the failure.\n }\n const correlation = {\n platform: thread.adapter.name,\n threadId: thread.id,\n messageId: message.id,\n authorId: message.author.userId,\n };\n const logger = typeof ctx.mastra?.getLogger === 'function' ? ctx.mastra.getLogger() : undefined;\n const logError = (line: string) => (logger ? logger.error(line) : console.error(line));\n logError(`[slack] Pre-dispatch failure ${JSON.stringify({ ...correlation, error: preDispatchErrorDetails(error) })}`);\n try {\n // The message id is the lookup key for the diagnostic above.\n await thread.post(`Couldn’t start processing your message. Please try again.\\n\\`messageId: ${message.id}\\``);\n } catch (deliveryError) {\n logError(\n `[slack] Failed to deliver pre-dispatch error reply ${JSON.stringify({ ...correlation, error: preDispatchErrorDetails(deliveryError) })}`,\n );\n }\n}\n\n/**\n * What the sender sees while nothing has been posted yet. Core's typing\n * wrapper only sets a status once the agent stream yields its first chunk,\n * and everything before that — credential priming, session rows, the\n * controller start hook, model time-to-first-token — is silent. Pin the same\n * status Factory's `typingStatus` uses for reasoning so the bot visibly\n * \"picks up\" as soon as the gate passes, and the stream's own status replaces\n * it without a visual change. Slack clears it on the first post; core clears\n * it when a run ends without posting.\n */\nconst EARLY_TYPING_STATUS = 'is thinking...';\n\nfunction acknowledgeReceipt(thread: HandlerThread, deps: SlackChannelDeps): void {\n if (deps.adapterOptions?.typingStatus === false) return;\n // Best-effort like every other status call: a failed status must not\n // keep the run from starting.\n void thread.startTyping(EARLY_TYPING_STATUS).catch(error => {\n console.warn('[slack] early typing status failed', { thread: thread.id, error });\n });\n}\n\nfunction createNewSessionChatHandler(deps: SlackChannelDeps): ChannelHandler {\n return async (thread, message, defaultHandler, ctx) => {\n // Gate on the sender having linked their Slack account to a Mastra tenant.\n // Unlinked → post the ephemeral Connect card and stop; no session/run is\n // created (which would otherwise be tenant-less and fail credential\n // resolution). This handler is the only gate — core dispatches whatever\n // reaches it — so every slot that can start a run must call it.\n try {\n if (!(await gateDispatch(thread, message, deps, ctx))) return;\n // A mention on a not-yet-subscribed thread is a NEW session. The default\n // handler auto-subscribes, so once subscribed this is a follow-up — the\n // thread already carries its link. Factory posts nothing of its own:\n // FactoryChannelSessionLinkProcessor emits the `Open session ↗` link right\n // after the agent's first reply.\n if (!(await thread.isSubscribed())) markNewChannelSession(ctx.requestContext);\n acknowledgeReceipt(thread, deps);\n } catch (error) {\n await reportPreDispatchError(thread, message, ctx, error);\n return;\n }\n await defaultHandler(thread, message);\n };\n}\n\n/**\n * An `aside` never reaches the agent, but it is still part of the conversation:\n * record it on the card the thread already has. The link is resolved here rather\n * than through `gateDispatch`, which posts a Connect card an aside must not\n * trigger. Best-effort — nothing here may surface in the Slack thread.\n */\nasync function ingestAside(\n thread: HandlerThread,\n message: HandlerMessage,\n { feed, workItems, accountLinks }: SlackChannelDeps,\n): Promise<void> {\n if (!feed || !workItems) return;\n const body = resolveEmojiShortcodes(message.text.replace(/^aside\\b[,:]?\\s*/i, '').trim());\n if (!body) return;\n try {\n const teamId = slackTeamId(message);\n const workItem = await findThreadWorkItem(workItems, thread, teamId);\n if (!workItem) return;\n\n const external: FactoryActorExternalIdentity = {\n platform: thread.adapter.name,\n ...(teamId ? { teamId } : {}),\n messageId: message.id,\n userId: message.author.userId,\n userName: message.author.userName,\n fullName: message.author.fullName,\n isBot: message.author.isBot,\n };\n const link =\n accountLinks && teamId\n ? await accountLinks.getAccountLink({\n platform: external.platform,\n externalTeamId: teamId,\n externalUserId: external.userId,\n })\n : null;\n\n const result = await feed.createComment({\n orgId: workItem.orgId,\n workItemId: workItem.id,\n author: actorFromChannelAuthor(external, link),\n body,\n occurredAt: message.metadata.dateSent,\n externalSource: slackCommentSource(thread.id, message.id, teamId),\n });\n if (result.status !== 'created') {\n console.warn('[slack] aside not ingested', { thread: thread.id, messageTs: message.id, status: result.status });\n }\n } catch (error) {\n // Slack was acked before this handler ran, so a lost aside is never redelivered.\n console.warn('[slack] aside ingest failed', { thread: thread.id, messageTs: message.id, error });\n }\n}\n\nexport const createHandlers = (deps: SlackChannelDeps): ChannelHandlers => {\n const newSessionChatHandler = createNewSessionChatHandler(deps);\n\n return {\n onSubscribedMessage: async (thread, message, defaultHandler, ctx) => {\n // `aside` as its own leading word lets humans talk in a subscribed\n // thread without the bot replying. Word boundary so messages that\n // merely start with \"aside...\" (e.g. \"asides can wait\") still route.\n if (/^aside\\b/i.test(message.text)) return ingestAside(thread, message, deps);\n // Looked up once here for the mute check and the access gate below.\n const internalThread = await findInternalThread(ctx.mastra, thread);\n // A muted thread ignores everything that doesn't tag the bot, and the\n // skipped messages never reach the session.\n if (!message.isMention && internalThread?.metadata?.[MUTED_KEY] === 'true') return;\n // A subscribed follow-up from an unlinked sender must not run either\n // (e.g. the link was removed mid-conversation), and it must still\n // resolve a factory (e.g. the default was cleared or its factory\n // deleted mid-conversation). Mute and unmute pass the same gate, so only\n // someone allowed to prompt this session can change what it listens to.\n try {\n const gate = await gateDispatch(thread, message, deps, ctx, { requireInternalThread: true, internalThread });\n if (!gate) return;\n } catch (error) {\n await reportPreDispatchError(thread, message, ctx, error);\n return;\n }\n const command = message.isMention ? muteCommand(message) : undefined;\n if (command && internalThread) return setThreadMuted(thread, internalThread, message, ctx, command === 'mute');\n acknowledgeReceipt(thread, deps);\n await defaultHandler(thread, message);\n },\n onMention: async (thread, message, defaultHandler, ctx) => {\n // `@bot mute` in a thread the bot never joined has nothing to mute, and\n // must not start a session about the word \"mute\".\n if (muteCommand(message)) return;\n await newSessionChatHandler(thread, message, defaultHandler, ctx);\n },\n onDirectMessage: newSessionChatHandler,\n };\n};\n\n/** Thread metadata key; one Factory runs one Slack app, so this is per bot. */\nconst MUTED_KEY = 'factory_muted';\n\n/**\n * `mute` or `unmute` when that word, with the mentions taken out, is the whole\n * message. Reads Slack's raw text, where a mention is still `<@U…>`: the\n * normalized `message.text` spells it as the bot's display name, which can\n * hold spaces and punctuation (`@Mastra Factory(Caleb)`).\n */\nfunction muteCommand(message: HandlerMessage): 'mute' | 'unmute' | undefined {\n const raw = message.raw as { text?: unknown } | undefined;\n const text = typeof raw?.text === 'string' ? raw.text : message.text;\n const rest = text\n .replace(/<@[^>]+>/g, ' ')\n .replace(/(?<!\\w)@[\\w.-]+/g, ' ')\n .trim()\n .replace(/[.!]+$/, '')\n .toLowerCase();\n return rest === 'mute' || rest === 'unmute' ? rest : undefined;\n}\n\n/**\n * Stored on the Mastra thread next to `channel_subscribed`, so a mute holds\n * across deploys and replicas. Callers run the sender's link and session\n * access gate first. A reaction confirms it without posting.\n */\nasync function setThreadMuted(\n thread: HandlerThread,\n internal: NonNullable<InternalThread>,\n message: HandlerMessage,\n ctx: ChannelHandlerContext,\n muted: boolean,\n): Promise<void> {\n const store = await ctx.mastra?.getStorage()?.getStore('memory');\n if (!store) return;\n await store.patchThread({ id: internal.id, metadata: { ...internal.metadata, [MUTED_KEY]: String(muted) } });\n try {\n await thread.adapter.addReaction(thread.id, message.id, muted ? 'mute' : 'loud_sound');\n } catch (error) {\n console.warn('[slack] mute reaction failed', { thread: thread.id, error });\n }\n}\n\n/** Slack app credentials, passed in explicitly rather than read from env here. */\ninterface SlackCredentials {\n clientId?: string;\n clientSecret?: string;\n signingSecret: string;\n botToken?: string;\n}\n\nexport function createSlackChannelsConfig(deps: SlackChannelDeps & { slack: SlackCredentials }): FactoryChannelsConfig {\n // Per HTTP attempt, not per call: the WebClient's own retry policy still\n // waits out a 429. Without it a dead socket hangs a post forever.\n const adapter = createSlackAdapter({ ...deps.slack, webClientOptions: { timeout: SLACK_REQUEST_TIMEOUT_MS } });\n const slack =\n deps.adapterOptions?.streaming === false\n ? { adapter, ...deps.adapterOptions }\n : { adapter, ...deps.adapterOptions, streaming: deps.adapterOptions?.streaming ?? true };\n\n const startedSessions: SlackStartedSessions = new WeakMap();\n return {\n adapters: { slack },\n handlers: createHandlers(deps),\n // New linked+repo-backed threads belong to the Factory and are named after\n // the Factory session they start, which makes the controller session repo-backed.\n resolveResourceId: createChannelResourceIdResolver(deps, startedSessions),\n resolveThreadId: createChannelThreadIdResolver(startedSessions),\n resolveSession: createChannelSessionResolver(deps),\n // Those sessions are created by the channel machinery, not the web kickoff,\n // so this is where they pick up the factory's configuration.\n onSessionStart: createChannelSessionStartHook(deps),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slack.js","names":[],"sources":["../../../src/integrations/slack/slack.ts"],"sourcesContent":["import { migratePersistedModelSelection } from '@mastra/core/agent-controller';\nimport { ChannelSessionRejectedError } from '@mastra/core/channels';\nimport type {\n ChannelHandler,\n ChannelHandlerContext,\n ChannelHandlers,\n ChannelSessionResolve,\n ChannelSessionStart,\n ResolveResourceId,\n ResolveThreadId,\n} from '@mastra/core/channels';\nimport type { Mastra } from '@mastra/core/mastra';\nimport { createSlackAdapter } from '@mastra/slack';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\nimport { Card, CardText, Actions, LinkButton } from 'chat';\n\nimport { FACTORY_INTERACTIVE_KEY } from '../../auth.js';\nimport {\n primeTenantCredentialsForRequestContext,\n primeTenantCredentialsForTenant,\n} from '../../routes/tenant-credentials.js';\nimport {\n createSourceControlSessionLookup,\n FactorySourceControlConflictError,\n hydrateFactorySession,\n resolveFactorySessionModel,\n resolveFactorySourceControl,\n resolvePrimaryEnvironmentRepository,\n} from '../../session/factory-session.js';\nimport { readRequestContextOrgId, seedSessionOrg } from '../../session/org-seed.js';\nimport { isRunBound, memoryResourceOf, sessionStampCounts } from '../../session/session-threads.js';\nimport type { SessionRunBindings } from '../../session/session-threads.js';\nimport type {\n ChannelAccountLink,\n ChannelAccountLinkKey,\n ChannelIdentityStorage,\n} from '../../storage/domains/channel-identity/base.js';\nimport type { FactoryActorExternalIdentity } from '../../storage/domains/comments/actor.js';\nimport { actorFromChannelAuthor } from '../../storage/domains/comments/actor.js';\nimport type { CommentsDomain } from '../../storage/domains/comments/domain.js';\nimport type { ModelDefaultsStorage } from '../../storage/domains/model-defaults/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport type { ExternalWorkItemSource, WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport { FACTORY_SLACK_SENDER_KEY } from '../../tools/scope.js';\nimport type { FactoryChannelsConfig } from '../base.js';\nimport { prepareSessionRunContext } from '../subscription-session.js';\nimport { markNewChannelSession } from './channel-session-link-processor.js';\n\nimport { resolveEmojiShortcodes } from './emoji.js';\nimport { slackCommentSource } from './feed-publisher.js';\nimport { slackRunErrorCard } from './run-error-card.js';\n\n// Derive the thread/message types from the core handler signature rather than\n// importing them from `chat` directly: mc-web can resolve a different `chat`\n// version than @mastra/core, and the two `Thread`/`Message` declarations are\n// structurally incompatible (private fields). Using the handler's own types\n// keeps everything on one version.\ntype HandlerThread = Parameters<ChannelHandler>[0];\ntype HandlerMessage = Parameters<ChannelHandler>[1];\n\nconst SLACK_REQUEST_TIMEOUT_MS = 15_000;\n\nclass SlackSessionStartError extends Error {}\n\n/** Dependencies the Slack channel handlers close over, injected from the web entry. */\ninterface SlackChannelDeps {\n /**\n * The factory's reverse-index store mapping a Slack sender to a Mastra\n * tenant. When provided, inbound messages from an unlinked sender are not\n * dispatched — the run only proceeds (with the sender's tenant stamped on\n * the request context) once they've linked their account. Unlinked senders\n * get an ephemeral \"connect your account\" card instead.\n */\n accountLinks?: ChannelIdentityStorage;\n /**\n * Factory projects domain. When provided (alongside `accountLinks`), a\n * linked sender's run must also resolve to a Factory project before it\n * dispatches: their link's default factory, else their tenant's only\n * factory (stamped back onto the link), else a \"pick a default factory\"\n * card posted in the thread and no run. Unset → no factory routing (runs\n * dispatch as before).\n */\n projects?: FactoryProjectsStorage;\n /**\n * Storage handle of the integration that owns source control\n * (`IntegrationContext.storage.sourceControlOwner`). Used to make new Slack\n * threads repo-backed: when the sender is linked and their factory has a\n * repository, the thread's resourceId becomes a Factory user-session id (repo\n * cloned on a `slack/{threadTs}` branch) instead of the chat-only\n * `channel:...` id. It also lets a started session read back the project it\n * belongs to. Nothing here is provider-specific — the connection is matched\n * by the handle's own `integrationId`. Absent (no source-control integration\n * registered) → chat-only sessions as before.\n */\n sourceControl?: SourceControlStorageHandle;\n /** Registered source-control partitions used to select the provider linked to each Factory project. */\n sourceControls?: readonly SourceControlStorageHandle[];\n /**\n * Model-defaults domain. When provided, a new repo-backed session starts on\n * the linked sender's default model before the factory's shared default. Read\n * once, when the thread's build model is first chosen; the choice is persisted\n * on the thread and never re-resolved. Unset → the factory default, as before.\n */\n modelDefaults?: ModelDefaultsStorage;\n /**\n * Factory work-items domain, used to find the card a Slack thread already\n * has so an `aside` can land on it. New threads never create cards.\n */\n workItems?: WorkItemsStorage;\n /**\n * Work-item feed. With it (and `workItems`), an `aside` lands as a comment on\n * the card the thread already has. Unset → asides stay ignored, as before.\n */\n feed?: Pick<CommentsDomain, 'createComment'>;\n /** Overrides applied to the Slack channel adapter entry. */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nfunction configuredSourceControls(deps: SlackChannelDeps): readonly SourceControlStorageHandle[] {\n return deps.sourceControls ?? (deps.sourceControl ? [deps.sourceControl] : []);\n}\n\n/**\n * Read the Slack team id off a raw platform payload (Events API envelope or\n * slash-command body — both carry `team_id`), duck-typed to build the\n * workspace-scoped account-link key.\n */\nfunction rawTeamId(rawPayload: unknown): string | undefined {\n if (!rawPayload || typeof rawPayload !== 'object') return undefined;\n const raw = rawPayload as { team_id?: unknown; team?: unknown };\n if (typeof raw.team_id === 'string' && raw.team_id) return raw.team_id;\n if (typeof raw.team === 'string' && raw.team) return raw.team;\n if (raw.team && typeof raw.team === 'object') {\n const id = (raw.team as { id?: unknown }).id;\n if (typeof id === 'string' && id) return id;\n }\n return undefined;\n}\n\n/**\n * The Slack team id survives onto a normalized chat Message only on\n * `message.raw` (the Slack Events API envelope).\n */\nfunction slackTeamId(message: HandlerMessage): string | undefined {\n return rawTeamId(message.raw);\n}\n\n/**\n * Resolve the web-UI origin for links humans open in a browser (Connect card,\n * session deep links). Prefers `MASTRACODE_PUBLIC_URL` — the origin auth\n * cookies and OAuth redirect allow-lists are registered against — over the\n * channels tunnel, which only Slack's servers need to reach.\n */\nfunction webPublicUrl(): string | undefined {\n return process.env.MASTRACODE_PUBLIC_URL ?? process.env.MASTRACODE_CHANNELS_PUBLIC_URL;\n}\n\n/** Outcome of the sender-link gate for one inbound message. */\ntype LinkedSenderResult =\n /** Gating not configured — dispatch as before account linking existed. */\n | { status: 'ungated' }\n /** Sender unlinked — Connect card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** Sender linked — their tenant plus the sender key the link lives under. */\n | { status: 'linked'; link: ChannelAccountLink; key: ChannelAccountLinkKey };\n\n/**\n * Resolve the sender's account link, posting an ephemeral \"connect your\n * account\" card (visible only to the sender) linking into the web UI's\n * Slack-connect flow when they're unlinked.\n */\nexport async function resolveLinkedSender({\n thread,\n message,\n accountLinks,\n}: {\n thread: HandlerThread;\n message: HandlerMessage;\n accountLinks?: ChannelIdentityStorage;\n}): Promise<LinkedSenderResult> {\n if (!accountLinks) return { status: 'ungated' };\n const platform = thread.adapter.name;\n const externalUserId = message.author.userId;\n const externalTeamId = slackTeamId(message);\n // Without a team id we can't identify the workspace-scoped link; treat as\n // unlinked so a run never proceeds tenant-less.\n const key = externalTeamId ? { platform, externalTeamId, externalUserId } : undefined;\n const link = key ? await accountLinks.getAccountLink(key) : null;\n if (link && key) return { status: 'linked', link, key };\n\n const publicUrl = webPublicUrl();\n // A public origin is all the card needs. The link carries no identity: the\n // web app authenticates the visitor, then Slack's OIDC flow proves which\n // Slack account they control. Without an origin, still block, just no card.\n if (publicUrl) {\n await thread.postEphemeral(message.author, buildConnectCard(publicUrl, thread), { fallbackToDM: true });\n }\n return { status: 'blocked' };\n}\n\nconst retryHint = (thread: HandlerThread) => (thread.isDM ? 'message me again' : 'mention me again');\n\n/**\n * The \"connect your account\" card. The link is deliberately identity-free —\n * `/connect/slack` sends the visitor to Connections, where \"Connect Slack\"\n * runs the OIDC flow and Slack itself asserts the (team, user) pair.\n */\nfunction buildConnectCard(publicUrl: string, thread: HandlerThread) {\n return Card({\n title: 'Connect your account',\n children: [\n CardText(`Connect your account to use this agent, then ${retryHint(thread)}.`),\n Actions([\n LinkButton({\n url: `${publicUrl}/connect/slack`,\n label: 'Connect account',\n }),\n ]),\n ],\n });\n}\n\n/** Outcome of factory routing for one linked sender's inbound message. */\ntype FactoryRouteResult =\n /** Factory routing not configured — dispatch without a factory. */\n | { status: 'ungated' }\n /** No factory resolved — prompt card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** The Factory project this sender's runs route to. */\n | { status: 'resolved'; factoryProjectId: string };\n\n/**\n * Decide which Factory project a linked sender's run belongs to:\n *\n * 1. The link's `defaultFactoryProjectId`, when it still exists (a stale id —\n * deleted factory — falls through as if unset).\n * 2. Else, the tenant's only factory, stamped back onto the link so it shows\n * up (and stays editable) in Connected Accounts settings.\n * 3. Else — zero or several factories — a \"pick a default factory\" card\n * posted publicly in the thread so Slack notifies the sender, and the run is\n * blocked.\n */\nexport async function resolveFactoryForLink({\n thread,\n link,\n key,\n accountLinks,\n projects,\n}: {\n thread: HandlerThread;\n link: ChannelAccountLink;\n key: ChannelAccountLinkKey;\n accountLinks: ChannelIdentityStorage;\n projects?: FactoryProjectsStorage;\n}): Promise<FactoryRouteResult> {\n if (!projects) return { status: 'ungated' };\n // Factories are org-scoped; a personal account (no org) has none and lands\n // on the prompt below.\n const orgId = link.orgId ?? '';\n\n if (link.defaultFactoryProjectId) {\n const existing = await projects.get({ orgId, id: link.defaultFactoryProjectId });\n if (existing) {\n return { status: 'resolved', factoryProjectId: existing.id };\n }\n }\n\n const factories = orgId ? await projects.list({ orgId }) : [];\n if (factories.length === 1) {\n const only = factories[0]!;\n await accountLinks.setDefaultFactory({ ...key, userId: link.userId, factoryProjectId: only.id });\n return { status: 'resolved', factoryProjectId: only.id };\n }\n\n const publicUrl = webPublicUrl();\n if (publicUrl) {\n await thread.post(\n Card({\n title: 'Pick a default factory',\n children: [\n CardText(\n factories.length === 0\n ? `Your account has no factory yet. Create one in the web app, then ${retryHint(thread)}.`\n : `Your account has several factories. Pick which one Slack sessions should go to, then ${retryHint(thread)}.`,\n ),\n Actions([\n LinkButton({\n url: `${publicUrl}/settings/connections`,\n label: 'Open settings',\n }),\n ]),\n ],\n }),\n );\n }\n return { status: 'blocked' };\n}\n\n/**\n * Deterministic per-thread branch name: `slack/{threadTs}` with characters\n * outside the sandbox git-ref allow-list (`[A-Za-z0-9_./-]`, and `.` for\n * readability) mapped to `-`. `thread.id` is `{channelId}:{threadTs}`\n * (platform-prefixed on handler threads) — the trailing segment is the ts.\n *\n * Top-level DM and channel conversations use the empty-threadTs thread form,\n * so the trailing segment can be empty; a bare `slack/` is not a valid git\n * ref. Fall back to the last non-empty segment (the channel id): one\n * deterministic branch per top-level conversation.\n */\nfunction threadBranch(threadId: string): string {\n const segments = threadId.split(':');\n const tail = segments.findLast(segment => segment.length > 0) ?? threadId;\n return `slack/${tail.replace(/[^A-Za-z0-9_/-]/g, '-')}`;\n}\n\n/**\n * Session ids of repo-backed Slack threads, keyed by the message that started\n * them. The resourceId resolver records the session it found or created; the\n * thread-id resolver, which core runs next for the same message, names the new\n * thread after it.\n */\nexport type SlackStartedSessions = WeakMap<object, string>;\n\n/**\n * Resolve the resourceId for a NEW Slack channel thread. A linked sender whose\n * factory has a repository gets a Factory session. A channel thread belongs to\n * the Factory: its resourceId is the Factory id, so the thread shares the\n * Factory's memory. A DM is private to the sender, so it runs under the\n * sender's user id and shares memory with their own sessions. The session id is recorded in `startedSessions` for the\n * thread-id resolver. The controller\n * session then materializes the repo sandbox via the factory's dynamic\n * workspace (clone + PAT), the session shows up in the web Sessions list, and\n * View Session deep-links land on the normal workspace route.\n *\n * A gated deployment refuses a thread it cannot place, rather than falling back\n * to a chat-only session: a repo-backed thread is the whole point of the\n * integration, and answering in a chat-only one would run the sender's request\n * in no project, on the SDK's built-in defaults. A linked sender whose project\n * cannot start a session gets an actionable error in Slack; the dispatch gate\n * handles unlinked or unrouted senders before this hook runs.\n * The hook only runs when a NEW thread is actually created (`getOrCreateThread`\n * resolves the owner lazily), so an established conversation is never touched:\n * it keeps its session, its model, and its history.\n *\n * The chat-only `channel:...` id survives for two cases where refusing would be\n * wrong: a deployment that cannot produce a repo-backed thread at all (no\n * account linking, no projects, or no source-control integration registered),\n * and a message carrying no sender id. In both, a `channel:` thread is the only\n * shape a session can take.\n *\n * Pure lookups only — cards for unlinked/unrouted senders are the dispatch\n * gate's job; this hook must never post.\n */\nexport function createChannelResourceIdResolver(\n deps: SlackChannelDeps,\n startedSessions: SlackStartedSessions = new WeakMap(),\n): ResolveResourceId {\n const { accountLinks, projects } = deps;\n const sourceControls = configuredSourceControls(deps);\n return async ({ platform, thread, message }) => {\n // NOT the hook's `defaultResourceId`: configuring a custom resolver\n // bypasses AgentControllerChannels' own `channel:{thread.id}` derivation\n // (agent-controller-channels.ts `resolveChannelResourceId`), and the base\n // default is the per-USER memory key. Chat-only fallbacks must stay\n // per-thread, so reproduce the controller default here.\n const chatOnlyResourceId = `channel:${thread.id}`;\n // A deployment with no account linking, no projects, or no source-control\n // integration cannot produce a repo-backed thread at all, so a `channel:`\n // thread is the only shape it has. Every refusal below is a different thing\n // entirely: a sender the bot cannot place, in a deployment that can.\n if (!accountLinks || !projects || sourceControls.length === 0) return chatOnlyResourceId;\n // No sender id at all — a malformed message, not a sender the host turned\n // away. There is nothing to place, so give it the per-thread memory key.\n if (!message.author.userId) return chatOnlyResourceId;\n\n const externalTeamId = rawTeamId(message.raw);\n if (!externalTeamId) {\n throw new ChannelSessionRejectedError('No Slack workspace id on the message — the sender cannot be identified');\n }\n try {\n const link = await accountLinks.getAccountLink({\n platform,\n externalTeamId,\n externalUserId: message.author.userId,\n });\n if (!link) {\n throw new ChannelSessionRejectedError(\n `Slack sender ${message.author.userId} is not linked to a Factory account`,\n );\n }\n\n // Same chain as `resolveFactoryForLink`, minus prompts/stamping: the\n // dispatch gate has already run (and stamped a lone factory) by the\n // time a new thread is created, so this is a read-only re-resolve.\n const orgId = link.orgId ?? '';\n let factoryProjectId: string | undefined;\n if (link.defaultFactoryProjectId && (await projects.get({ orgId, id: link.defaultFactoryProjectId }))) {\n factoryProjectId = link.defaultFactoryProjectId;\n } else if (orgId) {\n const factories = await projects.list({ orgId });\n if (factories.length === 1) factoryProjectId = factories[0]!.id;\n }\n if (!factoryProjectId) {\n throw new ChannelSessionRejectedError(`Linked Slack sender ${message.author.userId} has no Factory project`);\n }\n\n // Linked and routed, so a repo-backed session is not a preference: with no\n // repository to work in there is no thread to start, and starting one\n // anyway would answer in a project the sender never picked, on the SDK's\n // built-in defaults.\n const sourceControl = await resolveFactorySourceControl({ sourceControls, orgId, factoryProjectId });\n if (!sourceControl) {\n const connections = await Promise.all(\n sourceControls.map(sourceControl => sourceControl.connections.list({ orgId, factoryProjectId })),\n );\n throw new SlackSessionStartError(\n connections.some(rows => rows.length > 0)\n ? 'Could not start a session: link a repository to this Factory project.'\n : 'Could not start a session: connect source control to this Factory project.',\n );\n }\n // The session's link is the factory's position-1 environment\n // repository (D1); the sandbox boots every environment repository.\n const repo = await resolvePrimaryEnvironmentRepository({ sourceControl, orgId, factoryProjectId });\n if (!repo.found) {\n throw new SlackSessionStartError(\n repo.reason === 'connection'\n ? 'Could not start a session: connect source control to this Factory project.'\n : 'Could not start a session: link a repository to this Factory project.',\n );\n }\n\n const branch = threadBranch(thread.id);\n const memoryResourceId = thread.isDM ? link.userId : factoryProjectId;\n // Attributed to the Slack sender, not to whoever connected the repository:\n // unlike an autonomous rule run, a Slack thread has a real interactive user.\n // Keyed by factory, not by link: the thread's session stays found when the\n // position-1 repository changes between two messages.\n const existing = await sourceControl.sessions.getForBranch({\n factoryProjectId,\n userId: link.userId,\n branch,\n });\n if (existing) {\n startedSessions.set(message, existing.sessionId);\n return memoryResourceId;\n }\n const session = await sourceControl.sessions.create({\n sessionId: globalThis.crypto.randomUUID(),\n projectRepositoryId: repo.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId: link.userId,\n branch,\n baseBranch: repo.baseBranch,\n // DMs are the only private origin; channel threads are org-visible.\n visibility: thread.isDM ? 'private' : 'org',\n });\n startedSessions.set(message, session.sessionId);\n return memoryResourceId;\n } catch (error) {\n if (error instanceof ChannelSessionRejectedError || error instanceof SlackSessionStartError) throw error;\n if (error instanceof FactorySourceControlConflictError) {\n throw new SlackSessionStartError(\n 'Could not start a session: this Factory project has repositories from more than one source-control provider.',\n { cause: error },\n );\n }\n console.error('[slack] failed to start repo-backed session for thread', thread.id, error);\n throw new SlackSessionStartError('Could not start a session right now. Please try again later.', {\n cause: error,\n });\n }\n };\n}\n\n/**\n * Thread id for a NEW Slack channel thread. Repo-backed threads take the\n * session id AS their thread id, matching the web convention\n * (FactoryStartCoordinator seeds threads with threadId = sessionId) so\n * `/workspaces/{sessionId}/threads/{sessionId}` resolves Slack-created\n * sessions exactly like web-created ones — no `?sessionId=` override needed.\n * Chat-only threads keep the default random id: their `channel:...`\n * resourceId is a memory key, not a unique thread id.\n */\nexport function createChannelThreadIdResolver(startedSessions: SlackStartedSessions): ResolveThreadId {\n return ({ message, defaultThreadId }) => startedSessions.get(message) ?? defaultThreadId;\n}\n\n/**\n * The Factory session a repo-backed Slack thread belongs to. A thread under\n * the Factory resource is stamped with its session id (the startup migration\n * stamps threads it moved there). An unmigrated thread runs under its session\n * id as its resource, and a newer thread is named after its session. A stamp\n * or thread id only counts under that session's memory resource; a thread\n * named after a session it can't claim is refused. With no session row for\n * either, the thread is addressed by its own id and fails authorization like\n * any session without a row.\n */\nconst UNCLAIMED_SESSION = Symbol('unclaimed-session');\n\nasync function slackThreadSessionId(\n sessions: ReturnType<typeof createSourceControlSessionLookup>,\n thread: { id: string; resourceId: string; metadata?: Record<string, unknown> },\n loadThread: (threadId: string) => Promise<{ resourceId: string } | null | undefined>,\n runs: SessionRunBindings | undefined,\n): Promise<string | null | typeof UNCLAIMED_SESSION> {\n if (thread.resourceId.startsWith('channel:')) return null;\n // Stamps and thread ids are client-writable: a session only claims a thread\n // under its own memory resource.\n const claims = async (sessionId: string) => {\n const row = await sessions.getBySessionId(sessionId);\n if (!row) return false;\n // A stamp only counts where the session's stamps are trusted.\n if (sessionId !== thread.id && !sessionStampCounts(row, thread.resourceId)) return false;\n const own = sessionId === thread.id ? thread : await loadThread(sessionId);\n return thread.resourceId === memoryResourceOf(row, own, await isRunBound(row, runs));\n };\n const stamped = thread.metadata?.factorySessionId;\n if (typeof stamped === 'string' && stamped && (await claims(stamped))) return stamped;\n if (await sessions.getSourceControlBySessionId(thread.resourceId)) return thread.resourceId;\n if (await claims(thread.id)) return thread.id;\n if (await sessions.getBySessionId(thread.id)) return UNCLAIMED_SESSION;\n // With no session row the thread is its own session: falling back to the\n // resource would put every such thread under the Factory (or a sender) into\n // one shared session.\n return thread.id;\n}\n\n/** Create channel sessions with their Factory ownership present in initial controller state. */\nexport function createChannelSessionResolver(deps: SlackChannelDeps): ChannelSessionResolve {\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ controller, thread, requestContext }) => {\n // A session-bound Slack thread is addressed by its Factory session id and\n // runs under the thread's resource (the Factory). A chat-only thread is its\n // own session, addressed by its `channel:` resource.\n // Approval clicks pass the thread without its metadata; load the stored\n // thread whenever the session stamp is missing so a migrated thread's\n // stamp is read.\n const stamped = (thread as { metadata?: Record<string, unknown> }).metadata?.factorySessionId;\n const stored =\n stamped || thread.resourceId.startsWith('channel:')\n ? thread\n : ((await controller.queryThreadById({ threadId: thread.id })) ?? thread);\n const resolved = await slackThreadSessionId(\n sourceControlSessions,\n stored,\n threadId => controller.queryThreadById({ threadId }),\n deps.workItems,\n );\n if (resolved === UNCLAIMED_SESSION) {\n throw new Error(`Slack thread ${thread.id} is named after a Factory session it doesn't belong to.`);\n }\n const sessionId = resolved ?? thread.resourceId;\n const sourceControl = sessionId.startsWith('channel:')\n ? null\n : await sourceControlSessions.getSourceControlBySessionId(sessionId);\n const session = sourceControl ? await sourceControl.sessions.getBySessionId(sessionId) : null;\n return controller.createSession({\n id: sessionId,\n ownerId: controller.id,\n resourceId: thread.resourceId,\n // Bind the Slack-created thread itself. Without it the session\n // auto-selects (or creates) another thread and is switched onto this one later.\n threadId: thread.id,\n requestContext,\n ...(session?.factoryProjectId ? { tags: { factoryProjectId: session.factoryProjectId } } : {}),\n });\n };\n}\n\n/**\n * Apply the factory's configuration to a Slack-created session the first time\n * its thread reaches the controller.\n *\n * Without this a Slack session runs on the SDK's built-in mode default\n * (`openai/gpt-5.5`), so a factory configured for any other provider fails every\n * message with a missing-credentials error. The web kickoff has always applied\n * the factory default; this brings Slack to the same footing.\n *\n * Only repo-backed threads are configured. Their resourceId IS the Factory\n * session id, which the source-control rows turn back into a project — a\n * chat-only `channel:...` id names no project, so there is nothing to read.\n *\n * The model this session starts on is picked here, once, from the project's\n * effective model scope for a session: the Factory default or the durable session owner's\n * default, else the SDK's built-in model. The choice is persisted on the thread\n * as `currentModelId`, so it outlives the process that made it.\n *\n * The model resolution is skipped on a session that already has a model\n * persisted on the thread. That is the durable record of a deliberate choice —\n * either an earlier start or a user's own switch — and re-applying a preference\n * over it would undo the user's selection every time the process restarts.\n */\nexport function createChannelSessionStartHook(deps: SlackChannelDeps): ChannelSessionStart {\n const { projects, modelDefaults } = deps;\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ session, thread, requestContext }) => {\n // Seed the tenant org above every guard below. `gateDispatch` stamps it on\n // the message's request context before the session exists, so this needs no\n // storage read — which matters, because the model lookups below deliberately\n // skip storage on a restarted session. A channel-only thread and a thread\n // whose dispatch was ungated both land here with no org and are marked\n // unresolved rather than being left to look like a local session.\n await seedSessionOrg(session, readRequestContextOrgId(requestContext));\n\n if (!projects) return;\n if (thread.resourceId.startsWith('channel:')) return;\n\n const sessionId = session.identity.getId();\n const sourceControl = await sourceControlSessions.getSourceControlBySessionId(sessionId);\n if (!sourceControl) return;\n const sourceSession = await sourceControl.sessions.getBySessionId(sessionId);\n if (!sourceSession?.factoryProjectId) return;\n const threadId = session.thread.getId();\n const binding =\n threadId && deps.workItems\n ? await deps.workItems.findRunBindingByThread({\n orgId: sourceSession.orgId,\n resourceId: session.identity.getResourceId(),\n sessionId,\n threadId,\n rejectAmbiguous: true,\n })\n : null;\n const owner = {\n factoryProjectId: binding?.factoryProjectId ?? sourceSession.factoryProjectId,\n orgId: sourceSession.orgId,\n userId: sourceSession.userId,\n };\n\n const sessionModel = await resolveFactorySessionModel({\n projects,\n modelDefaults,\n factoryProjectId: owner.factoryProjectId,\n orgId: owner.orgId,\n ownerUserId: owner.userId,\n context: binding ? 'run' : 'session',\n });\n\n // Repo-backed Slack sessions use the source-control row's durable owner,\n // never the sender, for both model and credential scope.\n await session.state.set({\n factoryProjectId: owner.factoryProjectId,\n factoryModelScope: sessionModel.modelScope,\n factoryOwnerUserId: owner.userId,\n });\n // Route the org through the seed helper rather than stamping it directly:\n // an ungated dispatch marked this session unresolved above, and owner\n // recovery is the resolution that has to clear that marker with it.\n await seedSessionOrg(session, owner.orgId);\n await primeTenantCredentialsForTenant({\n orgId: owner.orgId,\n userId: owner.userId,\n credentialScope: sessionModel.modelScope,\n });\n\n const activeThreadId = session.thread.getId();\n // Threads of one Factory share its resource; the stamp ties this thread to its session.\n if (activeThreadId) {\n await session.thread.setSettingOn({ threadId: activeThreadId, key: 'factorySessionId', value: sessionId });\n }\n const modeId = session.mode.get();\n const persistedModelId = await migratePersistedModelSelection({\n getMetadata: async () =>\n activeThreadId\n ? (((await session.thread.getById({ threadId: activeThreadId }))?.metadata as\n Record<string, unknown> | undefined) ?? {})\n : {},\n modeId,\n set: (key: string, value: unknown) =>\n activeThreadId ? session.thread.setSettingOn({ threadId: activeThreadId, key, value }) : Promise.resolve(),\n threadId: activeThreadId ?? undefined,\n });\n if (persistedModelId) {\n // Subagent models live in session state only, so restore the ones this\n // thread pinned at its first start instead of re-resolving them.\n for (const agentType of SUBAGENT_TYPES) {\n const modelId = await session.thread.getSetting({ key: pinnedSubagentModelKey(agentType) });\n if (typeof modelId === 'string') await applySubagentModel(session, agentType, modelId);\n }\n } else {\n // `hydrateFactorySession` persists the scoped model, a connected provider's\n // provider default, or the SDK mode default as this thread's model of record.\n const selectedModelId =\n (await hydrateFactorySession(session, {\n orgId: owner.orgId,\n modelScope: sessionModel.modelScope,\n ownerUserId: owner.userId,\n defaultModelId: sessionModel.modelId,\n })) ?? session.model.get();\n\n // Pin subagent models on the thread like the main model so a restart\n // restores the selected scope's choice rather than re-resolving it.\n for (const agentType of SUBAGENT_TYPES) {\n if (!selectedModelId) continue;\n if (await applySubagentModel(session, agentType, selectedModelId)) {\n await session.thread.setSetting({ key: pinnedSubagentModelKey(agentType), value: selectedModelId });\n }\n }\n }\n };\n}\n\nconst SUBAGENT_TYPES = ['explore', 'plan', 'execute'] as const;\nconst pinnedSubagentModelKey = (agentType: string) => `slackSubagentModelId_${agentType}`;\n\n/** Best-effort: a subagent model that can't be applied must not fail the message. */\nasync function applySubagentModel(\n session: Parameters<ChannelSessionStart>[0]['session'],\n agentType: (typeof SUBAGENT_TYPES)[number],\n modelId: string,\n): Promise<boolean> {\n try {\n await session.subagents.model.set({ modelId, agentType });\n return true;\n } catch (error) {\n console.warn('[slack] Failed to apply the subagent model', {\n agentType,\n modelId,\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n}\n\n/**\n * The internal Mastra thread the framework created for a channel conversation.\n * The handler's `thread.id` is the platform thread id (e.g. `slack:C123:ts`),\n * NOT the internal UUID — the mapping lives in the stored thread's channel\n * metadata.\n */\nasync function findInternalThread(mastra: Mastra | undefined, thread: HandlerThread) {\n const store = await mastra?.getStorage()?.getStore('memory');\n const { threads } = (await store?.listThreads({\n filter: {\n metadata: {\n channel_platform: thread.adapter.name,\n channel_externalThreadId: thread.id,\n channel_externalChannelId: thread.channelId,\n },\n },\n perPage: 1,\n })) ?? { threads: [] };\n return threads[0] ?? null;\n}\n\ntype InternalThread = Awaited<ReturnType<typeof findInternalThread>>;\n\nasync function prepareExistingSessionOwnerContext(\n thread: HandlerThread,\n deps: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n options: {\n expectedOrgId?: string;\n senderUserId?: string;\n requireInternalThread: boolean;\n /** Already looked up by the caller; `null` means it doesn't exist. */\n internalThread?: InternalThread;\n },\n): Promise<'ready' | 'organization-mismatch' | 'access-denied'> {\n const sourceControls = configuredSourceControls(deps);\n if (sourceControls.length === 0 && !options.requireInternalThread) return 'ready';\n\n const internalThread =\n options.internalThread !== undefined ? options.internalThread : await findInternalThread(ctx.mastra, thread);\n if (!internalThread) {\n if (options.requireInternalThread) {\n throw new Error(`Could not resolve the internal Slack thread for ${thread.id}.`);\n }\n return 'ready';\n }\n if (sourceControls.length === 0 || internalThread.resourceId.startsWith('channel:')) return 'ready';\n const sessions = createSourceControlSessionLookup(sourceControls);\n const memory = await ctx.mastra?.getStorage()?.getStore('memory');\n const resolved = await slackThreadSessionId(\n sessions,\n internalThread,\n async threadId => memory?.getThreadById({ threadId }),\n deps.workItems,\n );\n if (resolved === UNCLAIMED_SESSION) return 'access-denied';\n const sessionId = resolved ?? internalThread.resourceId;\n const sessionRow = await sessions.getBySessionId(sessionId);\n if (\n sessionRow &&\n sessionRow.orgId === options.expectedOrgId &&\n sessionRow.visibility === 'private' &&\n sessionRow.userId !== options.senderUserId\n ) {\n return 'access-denied';\n }\n if (!options.expectedOrgId) {\n throw new Error(\n `Could not authorize the owner of Slack Factory session ${sessionId}: the sender's account link has no organization.`,\n );\n }\n\n const preparation = await prepareSessionRunContext(\n ctx.requestContext,\n sessionId,\n { sessions },\n { expectedOrgId: options.expectedOrgId },\n );\n if (preparation === 'organization-mismatch') return preparation;\n if (preparation !== 'prepared') {\n const reason =\n preparation === 'session-not-found'\n ? 'no source-control session row exists for it, or its project repository row is missing'\n : 'its source-control session row has no organization';\n throw new Error(`Could not authorize the owner of Slack Factory session ${sessionId}: ${reason}.`);\n }\n return 'ready';\n}\n\n/**\n * Build the \"new session\" handler for mention / direct-message events. A mention or\n * DM on a not-yet-subscribed thread starts a NEW session; once subscribed, later\n * events are follow-ups and don't re-announce.\n */\n/**\n * Run the account-link + factory-routing gates for one inbound message.\n * Returns `null` when the run must not dispatch (a prompt card was posted\n * where possible); otherwise the dispatch context — with `routed` present\n * only when a linked sender resolved to a factory.\n */\nasync function gateDispatch(\n thread: HandlerThread,\n message: HandlerMessage,\n deps: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n options: { requireInternalThread: boolean; internalThread?: InternalThread } = { requireInternalThread: false },\n): Promise<{\n routed?: { link: ChannelAccountLink; factoryProjectId: string };\n} | null> {\n const { accountLinks, projects } = deps;\n const sender = await resolveLinkedSender({ thread, message, accountLinks });\n if (sender.status === 'blocked') return null;\n // Linked senders must also route to a Factory project before a run starts.\n if (sender.status === 'linked' && accountLinks) {\n // Stamp the tenant on the run's request context — the single seam\n // `resolveCredentialStore` reads to load this sender's model credentials.\n // This belongs to the link, not to the routing: a linked sender whose\n // factory routing comes back `ungated` still exits below and dispatches, so\n // stamping only in the routed branch would silently run them on default\n // credentials.\n ctx.requestContext.set('user', { id: sender.link.userId, organizationId: sender.link.orgId });\n const ownerContext = await prepareExistingSessionOwnerContext(thread, deps, ctx, {\n expectedOrgId: sender.link.orgId,\n senderUserId: sender.link.userId,\n requireInternalThread: options.requireInternalThread,\n internalThread: options.internalThread,\n });\n if (ownerContext === 'access-denied') {\n await thread.post('This private Factory session belongs to another user.');\n return null;\n }\n if (ownerContext === 'organization-mismatch') {\n await thread.post('This thread belongs to a Factory session in another organization.');\n return null;\n }\n // Credential resolution reads an in-memory snapshot that only priming\n // fills, so prime whichever tenant now owns the run (the sender, or the\n // existing session's owner) — otherwise a cold snapshot after a restart\n // fails the run until that user happens to hit the web UI.\n await primeTenantCredentialsForRequestContext(ctx.requestContext);\n\n const route = await resolveFactoryForLink({ thread, ...sender, accountLinks, projects });\n if (route.status === 'blocked') return null;\n if (route.status === 'resolved') {\n // The model runs with the session owner's credentials, but Factory tools\n // must act as the authenticated Slack sender, not the session owner.\n if (sender.link.orgId) {\n ctx.requestContext.set(FACTORY_SLACK_SENDER_KEY, { userId: sender.link.userId, orgId: sender.link.orgId });\n ctx.requestContext.set(FACTORY_INTERACTIVE_KEY, true);\n }\n return { routed: { link: sender.link, factoryProjectId: route.factoryProjectId } };\n }\n }\n return {};\n}\n\n/**\n * A channel id and a `ts` name a thread only inside the workspace that issued\n * them, so the team scopes the card's key — without it two workspaces could\n * hold one key and an aside would land on another tenant's card.\n */\nexport function slackThreadSource(thread: HandlerThread, teamId?: string): ExternalWorkItemSource {\n return {\n integrationId: thread.adapter.name,\n type: 'slack-thread',\n ...(teamId ? { workspaceId: teamId } : {}),\n externalId: thread.id,\n };\n}\n\n/** Cards written before the key carried a team are still keyed bare. */\nasync function findThreadWorkItem(\n workItems: WorkItemsStorage,\n thread: HandlerThread,\n teamId?: string,\n): Promise<WorkItemRow | null> {\n const scoped = await workItems.getBySource(slackThreadSource(thread, teamId));\n if (scoped || !teamId) return scoped;\n return workItems.getBySource(slackThreadSource(thread));\n}\n\nfunction preDispatchErrorDetails(error: unknown): { message: string; cause?: { message: string } } {\n const readMessage = (value: unknown) => {\n const message =\n typeof value === 'string'\n ? value\n : value && typeof value === 'object' && 'message' in value\n ? value.message\n : undefined;\n return typeof message === 'string' && message.length > 0 ? message : undefined;\n };\n try {\n const message = readMessage(error) ?? 'Unknown error';\n const cause = error && typeof error === 'object' && 'cause' in error ? readMessage(error.cause) : undefined;\n return { message, ...(cause ? { cause: { message: cause } } : {}) };\n } catch {\n return { message: 'Error details unavailable' };\n }\n}\n\nasync function reportPreDispatchError(\n thread: HandlerThread,\n message: HandlerMessage,\n ctx: ChannelHandlerContext,\n error: unknown,\n): Promise<void> {\n try {\n if (error instanceof ChannelSessionRejectedError) return;\n } catch {\n // A thrown proxy can fail even the refusal check; still report the failure.\n }\n const correlation = {\n platform: thread.adapter.name,\n threadId: thread.id,\n messageId: message.id,\n authorId: message.author.userId,\n };\n const logger = typeof ctx.mastra?.getLogger === 'function' ? ctx.mastra.getLogger() : undefined;\n const logError = (line: string) => (logger ? logger.error(line) : console.error(line));\n logError(`[slack] Pre-dispatch failure ${JSON.stringify({ ...correlation, error: preDispatchErrorDetails(error) })}`);\n try {\n // The message id is the lookup key for the diagnostic above.\n await thread.post(`Couldn’t start processing your message. Please try again.\\n\\`messageId: ${message.id}\\``);\n } catch (deliveryError) {\n logError(\n `[slack] Failed to deliver pre-dispatch error reply ${JSON.stringify({ ...correlation, error: preDispatchErrorDetails(deliveryError) })}`,\n );\n }\n}\n\n/**\n * What the sender sees while nothing has been posted yet. Core's typing\n * wrapper only sets a status once the agent stream yields its first chunk,\n * and everything before that — credential priming, session rows, the\n * controller start hook, model time-to-first-token — is silent. Pin the same\n * status Factory's `typingStatus` uses for reasoning so the bot visibly\n * \"picks up\" as soon as the gate passes, and the stream's own status replaces\n * it without a visual change. Slack clears it on the first post; core clears\n * it when a run ends without posting.\n */\nconst EARLY_TYPING_STATUS = 'is thinking...';\n\nfunction acknowledgeReceipt(thread: HandlerThread, deps: SlackChannelDeps): void {\n if (deps.adapterOptions?.typingStatus === false) return;\n // Best-effort like every other status call: a failed status must not\n // keep the run from starting.\n void thread.startTyping(EARLY_TYPING_STATUS).catch(error => {\n console.warn('[slack] early typing status failed', { thread: thread.id, error });\n });\n}\n\nfunction createNewSessionChatHandler(deps: SlackChannelDeps): ChannelHandler {\n return async (thread, message, defaultHandler, ctx) => {\n // Gate on the sender having linked their Slack account to a Mastra tenant.\n // Unlinked → post the ephemeral Connect card and stop; no session/run is\n // created (which would otherwise be tenant-less and fail credential\n // resolution). This handler is the only gate — core dispatches whatever\n // reaches it — so every slot that can start a run must call it.\n try {\n if (!(await gateDispatch(thread, message, deps, ctx))) return;\n // A mention on a not-yet-subscribed thread is a NEW session. The default\n // handler auto-subscribes, so once subscribed this is a follow-up — the\n // thread already carries its link. Factory posts nothing of its own:\n // FactoryChannelSessionLinkProcessor emits the `Open session ↗` link right\n // after the agent's first reply.\n if (!(await thread.isSubscribed())) markNewChannelSession(ctx.requestContext);\n acknowledgeReceipt(thread, deps);\n } catch (error) {\n await reportPreDispatchError(thread, message, ctx, error);\n return;\n }\n await defaultHandler(thread, message);\n };\n}\n\n/**\n * An `aside` never reaches the agent, but it is still part of the conversation:\n * record it on the card the thread already has. The link is resolved here rather\n * than through `gateDispatch`, which posts a Connect card an aside must not\n * trigger. Best-effort — nothing here may surface in the Slack thread.\n */\nasync function ingestAside(\n thread: HandlerThread,\n message: HandlerMessage,\n { feed, workItems, accountLinks }: SlackChannelDeps,\n): Promise<void> {\n if (!feed || !workItems) return;\n const body = resolveEmojiShortcodes(message.text.replace(/^aside\\b[,:]?\\s*/i, '').trim());\n if (!body) return;\n try {\n const teamId = slackTeamId(message);\n const workItem = await findThreadWorkItem(workItems, thread, teamId);\n if (!workItem) return;\n\n const external: FactoryActorExternalIdentity = {\n platform: thread.adapter.name,\n ...(teamId ? { teamId } : {}),\n messageId: message.id,\n userId: message.author.userId,\n userName: message.author.userName,\n fullName: message.author.fullName,\n isBot: message.author.isBot,\n };\n const link =\n accountLinks && teamId\n ? await accountLinks.getAccountLink({\n platform: external.platform,\n externalTeamId: teamId,\n externalUserId: external.userId,\n })\n : null;\n\n const result = await feed.createComment({\n orgId: workItem.orgId,\n workItemId: workItem.id,\n author: actorFromChannelAuthor(external, link),\n body,\n occurredAt: message.metadata.dateSent,\n externalSource: slackCommentSource(thread.id, message.id, teamId),\n });\n if (result.status !== 'created') {\n console.warn('[slack] aside not ingested', { thread: thread.id, messageTs: message.id, status: result.status });\n }\n } catch (error) {\n // Slack was acked before this handler ran, so a lost aside is never redelivered.\n console.warn('[slack] aside ingest failed', { thread: thread.id, messageTs: message.id, error });\n }\n}\n\nexport const createHandlers = (deps: SlackChannelDeps): ChannelHandlers => {\n const newSessionChatHandler = createNewSessionChatHandler(deps);\n\n return {\n onSubscribedMessage: async (thread, message, defaultHandler, ctx) => {\n // `aside` as its own leading word lets humans talk in a subscribed\n // thread without the bot replying. Word boundary so messages that\n // merely start with \"aside...\" (e.g. \"asides can wait\") still route.\n if (/^aside\\b/i.test(message.text)) return ingestAside(thread, message, deps);\n // Looked up once here for the mute check and the access gate below.\n const internalThread = await findInternalThread(ctx.mastra, thread);\n // A muted thread ignores everything that doesn't tag the bot, and the\n // skipped messages never reach the session.\n if (!message.isMention && internalThread?.metadata?.[MUTED_KEY] === 'true') return;\n // A subscribed follow-up from an unlinked sender must not run either\n // (e.g. the link was removed mid-conversation), and it must still\n // resolve a factory (e.g. the default was cleared or its factory\n // deleted mid-conversation). Mute and unmute pass the same gate, so only\n // someone allowed to prompt this session can change what it listens to.\n try {\n const gate = await gateDispatch(thread, message, deps, ctx, { requireInternalThread: true, internalThread });\n if (!gate) return;\n } catch (error) {\n await reportPreDispatchError(thread, message, ctx, error);\n return;\n }\n const command = message.isMention ? muteCommand(message) : undefined;\n if (command && internalThread) return setThreadMuted(thread, internalThread, message, ctx, command === 'mute');\n acknowledgeReceipt(thread, deps);\n await defaultHandler(thread, message);\n },\n onMention: async (thread, message, defaultHandler, ctx) => {\n // `@bot mute` in a thread the bot never joined has nothing to mute, and\n // must not start a session about the word \"mute\".\n if (muteCommand(message)) return;\n await newSessionChatHandler(thread, message, defaultHandler, ctx);\n },\n onDirectMessage: newSessionChatHandler,\n };\n};\n\n/** Thread metadata key; one Factory runs one Slack app, so this is per bot. */\nconst MUTED_KEY = 'factory_muted';\n\n/**\n * `mute` or `unmute` when that word, with the mentions taken out, is the whole\n * message. Reads Slack's raw text, where a mention is still `<@U…>`: the\n * normalized `message.text` spells it as the bot's display name, which can\n * hold spaces and punctuation (`@Mastra Factory(Caleb)`).\n */\nfunction muteCommand(message: HandlerMessage): 'mute' | 'unmute' | undefined {\n const raw = message.raw as { text?: unknown } | undefined;\n const text = typeof raw?.text === 'string' ? raw.text : message.text;\n const rest = text\n .replace(/<@[^>]+>/g, ' ')\n .replace(/(?<!\\w)@[\\w.-]+/g, ' ')\n .trim()\n .replace(/[.!]+$/, '')\n .toLowerCase();\n return rest === 'mute' || rest === 'unmute' ? rest : undefined;\n}\n\n/**\n * Stored on the Mastra thread next to `channel_subscribed`, so a mute holds\n * across deploys and replicas. Callers run the sender's link and session\n * access gate first. A reaction confirms it without posting.\n */\nasync function setThreadMuted(\n thread: HandlerThread,\n internal: NonNullable<InternalThread>,\n message: HandlerMessage,\n ctx: ChannelHandlerContext,\n muted: boolean,\n): Promise<void> {\n const store = await ctx.mastra?.getStorage()?.getStore('memory');\n if (!store) return;\n await store.patchThread({ id: internal.id, metadata: { ...internal.metadata, [MUTED_KEY]: String(muted) } });\n try {\n await thread.adapter.addReaction(thread.id, message.id, muted ? 'mute' : 'loud_sound');\n } catch (error) {\n console.warn('[slack] mute reaction failed', { thread: thread.id, error });\n }\n}\n\n/** Slack app credentials, passed in explicitly rather than read from env here. */\ninterface SlackCredentials {\n clientId?: string;\n clientSecret?: string;\n signingSecret: string;\n botToken?: string;\n}\n\nexport function createSlackChannelsConfig(deps: SlackChannelDeps & { slack: SlackCredentials }): FactoryChannelsConfig {\n // Per HTTP attempt, not per call: the WebClient's own retry policy still\n // waits out a 429. Without it a dead socket hangs a post forever.\n const adapter = createSlackAdapter({ ...deps.slack, webClientOptions: { timeout: SLACK_REQUEST_TIMEOUT_MS } });\n const formatError: SlackAdapterChannelConfig['formatError'] = deps.adapterOptions?.formatError ?? slackRunErrorCard;\n const slack =\n deps.adapterOptions?.streaming === false\n ? { adapter, ...deps.adapterOptions, formatError }\n : { adapter, ...deps.adapterOptions, formatError, streaming: deps.adapterOptions?.streaming ?? true };\n\n const startedSessions: SlackStartedSessions = new WeakMap();\n return {\n adapters: { slack },\n handlers: createHandlers(deps),\n // New linked+repo-backed threads belong to the Factory and are named after\n // the Factory session they start, which makes the controller session repo-backed.\n resolveResourceId: createChannelResourceIdResolver(deps, startedSessions),\n resolveThreadId: createChannelThreadIdResolver(startedSessions),\n resolveSession: createChannelSessionResolver(deps),\n // Those sessions are created by the channel machinery, not the web kickoff,\n // so this is where they pick up the factory's configuration.\n onSessionStart: createChannelSessionStartHook(deps),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA6DA,MAAM,2BAA2B;AAEjC,IAAM,yBAAN,cAAqC,MAAM,CAAC;AAwD5C,SAAS,yBAAyB,MAA+D;CAC/F,OAAO,KAAK,mBAAmB,KAAK,gBAAgB,CAAC,KAAK,aAAa,IAAI,CAAC;AAC9E;;;;;;AAOA,SAAS,UAAU,YAAyC;CAC1D,IAAI,CAAC,cAAc,OAAO,eAAe,UAAU,OAAO,KAAA;CAC1D,MAAM,MAAM;CACZ,IAAI,OAAO,IAAI,YAAY,YAAY,IAAI,SAAS,OAAO,IAAI;CAC/D,IAAI,OAAO,IAAI,SAAS,YAAY,IAAI,MAAM,OAAO,IAAI;CACzD,IAAI,IAAI,QAAQ,OAAO,IAAI,SAAS,UAAU;EAC5C,MAAM,KAAM,IAAI,KAA0B;EAC1C,IAAI,OAAO,OAAO,YAAY,IAAI,OAAO;CAC3C;AAEF;;;;;AAMA,SAAS,YAAY,SAA6C;CAChE,OAAO,UAAU,QAAQ,GAAG;AAC9B;;;;;;;AAQA,SAAS,eAAmC;CAC1C,OAAO,QAAQ,IAAI,yBAAyB,QAAQ,IAAI;AAC1D;;;;;;AAgBA,eAAsB,oBAAoB,EACxC,QACA,SACA,gBAK8B;CAC9B,IAAI,CAAC,cAAc,OAAO,EAAE,QAAQ,UAAU;CAC9C,MAAM,WAAW,OAAO,QAAQ;CAChC,MAAM,iBAAiB,QAAQ,OAAO;CACtC,MAAM,iBAAiB,YAAY,OAAO;CAG1C,MAAM,MAAM,iBAAiB;EAAE;EAAU;EAAgB;CAAe,IAAI,KAAA;CAC5E,MAAM,OAAO,MAAM,MAAM,aAAa,eAAe,GAAG,IAAI;CAC5D,IAAI,QAAQ,KAAK,OAAO;EAAE,QAAQ;EAAU;EAAM;CAAI;CAEtD,MAAM,YAAY,aAAa;CAI/B,IAAI,WACF,MAAM,OAAO,cAAc,QAAQ,QAAQ,iBAAiB,WAAW,MAAM,GAAG,EAAE,cAAc,KAAK,CAAC;CAExG,OAAO,EAAE,QAAQ,UAAU;AAC7B;AAEA,MAAM,aAAa,WAA2B,OAAO,OAAO,qBAAqB;;;;;;AAOjF,SAAS,iBAAiB,WAAmB,QAAuB;CAClE,OAAO,KAAK;EACV,OAAO;EACP,UAAU,CACR,SAAS,gDAAgD,UAAU,MAAM,EAAE,EAAE,GAC7E,QAAQ,CACN,WAAW;GACT,KAAK,GAAG,UAAU;GAClB,OAAO;EACT,CAAC,CACH,CAAC,CACH;CACF,CAAC;AACH;;;;;;;;;;;;AAsBA,eAAsB,sBAAsB,EAC1C,QACA,MACA,KACA,cACA,YAO8B;CAC9B,IAAI,CAAC,UAAU,OAAO,EAAE,QAAQ,UAAU;CAG1C,MAAM,QAAQ,KAAK,SAAS;CAE5B,IAAI,KAAK,yBAAyB;EAChC,MAAM,WAAW,MAAM,SAAS,IAAI;GAAE;GAAO,IAAI,KAAK;EAAwB,CAAC;EAC/E,IAAI,UACF,OAAO;GAAE,QAAQ;GAAY,kBAAkB,SAAS;EAAG;CAE/D;CAEA,MAAM,YAAY,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC;CAC5D,IAAI,UAAU,WAAW,GAAG;EAC1B,MAAM,OAAO,UAAU;EACvB,MAAM,aAAa,kBAAkB;GAAE,GAAG;GAAK,QAAQ,KAAK;GAAQ,kBAAkB,KAAK;EAAG,CAAC;EAC/F,OAAO;GAAE,QAAQ;GAAY,kBAAkB,KAAK;EAAG;CACzD;CAEA,MAAM,YAAY,aAAa;CAC/B,IAAI,WACF,MAAM,OAAO,KACX,KAAK;EACH,OAAO;EACP,UAAU,CACR,SACE,UAAU,WAAW,IACjB,oEAAoE,UAAU,MAAM,EAAE,KACtF,wFAAwF,UAAU,MAAM,EAAE,EAChH,GACA,QAAQ,CACN,WAAW;GACT,KAAK,GAAG,UAAU;GAClB,OAAO;EACT,CAAC,CACH,CAAC,CACH;CACF,CAAC,CACH;CAEF,OAAO,EAAE,QAAQ,UAAU;AAC7B;;;;;;;;;;;;AAaA,SAAS,aAAa,UAA0B;CAG9C,OAAO,UAFU,SAAS,MAAM,GACZ,CAAC,CAAC,UAAS,YAAW,QAAQ,SAAS,CAAC,KAAK,SAAA,CAC5C,QAAQ,oBAAoB,GAAG;AACtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAgB,gCACd,MACA,kCAAwC,IAAI,QAAQ,GACjC;CACnB,MAAM,EAAE,cAAc,aAAa;CACnC,MAAM,iBAAiB,yBAAyB,IAAI;CACpD,OAAO,OAAO,EAAE,UAAU,QAAQ,cAAc;EAM9C,MAAM,qBAAqB,WAAW,OAAO;EAK7C,IAAI,CAAC,gBAAgB,CAAC,YAAY,eAAe,WAAW,GAAG,OAAO;EAGtE,IAAI,CAAC,QAAQ,OAAO,QAAQ,OAAO;EAEnC,MAAM,iBAAiB,UAAU,QAAQ,GAAG;EAC5C,IAAI,CAAC,gBACH,MAAM,IAAI,4BAA4B,wEAAwE;EAEhH,IAAI;GACF,MAAM,OAAO,MAAM,aAAa,eAAe;IAC7C;IACA;IACA,gBAAgB,QAAQ,OAAO;GACjC,CAAC;GACD,IAAI,CAAC,MACH,MAAM,IAAI,4BACR,gBAAgB,QAAQ,OAAO,OAAO,oCACxC;GAMF,MAAM,QAAQ,KAAK,SAAS;GAC5B,IAAI;GACJ,IAAI,KAAK,2BAA4B,MAAM,SAAS,IAAI;IAAE;IAAO,IAAI,KAAK;GAAwB,CAAC,GACjG,mBAAmB,KAAK;QACnB,IAAI,OAAO;IAChB,MAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,CAAC;IAC/C,IAAI,UAAU,WAAW,GAAG,mBAAmB,UAAU,EAAE,CAAE;GAC/D;GACA,IAAI,CAAC,kBACH,MAAM,IAAI,4BAA4B,uBAAuB,QAAQ,OAAO,OAAO,wBAAwB;GAO7G,MAAM,gBAAgB,MAAM,4BAA4B;IAAE;IAAgB;IAAO;GAAiB,CAAC;GACnG,IAAI,CAAC,eAIH,MAAM,IAAI,wBACR,MAJwB,QAAQ,IAChC,eAAe,KAAI,kBAAiB,cAAc,YAAY,KAAK;IAAE;IAAO;GAAiB,CAAC,CAAC,CACjG,EAEE,CAAY,MAAK,SAAQ,KAAK,SAAS,CAAC,IACpC,0EACA,4EACN;GAIF,MAAM,OAAO,MAAM,oCAAoC;IAAE;IAAe;IAAO;GAAiB,CAAC;GACjG,IAAI,CAAC,KAAK,OACR,MAAM,IAAI,uBACR,KAAK,WAAW,eACZ,+EACA,uEACN;GAGF,MAAM,SAAS,aAAa,OAAO,EAAE;GACrC,MAAM,mBAAmB,OAAO,OAAO,KAAK,SAAS;GAKrD,MAAM,WAAW,MAAM,cAAc,SAAS,aAAa;IACzD;IACA,QAAQ,KAAK;IACb;GACF,CAAC;GACD,IAAI,UAAU;IACZ,gBAAgB,IAAI,SAAS,SAAS,SAAS;IAC/C,OAAO;GACT;GACA,MAAM,UAAU,MAAM,cAAc,SAAS,OAAO;IAClD,WAAW,WAAW,OAAO,WAAW;IACxC,qBAAqB,KAAK;IAC1B;IACA;IACA,QAAQ,KAAK;IACb;IACA,YAAY,KAAK;IAEjB,YAAY,OAAO,OAAO,YAAY;GACxC,CAAC;GACD,gBAAgB,IAAI,SAAS,QAAQ,SAAS;GAC9C,OAAO;EACT,SAAS,OAAO;GACd,IAAI,iBAAiB,+BAA+B,iBAAiB,wBAAwB,MAAM;GACnG,IAAI,iBAAiB,mCACnB,MAAM,IAAI,uBACR,gHACA,EAAE,OAAO,MAAM,CACjB;GAEF,QAAQ,MAAM,0DAA0D,OAAO,IAAI,KAAK;GACxF,MAAM,IAAI,uBAAuB,gEAAgE,EAC/F,OAAO,MACT,CAAC;EACH;CACF;AACF;;;;;;;;;;AAWA,SAAgB,8BAA8B,iBAAwD;CACpG,QAAQ,EAAE,SAAS,sBAAsB,gBAAgB,IAAI,OAAO,KAAK;AAC3E;;;;;;;;;;;AAYA,MAAM,oBAAoB,OAAO,mBAAmB;AAEpD,eAAe,qBACb,UACA,QACA,YACA,MACmD;CACnD,IAAI,OAAO,WAAW,WAAW,UAAU,GAAG,OAAO;CAGrD,MAAM,SAAS,OAAO,cAAsB;EAC1C,MAAM,MAAM,MAAM,SAAS,eAAe,SAAS;EACnD,IAAI,CAAC,KAAK,OAAO;EAEjB,IAAI,cAAc,OAAO,MAAM,CAAC,mBAAmB,KAAK,OAAO,UAAU,GAAG,OAAO;EACnF,MAAM,MAAM,cAAc,OAAO,KAAK,SAAS,MAAM,WAAW,SAAS;EACzE,OAAO,OAAO,eAAe,iBAAiB,KAAK,KAAK,MAAM,WAAW,KAAK,IAAI,CAAC;CACrF;CACA,MAAM,UAAU,OAAO,UAAU;CACjC,IAAI,OAAO,YAAY,YAAY,WAAY,MAAM,OAAO,OAAO,GAAI,OAAO;CAC9E,IAAI,MAAM,SAAS,4BAA4B,OAAO,UAAU,GAAG,OAAO,OAAO;CACjF,IAAI,MAAM,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO;CAC3C,IAAI,MAAM,SAAS,eAAe,OAAO,EAAE,GAAG,OAAO;CAIrD,OAAO,OAAO;AAChB;;AAGA,SAAgB,6BAA6B,MAA+C;CAC1F,MAAM,wBAAwB,iCAAiC,yBAAyB,IAAI,CAAC;CAC7F,OAAO,OAAO,EAAE,YAAY,QAAQ,qBAAqB;EAQvD,MAAM,SADW,OAAkD,UAAU,oBAEhE,OAAO,WAAW,WAAW,UAAU,IAC9C,SACE,MAAM,WAAW,gBAAgB,EAAE,UAAU,OAAO,GAAG,CAAC,KAAM;EACtE,MAAM,WAAW,MAAM,qBACrB,uBACA,SACA,aAAY,WAAW,gBAAgB,EAAE,SAAS,CAAC,GACnD,KAAK,SACP;EACA,IAAI,aAAa,mBACf,MAAM,IAAI,MAAM,gBAAgB,OAAO,GAAG,wDAAwD;EAEpG,MAAM,YAAY,YAAY,OAAO;EACrC,MAAM,gBAAgB,UAAU,WAAW,UAAU,IACjD,OACA,MAAM,sBAAsB,4BAA4B,SAAS;EACrE,MAAM,UAAU,gBAAgB,MAAM,cAAc,SAAS,eAAe,SAAS,IAAI;EACzF,OAAO,WAAW,cAAc;GAC9B,IAAI;GACJ,SAAS,WAAW;GACpB,YAAY,OAAO;GAGnB,UAAU,OAAO;GACjB;GACA,GAAI,SAAS,mBAAmB,EAAE,MAAM,EAAE,kBAAkB,QAAQ,iBAAiB,EAAE,IAAI,CAAC;EAC9F,CAAC;CACH;AACF;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,8BAA8B,MAA6C;CACzF,MAAM,EAAE,UAAU,kBAAkB;CACpC,MAAM,wBAAwB,iCAAiC,yBAAyB,IAAI,CAAC;CAC7F,OAAO,OAAO,EAAE,SAAS,QAAQ,qBAAqB;EAOpD,MAAM,eAAe,SAAS,wBAAwB,cAAc,CAAC;EAErE,IAAI,CAAC,UAAU;EACf,IAAI,OAAO,WAAW,WAAW,UAAU,GAAG;EAE9C,MAAM,YAAY,QAAQ,SAAS,MAAM;EACzC,MAAM,gBAAgB,MAAM,sBAAsB,4BAA4B,SAAS;EACvF,IAAI,CAAC,eAAe;EACpB,MAAM,gBAAgB,MAAM,cAAc,SAAS,eAAe,SAAS;EAC3E,IAAI,CAAC,eAAe,kBAAkB;EACtC,MAAM,WAAW,QAAQ,OAAO,MAAM;EACtC,MAAM,UACJ,YAAY,KAAK,YACb,MAAM,KAAK,UAAU,uBAAuB;GAC1C,OAAO,cAAc;GACrB,YAAY,QAAQ,SAAS,cAAc;GAC3C;GACA;GACA,iBAAiB;EACnB,CAAC,IACD;EACN,MAAM,QAAQ;GACZ,kBAAkB,SAAS,oBAAoB,cAAc;GAC7D,OAAO,cAAc;GACrB,QAAQ,cAAc;EACxB;EAEA,MAAM,eAAe,MAAM,2BAA2B;GACpD;GACA;GACA,kBAAkB,MAAM;GACxB,OAAO,MAAM;GACb,aAAa,MAAM;GACnB,SAAS,UAAU,QAAQ;EAC7B,CAAC;EAID,MAAM,QAAQ,MAAM,IAAI;GACtB,kBAAkB,MAAM;GACxB,mBAAmB,aAAa;GAChC,oBAAoB,MAAM;EAC5B,CAAC;EAID,MAAM,eAAe,SAAS,MAAM,KAAK;EACzC,MAAM,gCAAgC;GACpC,OAAO,MAAM;GACb,QAAQ,MAAM;GACd,iBAAiB,aAAa;EAChC,CAAC;EAED,MAAM,iBAAiB,QAAQ,OAAO,MAAM;EAE5C,IAAI,gBACF,MAAM,QAAQ,OAAO,aAAa;GAAE,UAAU;GAAgB,KAAK;GAAoB,OAAO;EAAU,CAAC;EAE3G,MAAM,SAAS,QAAQ,KAAK,IAAI;EAYhC,IAAI,MAX2B,+BAA+B;GAC5D,aAAa,YACX,kBACO,MAAM,QAAQ,OAAO,QAAQ,EAAE,UAAU,eAAe,CAAC,EAAA,EAAI,YACtB,CAAC,IAC3C,CAAC;GACP;GACA,MAAM,KAAa,UACjB,iBAAiB,QAAQ,OAAO,aAAa;IAAE,UAAU;IAAgB;IAAK;GAAM,CAAC,IAAI,QAAQ,QAAQ;GAC3G,UAAU,kBAAkB,KAAA;EAC9B,CAAC,GAIC,KAAK,MAAM,aAAa,gBAAgB;GACtC,MAAM,UAAU,MAAM,QAAQ,OAAO,WAAW,EAAE,KAAK,uBAAuB,SAAS,EAAE,CAAC;GAC1F,IAAI,OAAO,YAAY,UAAU,MAAM,mBAAmB,SAAS,WAAW,OAAO;EACvF;OACK;GAGL,MAAM,kBACH,MAAM,sBAAsB,SAAS;IACpC,OAAO,MAAM;IACb,YAAY,aAAa;IACzB,aAAa,MAAM;IACnB,gBAAgB,aAAa;GAC/B,CAAC,KAAM,QAAQ,MAAM,IAAI;GAI3B,KAAK,MAAM,aAAa,gBAAgB;IACtC,IAAI,CAAC,iBAAiB;IACtB,IAAI,MAAM,mBAAmB,SAAS,WAAW,eAAe,GAC9D,MAAM,QAAQ,OAAO,WAAW;KAAE,KAAK,uBAAuB,SAAS;KAAG,OAAO;IAAgB,CAAC;GAEtG;EACF;CACF;AACF;AAEA,MAAM,iBAAiB;CAAC;CAAW;CAAQ;AAAS;AACpD,MAAM,0BAA0B,cAAsB,wBAAwB;;AAG9E,eAAe,mBACb,SACA,WACA,SACkB;CAClB,IAAI;EACF,MAAM,QAAQ,UAAU,MAAM,IAAI;GAAE;GAAS;EAAU,CAAC;EACxD,OAAO;CACT,SAAS,OAAO;EACd,QAAQ,KAAK,8CAA8C;GACzD;GACA;GACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAC9D,CAAC;EACD,OAAO;CACT;AACF;;;;;;;AAQA,eAAe,mBAAmB,QAA4B,QAAuB;CAEnF,MAAM,EAAE,YAAa,OAAM,MADP,QAAQ,WAAW,CAAC,EAAE,SAAS,QAAQ,EAAA,EACzB,YAAY;EAC5C,QAAQ,EACN,UAAU;GACR,kBAAkB,OAAO,QAAQ;GACjC,0BAA0B,OAAO;GACjC,2BAA2B,OAAO;EACpC,EACF;EACA,SAAS;CACX,CAAC,KAAM,EAAE,SAAS,CAAC,EAAE;CACrB,OAAO,QAAQ,MAAM;AACvB;AAIA,eAAe,mCACb,QACA,MACA,KACA,SAO8D;CAC9D,MAAM,iBAAiB,yBAAyB,IAAI;CACpD,IAAI,eAAe,WAAW,KAAK,CAAC,QAAQ,uBAAuB,OAAO;CAE1E,MAAM,iBACJ,QAAQ,mBAAmB,KAAA,IAAY,QAAQ,iBAAiB,MAAM,mBAAmB,IAAI,QAAQ,MAAM;CAC7G,IAAI,CAAC,gBAAgB;EACnB,IAAI,QAAQ,uBACV,MAAM,IAAI,MAAM,mDAAmD,OAAO,GAAG,EAAE;EAEjF,OAAO;CACT;CACA,IAAI,eAAe,WAAW,KAAK,eAAe,WAAW,WAAW,UAAU,GAAG,OAAO;CAC5F,MAAM,WAAW,iCAAiC,cAAc;CAChE,MAAM,SAAS,MAAM,IAAI,QAAQ,WAAW,CAAC,EAAE,SAAS,QAAQ;CAChE,MAAM,WAAW,MAAM,qBACrB,UACA,gBACA,OAAM,aAAY,QAAQ,cAAc,EAAE,SAAS,CAAC,GACpD,KAAK,SACP;CACA,IAAI,aAAa,mBAAmB,OAAO;CAC3C,MAAM,YAAY,YAAY,eAAe;CAC7C,MAAM,aAAa,MAAM,SAAS,eAAe,SAAS;CAC1D,IACE,cACA,WAAW,UAAU,QAAQ,iBAC7B,WAAW,eAAe,aAC1B,WAAW,WAAW,QAAQ,cAE9B,OAAO;CAET,IAAI,CAAC,QAAQ,eACX,MAAM,IAAI,MACR,0DAA0D,UAAU,iDACtE;CAGF,MAAM,cAAc,MAAM,yBACxB,IAAI,gBACJ,WACA,EAAE,SAAS,GACX,EAAE,eAAe,QAAQ,cAAc,CACzC;CACA,IAAI,gBAAgB,yBAAyB,OAAO;CACpD,IAAI,gBAAgB,YAKlB,MAAM,IAAI,MAAM,0DAA0D,UAAU,IAHlF,gBAAgB,sBACZ,0FACA,qDACyF,EAAE;CAEnG,OAAO;AACT;;;;;;;;;;;;AAaA,eAAe,aACb,QACA,SACA,MACA,KACA,UAA+E,EAAE,uBAAuB,MAAM,GAGtG;CACR,MAAM,EAAE,cAAc,aAAa;CACnC,MAAM,SAAS,MAAM,oBAAoB;EAAE;EAAQ;EAAS;CAAa,CAAC;CAC1E,IAAI,OAAO,WAAW,WAAW,OAAO;CAExC,IAAI,OAAO,WAAW,YAAY,cAAc;EAO9C,IAAI,eAAe,IAAI,QAAQ;GAAE,IAAI,OAAO,KAAK;GAAQ,gBAAgB,OAAO,KAAK;EAAM,CAAC;EAC5F,MAAM,eAAe,MAAM,mCAAmC,QAAQ,MAAM,KAAK;GAC/E,eAAe,OAAO,KAAK;GAC3B,cAAc,OAAO,KAAK;GAC1B,uBAAuB,QAAQ;GAC/B,gBAAgB,QAAQ;EAC1B,CAAC;EACD,IAAI,iBAAiB,iBAAiB;GACpC,MAAM,OAAO,KAAK,uDAAuD;GACzE,OAAO;EACT;EACA,IAAI,iBAAiB,yBAAyB;GAC5C,MAAM,OAAO,KAAK,mEAAmE;GACrF,OAAO;EACT;EAKA,MAAM,wCAAwC,IAAI,cAAc;EAEhE,MAAM,QAAQ,MAAM,sBAAsB;GAAE;GAAQ,GAAG;GAAQ;GAAc;EAAS,CAAC;EACvF,IAAI,MAAM,WAAW,WAAW,OAAO;EACvC,IAAI,MAAM,WAAW,YAAY;GAG/B,IAAI,OAAO,KAAK,OAAO;IACrB,IAAI,eAAe,IAAI,0BAA0B;KAAE,QAAQ,OAAO,KAAK;KAAQ,OAAO,OAAO,KAAK;IAAM,CAAC;IACzG,IAAI,eAAe,IAAI,yBAAyB,IAAI;GACtD;GACA,OAAO,EAAE,QAAQ;IAAE,MAAM,OAAO;IAAM,kBAAkB,MAAM;GAAiB,EAAE;EACnF;CACF;CACA,OAAO,CAAC;AACV;;;;;;AAOA,SAAgB,kBAAkB,QAAuB,QAAyC;CAChG,OAAO;EACL,eAAe,OAAO,QAAQ;EAC9B,MAAM;EACN,GAAI,SAAS,EAAE,aAAa,OAAO,IAAI,CAAC;EACxC,YAAY,OAAO;CACrB;AACF;;AAGA,eAAe,mBACb,WACA,QACA,QAC6B;CAC7B,MAAM,SAAS,MAAM,UAAU,YAAY,kBAAkB,QAAQ,MAAM,CAAC;CAC5E,IAAI,UAAU,CAAC,QAAQ,OAAO;CAC9B,OAAO,UAAU,YAAY,kBAAkB,MAAM,CAAC;AACxD;AAEA,SAAS,wBAAwB,OAAkE;CACjG,MAAM,eAAe,UAAmB;EACtC,MAAM,UACJ,OAAO,UAAU,WACb,QACA,SAAS,OAAO,UAAU,YAAY,aAAa,QACjD,MAAM,UACN,KAAA;EACR,OAAO,OAAO,YAAY,YAAY,QAAQ,SAAS,IAAI,UAAU,KAAA;CACvE;CACA,IAAI;EACF,MAAM,UAAU,YAAY,KAAK,KAAK;EACtC,MAAM,QAAQ,SAAS,OAAO,UAAU,YAAY,WAAW,QAAQ,YAAY,MAAM,KAAK,IAAI,KAAA;EAClG,OAAO;GAAE;GAAS,GAAI,QAAQ,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,IAAI,CAAC;EAAG;CACpE,QAAQ;EACN,OAAO,EAAE,SAAS,4BAA4B;CAChD;AACF;AAEA,eAAe,uBACb,QACA,SACA,KACA,OACe;CACf,IAAI;EACF,IAAI,iBAAiB,6BAA6B;CACpD,QAAQ,CAER;CACA,MAAM,cAAc;EAClB,UAAU,OAAO,QAAQ;EACzB,UAAU,OAAO;EACjB,WAAW,QAAQ;EACnB,UAAU,QAAQ,OAAO;CAC3B;CACA,MAAM,SAAS,OAAO,IAAI,QAAQ,cAAc,aAAa,IAAI,OAAO,UAAU,IAAI,KAAA;CACtF,MAAM,YAAY,SAAkB,SAAS,OAAO,MAAM,IAAI,IAAI,QAAQ,MAAM,IAAI;CACpF,SAAS,gCAAgC,KAAK,UAAU;EAAE,GAAG;EAAa,OAAO,wBAAwB,KAAK;CAAE,CAAC,GAAG;CACpH,IAAI;EAEF,MAAM,OAAO,KAAK,2EAA2E,QAAQ,GAAG,GAAG;CAC7G,SAAS,eAAe;EACtB,SACE,sDAAsD,KAAK,UAAU;GAAE,GAAG;GAAa,OAAO,wBAAwB,aAAa;EAAE,CAAC,GACxI;CACF;AACF;;;;;;;;;;;AAYA,MAAM,sBAAsB;AAE5B,SAAS,mBAAmB,QAAuB,MAA8B;CAC/E,IAAI,KAAK,gBAAgB,iBAAiB,OAAO;CAGjD,OAAY,YAAY,mBAAmB,CAAC,CAAC,OAAM,UAAS;EAC1D,QAAQ,KAAK,sCAAsC;GAAE,QAAQ,OAAO;GAAI;EAAM,CAAC;CACjF,CAAC;AACH;AAEA,SAAS,4BAA4B,MAAwC;CAC3E,OAAO,OAAO,QAAQ,SAAS,gBAAgB,QAAQ;EAMrD,IAAI;GACF,IAAI,CAAE,MAAM,aAAa,QAAQ,SAAS,MAAM,GAAG,GAAI;GAMvD,IAAI,CAAE,MAAM,OAAO,aAAa,GAAI,sBAAsB,IAAI,cAAc;GAC5E,mBAAmB,QAAQ,IAAI;EACjC,SAAS,OAAO;GACd,MAAM,uBAAuB,QAAQ,SAAS,KAAK,KAAK;GACxD;EACF;EACA,MAAM,eAAe,QAAQ,OAAO;CACtC;AACF;;;;;;;AAQA,eAAe,YACb,QACA,SACA,EAAE,MAAM,WAAW,gBACJ;CACf,IAAI,CAAC,QAAQ,CAAC,WAAW;CACzB,MAAM,OAAO,uBAAuB,QAAQ,KAAK,QAAQ,qBAAqB,EAAE,CAAC,CAAC,KAAK,CAAC;CACxF,IAAI,CAAC,MAAM;CACX,IAAI;EACF,MAAM,SAAS,YAAY,OAAO;EAClC,MAAM,WAAW,MAAM,mBAAmB,WAAW,QAAQ,MAAM;EACnE,IAAI,CAAC,UAAU;EAEf,MAAM,WAAyC;GAC7C,UAAU,OAAO,QAAQ;GACzB,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAC3B,WAAW,QAAQ;GACnB,QAAQ,QAAQ,OAAO;GACvB,UAAU,QAAQ,OAAO;GACzB,UAAU,QAAQ,OAAO;GACzB,OAAO,QAAQ,OAAO;EACxB;EACA,MAAM,OACJ,gBAAgB,SACZ,MAAM,aAAa,eAAe;GAChC,UAAU,SAAS;GACnB,gBAAgB;GAChB,gBAAgB,SAAS;EAC3B,CAAC,IACD;EAEN,MAAM,SAAS,MAAM,KAAK,cAAc;GACtC,OAAO,SAAS;GAChB,YAAY,SAAS;GACrB,QAAQ,uBAAuB,UAAU,IAAI;GAC7C;GACA,YAAY,QAAQ,SAAS;GAC7B,gBAAgB,mBAAmB,OAAO,IAAI,QAAQ,IAAI,MAAM;EAClE,CAAC;EACD,IAAI,OAAO,WAAW,WACpB,QAAQ,KAAK,8BAA8B;GAAE,QAAQ,OAAO;GAAI,WAAW,QAAQ;GAAI,QAAQ,OAAO;EAAO,CAAC;CAElH,SAAS,OAAO;EAEd,QAAQ,KAAK,+BAA+B;GAAE,QAAQ,OAAO;GAAI,WAAW,QAAQ;GAAI;EAAM,CAAC;CACjG;AACF;AAEA,MAAa,kBAAkB,SAA4C;CACzE,MAAM,wBAAwB,4BAA4B,IAAI;CAE9D,OAAO;EACL,qBAAqB,OAAO,QAAQ,SAAS,gBAAgB,QAAQ;GAInE,IAAI,YAAY,KAAK,QAAQ,IAAI,GAAG,OAAO,YAAY,QAAQ,SAAS,IAAI;GAE5E,MAAM,iBAAiB,MAAM,mBAAmB,IAAI,QAAQ,MAAM;GAGlE,IAAI,CAAC,QAAQ,aAAa,gBAAgB,WAAW,eAAe,QAAQ;GAM5E,IAAI;IAEF,IAAI,CAAC,MADc,aAAa,QAAQ,SAAS,MAAM,KAAK;KAAE,uBAAuB;KAAM;IAAe,CAAC,GAChG;GACb,SAAS,OAAO;IACd,MAAM,uBAAuB,QAAQ,SAAS,KAAK,KAAK;IACxD;GACF;GACA,MAAM,UAAU,QAAQ,YAAY,YAAY,OAAO,IAAI,KAAA;GAC3D,IAAI,WAAW,gBAAgB,OAAO,eAAe,QAAQ,gBAAgB,SAAS,KAAK,YAAY,MAAM;GAC7G,mBAAmB,QAAQ,IAAI;GAC/B,MAAM,eAAe,QAAQ,OAAO;EACtC;EACA,WAAW,OAAO,QAAQ,SAAS,gBAAgB,QAAQ;GAGzD,IAAI,YAAY,OAAO,GAAG;GAC1B,MAAM,sBAAsB,QAAQ,SAAS,gBAAgB,GAAG;EAClE;EACA,iBAAiB;CACnB;AACF;;AAGA,MAAM,YAAY;;;;;;;AAQlB,SAAS,YAAY,SAAwD;CAC3E,MAAM,MAAM,QAAQ;CAEpB,MAAM,QADO,OAAO,KAAK,SAAS,WAAW,IAAI,OAAO,QAAQ,KAAA,CAE7D,QAAQ,aAAa,GAAG,CAAC,CACzB,QAAQ,oBAAoB,GAAG,CAAC,CAChC,KAAK,CAAC,CACN,QAAQ,UAAU,EAAE,CAAC,CACrB,YAAY;CACf,OAAO,SAAS,UAAU,SAAS,WAAW,OAAO,KAAA;AACvD;;;;;;AAOA,eAAe,eACb,QACA,UACA,SACA,KACA,OACe;CACf,MAAM,QAAQ,MAAM,IAAI,QAAQ,WAAW,CAAC,EAAE,SAAS,QAAQ;CAC/D,IAAI,CAAC,OAAO;CACZ,MAAM,MAAM,YAAY;EAAE,IAAI,SAAS;EAAI,UAAU;GAAE,GAAG,SAAS;IAAW,YAAY,OAAO,KAAK;EAAE;CAAE,CAAC;CAC3G,IAAI;EACF,MAAM,OAAO,QAAQ,YAAY,OAAO,IAAI,QAAQ,IAAI,QAAQ,SAAS,YAAY;CACvF,SAAS,OAAO;EACd,QAAQ,KAAK,gCAAgC;GAAE,QAAQ,OAAO;GAAI;EAAM,CAAC;CAC3E;AACF;AAUA,SAAgB,0BAA0B,MAA6E;CAGrH,MAAM,UAAU,mBAAmB;EAAE,GAAG,KAAK;EAAO,kBAAkB,EAAE,SAAS,yBAAyB;CAAE,CAAC;CAC7G,MAAM,cAAwD,KAAK,gBAAgB,eAAe;CAClG,MAAM,QACJ,KAAK,gBAAgB,cAAc,QAC/B;EAAE;EAAS,GAAG,KAAK;EAAgB;CAAY,IAC/C;EAAE;EAAS,GAAG,KAAK;EAAgB;EAAa,WAAW,KAAK,gBAAgB,aAAa;CAAK;CAExG,MAAM,kCAAwC,IAAI,QAAQ;CAC1D,OAAO;EACL,UAAU,EAAE,MAAM;EAClB,UAAU,eAAe,IAAI;EAG7B,mBAAmB,gCAAgC,MAAM,eAAe;EACxE,iBAAiB,8BAA8B,eAAe;EAC9D,gBAAgB,6BAA6B,IAAI;EAGjD,gBAAgB,8BAA8B,IAAI;CACpD;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { primeTenantCredentialsForRequestContext } from "../routes/tenant-credentials.js";
2
2
  import { hasResolvedOrg, seedSessionOrg } from "../session/org-seed.js";
3
3
  import { RequestContext } from "@mastra/core/request-context";
4
- //#region src/integrations/subscription-session.ts
4
+ // src/integrations/subscription-session.ts
5
5
  /**
6
6
  * The request context a provider-triggered run executes under: the user who
7
7
  * subscribed the thread, in the subscription's organization. A webhook carries
@@ -112,7 +112,6 @@ async function resolveSubscriptionSession(controller, subscription, options) {
112
112
  if (session.thread.getId() !== threadId) throw new Error(`Session ${sessionId} did not bind thread ${threadId}.`);
113
113
  return session;
114
114
  }
115
- //#endregion
116
115
  export { createSessionAsRowOwner, prepareSessionRunContext, resolveSubscriptionSession, subscriptionRunContext };
117
116
 
118
117
  //# sourceMappingURL=subscription-session.js.map
@@ -1,6 +1,6 @@
1
1
  import { WorkOSAdminPortal } from "@mastra/auth-workos";
2
2
  import { registerApiRoute } from "@mastra/core/server";
3
- //#region src/integrations/workos/integration.ts
3
+ // src/integrations/workos/integration.ts
4
4
  const UNKNOWN_LOCATION = "unknown";
5
5
  function loose(c) {
6
6
  return c;
@@ -85,7 +85,6 @@ var WorkOSAuditIntegration = class {
85
85
  return { configured: true };
86
86
  }
87
87
  };
88
- //#endregion
89
88
  export { WorkOSAuditIntegration, toWorkOSEvent };
90
89
 
91
90
  //# sourceMappingURL=integration.js.map
@@ -1,4 +1,4 @@
1
- //#region src/mcp/disconnect.ts
1
+ // src/mcp/disconnect.ts
2
2
  /**
3
3
  * Emits `close` on the `toReqRes` response when the HTTP client cancels the
4
4
  * body, so the MCP transport aborts instead of writing into a closed stream.
@@ -38,7 +38,6 @@ function propagateClientDisconnect(fetchResponse, res) {
38
38
  headers: fetchResponse.headers
39
39
  });
40
40
  }
41
- //#endregion
42
41
  export { propagateClientDisconnect };
43
42
 
44
43
  //# sourceMappingURL=disconnect.js.map
@@ -1,10 +1,10 @@
1
- import { canViewSourceControlSession } from "../routes/session-access.js";
2
1
  import { ACTOR_PROFILE_METADATA_KEY, auditCursorAt, decodeAuditAfterCursor, encodeAuditCursor } from "../storage/domains/audit/base.js";
2
+ import { canViewSourceControlSession } from "../routes/session-access.js";
3
3
  import { parseAuditAction } from "../storage/domains/audit/actions.js";
4
4
  import { AUDIT_ACTOR_TYPES } from "../storage/domains/audit/actors.js";
5
5
  import { z } from "zod";
6
6
  import { defineEvent } from "@mastra/mcp";
7
- //#region src/mcp/events.ts
7
+ // src/mcp/events.ts
8
8
  const activityArgs = z.object({
9
9
  workItemId: z.string().min(1).optional().describe("Only activity on this work item."),
10
10
  actions: z.array(z.string().min(1)).max(50).optional().describe("Only these actions, e.g. `factory.work_item.stage_moved`.")
@@ -152,7 +152,6 @@ function factoryActivityEvent(deps) {
152
152
  }
153
153
  });
154
154
  }
155
- //#endregion
156
155
  export { ACTIVITY_SETTLE_MS, factoryActivityEvent };
157
156
 
158
157
  //# sourceMappingURL=events.js.map
@@ -12,7 +12,7 @@ import { z } from "zod";
12
12
  import { createTool } from "@mastra/core/tools";
13
13
  import { MCPServer } from "@mastra/mcp";
14
14
  import { toFetchResponse, toReqRes } from "fetch-to-node";
15
- //#region src/mcp/routes.ts
15
+ // src/mcp/routes.ts
16
16
  const FACTORY_MCP_PATH = "/web/factory/projects/:projectId/mcp";
17
17
  const PROTECTED_RESOURCE_PREFIX = "/.well-known/oauth-protected-resource";
18
18
  /**
@@ -264,7 +264,6 @@ function buildFactoryMcpRoutes(deps) {
264
264
  }
265
265
  })];
266
266
  }
267
- //#endregion
268
267
  export { FACTORY_MCP_PATH, PLATFORM_MCP_AUTHORIZATION_SERVER, buildFactoryMcpRoutes, connectorSessionId, createFactoryMcpTools, resolveMcpAuthorizationServers };
269
268
 
270
269
  //# sourceMappingURL=routes.js.map
@@ -1,7 +1,7 @@
1
1
  import "@mastra/core/workspace";
2
2
  import "path";
3
3
  import { createHash } from "crypto";
4
- //#region ../../packages/_internals/workspace/dist/index.js
4
+ // ../../packages/_internals/workspace/dist/index.js
5
5
  /** Directory, relative to the template's build cwd, that holds every marker below. */
6
6
  const SETUP_MARKER_DIR = ".mastra-sandbox";
7
7
  /**
@@ -59,7 +59,6 @@ const FILE_COMMAND_EXIT = {
59
59
  destIsDirectory: 24
60
60
  };
61
61
  const { notFound, isDirectory, exists, notDirectory, destIsDirectory } = FILE_COMMAND_EXIT;
62
- //#endregion
63
62
  export { FILE_COMMAND_EXIT, SETUP_FAILED_MARKER_PATH, SETUP_MARKER_DIR, SETUP_MARKER_PATH, WORKSPACE_SETUP_MARKER_PATH, normalizeSetupCommands, repoSetupMarkerPath, setupMarkerContent };
64
63
 
65
64
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../../../../packages/_internals/workspace/dist/index.js"],"sourcesContent":["import { createHash } from \"crypto\";\nimport { posix } from \"path\";\nimport { Buffer } from \"buffer\";\nimport { DirectoryNotEmptyError, DirectoryNotFoundError, FileExistsError, FileNotFoundError, IsDirectoryError, MastraFilesystem, NotDirectoryError, ProcessHandle, SandboxProcessManager, UnsupportedStdinCloseError } from \"@mastra/core/workspace\";\n//#region src/setup-marker.ts\n/** Directory, relative to the template's build cwd, that holds every marker below. */\nconst SETUP_MARKER_DIR = \".mastra-sandbox\";\n/**\n* Setup completion marker shared by repo templates and their consumers.\n*\n* A repo template writes this file beside the checkout as its last build\n* step, so it exists only in images where every setup command succeeded. Its\n* content is a digest of the setup commands the image ran, letting a sandbox\n* booted from the image tell whether the setup it is about to run already\n* happened. Relative to the template's build cwd, which is also the runtime\n* working directory the repo was cloned into.\n*\n* This is the single-repository marker. Templates built from a repository\n* list write one marker per repository instead ({@link repoSetupMarkerPath}).\n*/\nconst SETUP_MARKER_PATH = \".mastra-sandbox/setup\";\n/**\n* Marker for the workspace-level setup of a multi-repository template: the\n* commands that ran at the working directory after every repository was set\n* up. Written even when there were no such commands, so a consumer can tell\n* \"ran nothing\" from \"never built\".\n*/\nconst WORKSPACE_SETUP_MARKER_PATH = \".mastra-sandbox/workspace-setup\";\n/**\n* One line per repository directory whose setup failed while the build was\n* told to continue. Absent when nothing failed. Consumers check this before\n* trusting a per-repository marker: that marker means every step ran, not\n* that every step succeeded.\n*/\nconst SETUP_FAILED_MARKER_PATH = \".mastra-sandbox/setup-failed\";\n/** Per-repository marker of a multi-repository template, keyed by the clone directory name. */\nfunction repoSetupMarkerPath(repoDir) {\n\treturn `${SETUP_MARKER_DIR}/repos/${repoDir}`;\n}\n/** Blank entries never become build steps, so they never count toward the digest either. */\nfunction normalizeSetupCommands(setupCommand) {\n\treturn (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== \"\");\n}\n/** The marker content for a setup command list: `sha256:<hex>` over the commands joined by newlines. */\nfunction setupMarkerContent(setupCommand) {\n\treturn `sha256:${createHash(\"sha256\").update(normalizeSetupCommands(setupCommand).join(\"\\n\")).digest(\"hex\")}`;\n}\n/** Shell step that writes the marker relative to the cwd. `content` is a digest, so it is shell-safe. */\nfunction setupMarkerCommand(content, markerPath = SETUP_MARKER_PATH) {\n\treturn `mkdir -p \"$(dirname \"${markerPath}\")\" && printf '%s' '${content}' > \"${markerPath}\"`;\n}\n/** A clone directory name is one path segment of word characters, dots and dashes, so it never needs quoting. */\nconst REPO_DIR_PATTERN = /^[\\w.-]+$/;\n/**\n* Shell step that runs one setup command inside a repository clone.\n*\n* Without the guard this is the plain `cd \"<dir>\" && <cmd>` step. With it,\n* the `cd` sits inside the parenthesized group so that the failure branch\n* runs back at the build cwd and the failure list lands at the workspace\n* level, never inside the repository.\n*/\nfunction guardedSetupCommand({ repoDir, command, continueOnFailure }) {\n\tif (!REPO_DIR_PATTERN.test(repoDir)) throw new TypeError(`Repository directory \"${repoDir}\" must match ${REPO_DIR_PATTERN}.`);\n\tif (!continueOnFailure) return `cd \"${repoDir}\" && ${command}`;\n\treturn `( cd \"${repoDir}\" && sh -c ${shellQuote$2(command)} ) || { mkdir -p \"${SETUP_MARKER_DIR}\" && grep -qxF -- '${repoDir}' \"${SETUP_FAILED_MARKER_PATH}\" 2>/dev/null || printf '%s\\\\n' '${repoDir}' >> \"${SETUP_FAILED_MARKER_PATH}\"; }`;\n}\nfunction shellQuote$2(value) {\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n//#endregion\n//#region src/repo-clone.ts\nfunction repoCloneCommand({ cloneUrl, destination, branch, tokenEnv }) {\n\treturn `git ${tokenEnv ? `${gitAuthFlag(tokenEnv)} ` : \"\"}clone --depth=1 --single-branch ${branch ? `--branch ${shellQuote$1(branch)} ` : \"\"}${shellQuote$1(cloneUrl)} ${shellQuote$1(destination)}`;\n}\n/** Per-invocation auth header; `-c` config never reaches `.git/config`. */\nfunction gitAuthFlag(tokenEnv) {\n\treturn `-c http.extraheader=\"AUTHORIZATION: basic $(printf 'x-access-token:%s' \"$${tokenEnv}\" | base64 -w0)\"`;\n}\nfunction shellQuote$1(value) {\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n//#endregion\n//#region src/file-commands.ts\n/**\n* Shell scripts for the file operations a sandbox can only do atomically (or\n* without moving file bytes over the network) by running a command inside the\n* VM. Each builder takes absolute paths and returns one POSIX `sh` script; the\n* caller runs it with `sh -c` and maps the exit codes below to typed errors.\n*/\n/** Exit codes the scripts use for outcomes the caller turns into typed errors. Any other non-zero code is a plain failure. */\nconst FILE_COMMAND_EXIT = {\n\tnotFound: 20,\n\tisDirectory: 21,\n\texists: 22,\n\tnotDirectory: 23,\n\tdestIsDirectory: 24\n};\n/** Single-quote a string for safe POSIX shell interpolation. */\nfunction shellQuote(value) {\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\nconst { notFound, isDirectory, exists, notDirectory, destIsDirectory } = FILE_COMMAND_EXIT;\n/** `[ -e ]` is false for a broken symlink, which still occupies the path. */\nconst present = (variable) => `{ [ -e \"$${variable}\" ] || [ -L \"$${variable}\" ]; }`;\n/**\n* A sibling temp path for `path`, so a later `ln` / `mv` onto `path` stays on\n* one filesystem. Hidden, and unique per call.\n*/\nfunction siblingTempPath(path) {\n\tconst suffix = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;\n\treturn posix.join(posix.dirname(path), `.${posix.basename(path)}.mastra-tmp-${suffix}`);\n}\n/**\n* Hard-link `$tmp` to `$dest` and delete `$tmp`, failing with `exists` when\n* something is at `$dest`. `ln` onto a directory (or a link to one) would\n* create the link inside it and succeed, so that case is undone and reported\n* as taken too.\n*/\nfunction linkTempIntoPlace() {\n\treturn [\n\t\t`ln \"$tmp\" \"$dest\" 2>/dev/null || { rm -f \"$tmp\"; ${present(\"dest\")} && exit ${exists}; exit 1; }`,\n\t\t`if [ -d \"$dest\" ]; then rm -f \"$dest/$(basename \"$tmp\")\" \"$tmp\"; exit ${exists}; fi`,\n\t\t`rm -f \"$tmp\"`\n\t];\n}\n/**\n* Copy `src` to `dest` inside the sandbox, creating `dest`'s parent.\n*\n* A file source copies its content (a symlinked source is followed). A\n* directory source with `recursive` copies its contents into `dest`, keeping\n* symlinks inside the tree as links. With `overwrite: false` the destination\n* is claimed atomically: a directory with an exclusive `mkdir`, a file by\n* copying to a temp name and hard-linking it into place (`link(2)` fails when\n* the destination exists).\n*\n* Exits: `notFound` (no source), `isDirectory` (directory source without\n* `recursive`), `destIsDirectory` (a file onto an existing directory or a link\n* to one), `exists`.\n*/\nfunction copyCommand(src, dest, options = {}) {\n\tconst noClobber = options.overwrite === false;\n\treturn [\n\t\t`src=${shellQuote(src)}`,\n\t\t`dest=${shellQuote(dest)}`,\n\t\t`[ -e \"$src\" ] || exit ${notFound}`,\n\t\t`mkdir -p \"$(dirname \"$dest\")\" || exit 1`,\n\t\t`if [ -d \"$src\" ]; then`,\n\t\toptions.recursive ? ` :` : ` exit ${isDirectory}`,\n\t\tnoClobber ? ` mkdir \"$dest\" 2>/dev/null || exit ${exists}` : ` mkdir -p \"$dest\" || exit 1`,\n\t\t` cp -R -P \"$src\"/. \"$dest\"/ || exit 1`,\n\t\t` exit 0`,\n\t\t`fi`,\n\t\tnoClobber ? `${present(\"dest\")} && exit ${exists}` : `if [ -d \"$dest\" ]; then exit ${destIsDirectory}; fi`,\n\t\t...noClobber ? [\n\t\t\t`tmp=${shellQuote(siblingTempPath(dest))}`,\n\t\t\t`cp \"$src\" \"$tmp\" || { rm -f \"$tmp\"; exit 1; }`,\n\t\t\t...linkTempIntoPlace()\n\t\t] : [`cp \"$src\" \"$dest\" || exit 1`]\n\t].join(\"\\n\");\n}\n/**\n* Move `src` to `dest` without replacing anything at `dest`, creating `dest`'s\n* parent. A skipped `mv -n` is detected by the source surviving, whatever\n* its exit code. If a directory appears at `dest` between the check and the\n* move, `mv` moves `src` into it (BSD `mv` has no `-T`), and that is reported\n* as success; the no-clobber rename itself is atomic.\n*\n* Exits: `notFound` (no source), `exists`.\n*/\nfunction moveNoClobberCommand(src, dest) {\n\treturn [\n\t\t`src=${shellQuote(src)}`,\n\t\t`dest=${shellQuote(dest)}`,\n\t\t`${present(\"src\")} || exit ${notFound}`,\n\t\t`${present(\"dest\")} && exit ${exists}`,\n\t\t`mkdir -p \"$(dirname \"$dest\")\" || exit 1`,\n\t\t`mv -n \"$src\" \"$dest\" 2>/dev/null || { ${present(\"src\")} && ${present(\"dest\")} && exit ${exists}; exit 1; }`,\n\t\t`${present(\"src\")} && exit ${exists}`,\n\t\t`exit 0`\n\t].join(\"\\n\");\n}\n/**\n* Append the bytes already written to `tmp` onto `dest`, then delete `tmp`.\n* The content travels as a file rather than a command argument, so it has no\n* size limit. Creates `dest` (and its parent) when missing.\n*\n* Exits: `isDirectory` (dest is a directory).\n*/\nfunction appendFromCommand(tmp, dest) {\n\treturn [\n\t\t`tmp=${shellQuote(tmp)}`,\n\t\t`dest=${shellQuote(dest)}`,\n\t\t`if [ -d \"$dest\" ]; then rm -f \"$tmp\"; exit ${isDirectory}; fi`,\n\t\t`mkdir -p \"$(dirname \"$dest\")\" && cat \"$tmp\" >> \"$dest\"`,\n\t\t`status=$?`,\n\t\t`rm -f \"$tmp\"`,\n\t\t`exit $status`\n\t].join(\"\\n\");\n}\n/**\n* Publish the file already written to `tmp` at `dest` only if nothing is\n* there, then delete `tmp`. `ln` (`link(2)`) is the atomic exclusivity check;\n* `tmp` must be on the same filesystem, e.g. from {@link siblingTempPath}.\n*\n* Exits: `exists`, `notDirectory` (dest's parent is not a directory).\n*/\nfunction linkNoClobberCommand(tmp, dest) {\n\treturn [\n\t\t`tmp=${shellQuote(tmp)}`,\n\t\t`dest=${shellQuote(dest)}`,\n\t\t`if [ ! -d \"$(dirname \"$dest\")\" ]; then rm -f \"$tmp\"; exit ${notDirectory}; fi`,\n\t\t`if ${present(\"dest\")}; then rm -f \"$tmp\"; exit ${exists}; fi`,\n\t\t...linkTempIntoPlace()\n\t].join(\"\\n\");\n}\n//#endregion\n//#region src/files-api-filesystem.ts\n/** Default recursion depth for `readdir({ recursive: true })`, matching `LocalFilesystem`. */\nconst DEFAULT_MAX_DEPTH = 100;\nconst toAbsolute = (path) => posix.resolve(\"/\", path);\n/** The entry's own type: a symlink is a link even when it points at a directory. */\nconst isSymlink = (info) => info.symlinkTarget !== void 0;\nfunction toWriteData(content) {\n\tif (typeof content === \"string\") return content;\n\treturn content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength);\n}\nfunction matchesExtension(name, extension) {\n\tif (!extension) return true;\n\tconst ext = posix.extname(name);\n\treturn (Array.isArray(extension) ? extension : [extension]).some((e) => e === ext || e === ext.slice(1));\n}\n/**\n* A sandbox's disk, read and written through E2B's envd files API, with the\n* operations the files API can't do in one call (copy, append, no-clobber\n* writes and moves) run as one shell command inside the VM so they are atomic\n* and file bytes never round-trip through the host. Paths are absolute inside\n* the sandbox; this class does not confine paths or symlinks to any root.\n*\n* Providers supply how to reach the files API and the shell, and how to read\n* their SDK's errors.\n*\n* If a provider's `runCommand` re-runs a command whose first run already\n* finished (e.g. a retry after a dropped connection), the second run sees the\n* first's result: a no-clobber copy, write or move reports `FileExistsError`\n* or `FileNotFoundError`, and an append fails, even though the work was done.\n*/\nvar FilesApiFilesystem = class extends MastraFilesystem {\n\tasync readFile(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tconst bytes = await this.runFiles(\"readFile\", async (files) => {\n\t\t\ttry {\n\t\t\t\treturn await files.read(abs, { format: \"bytes\" });\n\t\t\t} catch (error) {\n\t\t\t\tconst kind = this.errorKind(error);\n\t\t\t\tif (kind === \"not-found\") throw new FileNotFoundError(path);\n\t\t\t\tif (kind === \"invalid-argument\") throw new IsDirectoryError(path);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t\tconst buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n\t\treturn options?.encoding ? buffer.toString(options.encoding) : buffer;\n\t}\n\tasync writeFile(path, content, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tif (options?.overwrite !== false) {\n\t\t\tawait this.runFiles(\"writeFile\", async (files) => {\n\t\t\t\tif (options?.recursive === false) await this.#assertParentDirectory(files, abs, path);\n\t\t\t\tawait this.#write(files, abs, path, toWriteData(content));\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tconst tmp = siblingTempPath(abs);\n\t\tconst upload = async (files) => {\n\t\t\tif (options.recursive === false) await this.#assertParentDirectory(files, abs, path);\n\t\t\tawait this.#write(files, tmp, path, toWriteData(content));\n\t\t};\n\t\tawait this.#publishTemp(tmp, \"writeFile\", upload, linkNoClobberCommand(tmp, abs), path, {\n\t\t\texists: () => new FileExistsError(path),\n\t\t\tnotDirectory: () => new NotDirectoryError(posix.dirname(path))\n\t\t});\n\t}\n\tasync appendFile(path, content) {\n\t\tconst abs = toAbsolute(path);\n\t\tconst tmp = siblingTempPath(abs);\n\t\tconst upload = (files) => this.#write(files, tmp, path, toWriteData(content));\n\t\tawait this.#publishTemp(tmp, \"appendFile\", upload, appendFromCommand(tmp, abs), path, { isDirectory: () => new IsDirectoryError(path) });\n\t}\n\tasync deleteFile(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tawait this.runFiles(\"deleteFile\", async (files) => {\n\t\t\tconst info = await this.#info(files, abs);\n\t\t\tif (!info) {\n\t\t\t\tif (options?.force) return;\n\t\t\t\tthrow new FileNotFoundError(path);\n\t\t\t}\n\t\t\tif (!isSymlink(info) && info.type === \"dir\") throw new IsDirectoryError(path);\n\t\t\tawait files.remove(abs);\n\t\t});\n\t}\n\t/**\n\t* One `cp` inside the VM. A recursive copy keeps symlinks inside the tree as\n\t* links; `overwrite: false` claims the destination atomically.\n\t*/\n\tasync copyFile(src, dest, options) {\n\t\tconst command = copyCommand(toAbsolute(src), toAbsolute(dest), {\n\t\t\trecursive: options?.recursive,\n\t\t\toverwrite: options?.overwrite\n\t\t});\n\t\tawait this.#command(\"copyFile\", command, `${src} -> ${dest}`, {\n\t\t\tnotFound: () => new FileNotFoundError(src),\n\t\t\tisDirectory: () => new IsDirectoryError(src),\n\t\t\tdestIsDirectory: () => new IsDirectoryError(dest),\n\t\t\texists: () => new FileExistsError(dest)\n\t\t});\n\t}\n\tasync moveFile(src, dest, options) {\n\t\tconst srcAbs = toAbsolute(src);\n\t\tconst destAbs = toAbsolute(dest);\n\t\tif (options?.overwrite === false) {\n\t\t\tawait this.#command(\"moveFile\", moveNoClobberCommand(srcAbs, destAbs), `${src} -> ${dest}`, {\n\t\t\t\tnotFound: () => new FileNotFoundError(src),\n\t\t\t\texists: () => new FileExistsError(dest)\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tawait this.runFiles(\"moveFile\", async (files) => {\n\t\t\ttry {\n\t\t\t\tawait files.rename(srcAbs, destAbs);\n\t\t\t} catch (error) {\n\t\t\t\tif (this.errorKind(error) === \"not-found\") throw new FileNotFoundError(src);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t}\n\tasync mkdir(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tawait this.runFiles(\"mkdir\", async (files) => {\n\t\t\tif (options?.recursive === false) await this.#assertParentDirectory(files, abs, path);\n\t\t\ttry {\n\t\t\t\tawait files.makeDir(abs);\n\t\t\t} catch (error) {\n\t\t\t\tif (this.errorKind(error) === \"invalid-argument\") throw new FileExistsError(path);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t}\n\tasync rmdir(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tawait this.runFiles(\"rmdir\", async (files) => {\n\t\t\tconst info = await this.#info(files, abs);\n\t\t\tif (!info) {\n\t\t\t\tif (options?.force) return;\n\t\t\t\tthrow new DirectoryNotFoundError(path);\n\t\t\t}\n\t\t\tif (!options?.recursive) {\n\t\t\t\tif (isSymlink(info) || info.type !== \"dir\") throw new NotDirectoryError(path);\n\t\t\t\tif ((await files.list(abs)).length > 0) throw new DirectoryNotEmptyError(path);\n\t\t\t} else if (!isSymlink(info) && info.type !== \"dir\") throw new NotDirectoryError(path);\n\t\t\tawait files.remove(abs);\n\t\t});\n\t}\n\tasync readdir(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tconst depth = options?.recursive ? (options.maxDepth ?? DEFAULT_MAX_DEPTH) + 1 : 1;\n\t\tconst entries = await this.runFiles(\"readdir\", async (files) => {\n\t\t\ttry {\n\t\t\t\treturn await files.list(abs, { depth });\n\t\t\t} catch (error) {\n\t\t\t\tconst kind = this.errorKind(error);\n\t\t\t\tif (kind === \"not-found\") throw new DirectoryNotFoundError(path);\n\t\t\t\tif (kind === \"invalid-argument\") throw new NotDirectoryError(path);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t\tconst result = [];\n\t\tfor (const entry of entries) {\n\t\t\tif (!entry.type) continue;\n\t\t\tconst name = posix.relative(abs, entry.path);\n\t\t\tconst type = entry.type === \"dir\" ? \"directory\" : \"file\";\n\t\t\tif (type === \"file\" && !matchesExtension(name, options?.extension)) continue;\n\t\t\tconst link = isSymlink(entry);\n\t\t\tresult.push({\n\t\t\t\tname,\n\t\t\t\ttype,\n\t\t\t\t...type === \"file\" && !link && { size: entry.size },\n\t\t\t\t...link && {\n\t\t\t\t\tisSymlink: true,\n\t\t\t\t\tsymlinkTarget: entry.symlinkTarget\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t}\n\tasync exists(path) {\n\t\tconst abs = toAbsolute(path);\n\t\treturn this.runFiles(\"exists\", async (files) => Boolean((await this.#info(files, abs))?.type));\n\t}\n\tasync stat(path) {\n\t\tconst abs = toAbsolute(path);\n\t\treturn this.runFiles(\"stat\", async (files) => {\n\t\t\tlet info = await this.#info(files, abs);\n\t\t\tif (info?.type && isSymlink(info)) info = await this.#info(files, posix.resolve(posix.dirname(abs), info.symlinkTarget));\n\t\t\tif (!info?.type) throw new FileNotFoundError(path);\n\t\t\tconst modifiedAt = info.modifiedTime ?? /* @__PURE__ */ new Date(0);\n\t\t\treturn {\n\t\t\t\tname: posix.basename(abs),\n\t\t\t\tpath: abs,\n\t\t\t\ttype: info.type === \"dir\" ? \"directory\" : \"file\",\n\t\t\t\tsize: info.type === \"dir\" ? 0 : info.size,\n\t\t\t\tcreatedAt: modifiedAt,\n\t\t\t\tmodifiedAt\n\t\t\t};\n\t\t});\n\t}\n\t/** `getInfo` without following the entry itself, or `undefined` when nothing is there. */\n\tasync #info(files, abs) {\n\t\ttry {\n\t\t\treturn await files.getInfo(abs);\n\t\t} catch (error) {\n\t\t\tif (this.errorKind(error) === \"not-found\") return void 0;\n\t\t\tthrow error;\n\t\t}\n\t}\n\tasync #assertParentDirectory(files, abs, path) {\n\t\tconst parent = await this.#info(files, posix.dirname(abs));\n\t\tif (!parent?.type) throw new DirectoryNotFoundError(posix.dirname(path));\n\t\tif (parent.type !== \"dir\") throw new NotDirectoryError(posix.dirname(path));\n\t}\n\tasync #write(files, abs, path, data) {\n\t\ttry {\n\t\t\tawait files.write(abs, data);\n\t\t} catch (error) {\n\t\t\tif (this.errorKind(error) === \"invalid-argument\") throw new IsDirectoryError(path);\n\t\t\tthrow error;\n\t\t}\n\t}\n\t/**\n\t* Upload the temp file `tmp`, then run the command that consumes it. The\n\t* script deletes it on every path it controls; if the upload or the command\n\t* fails part-way, delete it here so it doesn't linger in the user's tree.\n\t*/\n\tasync #publishTemp(tmp, operation, upload, script, subject, errors) {\n\t\ttry {\n\t\t\tawait this.runFiles(operation, upload);\n\t\t\tawait this.#command(operation, script, subject, errors);\n\t\t} catch (error) {\n\t\t\tawait this.runFiles(operation, (files) => files.remove(tmp)).catch(() => {});\n\t\t\tthrow error;\n\t\t}\n\t}\n\t/** Run a file command and turn its known exit codes into typed errors. */\n\tasync #command(operation, script, subject, errors) {\n\t\tconst result = await this.runCommand(operation, script);\n\t\tif (result.exitCode === 0) return;\n\t\tfor (const [outcome, code] of Object.entries(FILE_COMMAND_EXIT)) {\n\t\t\tconst toError = errors[outcome];\n\t\t\tif (code === result.exitCode && toError) throw toError();\n\t\t}\n\t\tthrow new Error(`${operation} ${subject} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n\t}\n};\n//#endregion\n//#region src/commands-api-process-manager.ts\n/**\n* Attaching sets no deadline of its own: without one the SDK cuts the stream\n* after 60s, and the process's own timeout was set when it was spawned.\n*/\nconst ATTACH_TIMEOUT_MS = 0;\n/**\n* Output callbacks for `run`/`connect` that hold output until the handle\n* exists. The SDK's handle reads events from construction, before `run()`\n* returns, so output that arrives with the start event reaches the\n* callbacks first.\n*/\nfunction outputUntilHandled() {\n\tlet handle;\n\tconst early = [];\n\treturn {\n\t\tonStdout: (data) => {\n\t\t\tif (handle) handle.emitStdout(data);\n\t\t\telse early.push({\n\t\t\t\tstream: \"stdout\",\n\t\t\t\tdata\n\t\t\t});\n\t\t},\n\t\tonStderr: (data) => {\n\t\t\tif (handle) handle.emitStderr(data);\n\t\t\telse early.push({\n\t\t\t\tstream: \"stderr\",\n\t\t\t\tdata\n\t\t\t});\n\t\t},\n\t\thandledBy(created) {\n\t\t\tfor (const { stream, data } of early) if (stream === \"stdout\") created.emitStdout(data);\n\t\t\telse created.emitStderr(data);\n\t\t\tearly.length = 0;\n\t\t\thandle = created;\n\t\t\treturn created;\n\t\t}\n\t};\n}\n/** envd runs every command as `/bin/bash -l -c <command>`; show the command itself. */\nfunction commandOf(proc) {\n\tconst [flag, c, command] = proc.args;\n\tif (proc.cmd === \"/bin/bash\" && flag === \"-l\" && c === \"-c\" && command !== void 0 && proc.args.length === 3) return command;\n\treturn [proc.cmd, ...proc.args].join(\" \");\n}\n/**\n* A process started or attached through a {@link CommandsApi}. `kill` and\n* `sendStdin` go back through the manager, so they use whatever commands\n* handle the provider currently supplies.\n*/\nvar CommandsApiProcessHandle = class extends ProcessHandle {\n\tinner;\n\tmanager;\n\tstartTime;\n\tstdinMode;\n\tpid;\n\t/** The exit code `wait()` saw, for a handle that doesn't update its own. */\n\twaitedExitCode;\n\tconstructor(inner, manager, startTime, stdinMode, options) {\n\t\tsuper(options);\n\t\tthis.inner = inner;\n\t\tthis.manager = manager;\n\t\tthis.startTime = startTime;\n\t\tthis.stdinMode = stdinMode;\n\t\tthis.pid = String(inner.pid);\n\t}\n\t/** Read from the commands handle, so it reflects server-side state without `wait()`. */\n\tget exitCode() {\n\t\treturn this.inner.exitCode ?? this.waitedExitCode;\n\t}\n\t/** Wait for the process to exit. Providers whose output stream can drop while the process runs override this. */\n\twaitForExit() {\n\t\treturn this.inner.wait();\n\t}\n\tasync wait() {\n\t\ttry {\n\t\t\tconst result = await this.waitForExit();\n\t\t\tthis.waitedExitCode = result.exitCode;\n\t\t\treturn {\n\t\t\t\tsuccess: result.exitCode === 0,\n\t\t\t\texitCode: result.exitCode,\n\t\t\t\tstdout: this.stdout,\n\t\t\t\tstderr: this.stderr,\n\t\t\t\texecutionTimeMs: Date.now() - this.startTime\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconst errorObj = error;\n\t\t\tconst reportedExitCode = errorObj.result?.exitCode ?? errorObj.exitCode;\n\t\t\tif (reportedExitCode !== void 0) this.waitedExitCode = reportedExitCode;\n\t\t\tconst exitCode = reportedExitCode ?? this.exitCode ?? 1;\n\t\t\tconst attachedStdout = errorObj.result?.stdout || errorObj.stdout;\n\t\t\tconst attachedStderr = errorObj.result?.stderr || errorObj.stderr;\n\t\t\tif (!this.stdout && !this.stdoutTruncated && attachedStdout) this.emitStdout(attachedStdout);\n\t\t\tif (!this.stderr && !this.stderrTruncated && attachedStderr) this.emitStderr(attachedStderr);\n\t\t\tconst stdout = this.stdout;\n\t\t\tconst stderr = this.stderr;\n\t\t\tconst terminalError = errorObj.result?.error || errorObj.error || (error instanceof Error ? error.message : String(error));\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\texitCode,\n\t\t\t\tstdout,\n\t\t\t\tstderr: [stderr, terminalError && !stderr.includes(terminalError) ? `Error: ${terminalError}` : \"\"].filter(Boolean).join(\"\\n\"),\n\t\t\t\texecutionTimeMs: Date.now() - this.startTime\n\t\t\t};\n\t\t}\n\t}\n\tasync kill() {\n\t\tif (this.exitCode !== void 0) return false;\n\t\treturn this.manager.runCommands(\"kill\", (commands) => commands.kill(this.inner.pid));\n\t}\n\tasync sendStdin(data) {\n\t\tif (this.exitCode !== void 0) throw new Error(`Process ${this.pid} has already exited with code ${this.exitCode}`);\n\t\tif (this.stdinMode === \"ignore\") throw new Error(`Process ${this.pid} was not started with stdin support`);\n\t\tawait this.manager.runCommands(\"sendStdin\", (commands) => commands.sendStdin(this.inner.pid, data));\n\t}\n\tasync closeStdin() {\n\t\tthrow new UnsupportedStdinCloseError(\"The commands API does not expose a way to close stdin for a running command\");\n\t}\n};\n/**\n* A process manager over E2B's envd commands API. Providers supply the\n* commands handle through {@link runCommands}; everything else is shared.\n*/\nvar CommandsApiProcessManager = class extends SandboxProcessManager {\n\tdefaultTimeout;\n\tconstructor({ defaultTimeout, ...options } = {}) {\n\t\tsuper(options);\n\t\tthis.defaultTimeout = defaultTimeout;\n\t}\n\t/** Wrap a started or attached command. Providers override this to supply their own handle. */\n\tcreateHandle(inner, stdinMode, options) {\n\t\treturn new CommandsApiProcessHandle(inner, this, Date.now(), stdinMode, options);\n\t}\n\t/** Where a command runs when the caller gives no `cwd`. */\n\tdefaultCwd() {\n\t\treturn this.sandbox.workingDirectory;\n\t}\n\tasync spawn(command, options = {}) {\n\t\tconst envs = Object.fromEntries(Object.entries({ ...options.env }).filter((entry) => entry[1] !== void 0));\n\t\treturn this.runCommands(\"spawn\", async (commands) => {\n\t\t\tconst output = outputUntilHandled();\n\t\t\tconst inner = await commands.run(command, {\n\t\t\t\tbackground: true,\n\t\t\t\tstdin: options.stdinMode !== \"ignore\",\n\t\t\t\tcwd: options.cwd ?? this.defaultCwd(),\n\t\t\t\tenvs,\n\t\t\t\ttimeoutMs: options.timeout ?? this.defaultTimeout,\n\t\t\t\tonStdout: output.onStdout,\n\t\t\t\tonStderr: output.onStderr\n\t\t\t});\n\t\t\tconst handle = output.handledBy(this.createHandle(inner, options.stdinMode, options));\n\t\t\tthis._tracked.set(handle.pid, handle);\n\t\t\treturn handle;\n\t\t});\n\t}\n\t/**\n\t* Running processes as the VM reports them, plus exited processes this\n\t* manager still holds, so a finished command stays visible until it is released.\n\t*/\n\tasync list() {\n\t\tconst infos = (await this.runCommands(\"list\", (commands) => commands.list())).map((proc) => ({\n\t\t\tpid: String(proc.pid),\n\t\t\tcommand: commandOf(proc),\n\t\t\trunning: true\n\t\t}));\n\t\tconst listed = new Set(infos.map((info) => info.pid));\n\t\tfor (const [pid, handle] of this._tracked) {\n\t\t\tif (handle.exitCode === void 0 || listed.has(pid)) continue;\n\t\t\tinfos.push({\n\t\t\t\tpid,\n\t\t\t\tcommand: handle.command,\n\t\t\t\trunning: false,\n\t\t\t\texitCode: handle.exitCode\n\t\t\t});\n\t\t}\n\t\treturn infos;\n\t}\n\t/**\n\t* A tracked process, or one this manager didn't start, attached through\n\t* `connect`. Output from before attaching is not available.\n\t*/\n\tasync get(pid, _options) {\n\t\tconst tracked = this._tracked.get(pid);\n\t\tif (tracked) return tracked;\n\t\tconst numericPid = /^\\d+$/.test(pid) ? Number(pid) : void 0;\n\t\tif (numericPid === void 0) return void 0;\n\t\tconst output = outputUntilHandled();\n\t\ttry {\n\t\t\tconst inner = await this.runCommands(\"connect\", (commands) => commands.connect(numericPid, {\n\t\t\t\tonStdout: output.onStdout,\n\t\t\t\tonStderr: output.onStderr,\n\t\t\t\ttimeoutMs: 0\n\t\t\t}));\n\t\t\tconst handle = output.handledBy(this.createHandle(inner, void 0));\n\t\t\tthis._tracked.set(handle.pid, handle);\n\t\t\treturn handle;\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t}\n};\n//#endregion\nexport { ATTACH_TIMEOUT_MS, CommandsApiProcessHandle, CommandsApiProcessManager, FILE_COMMAND_EXIT, FilesApiFilesystem, SETUP_FAILED_MARKER_PATH, SETUP_MARKER_DIR, SETUP_MARKER_PATH, WORKSPACE_SETUP_MARKER_PATH, appendFromCommand, copyCommand, guardedSetupCommand, linkNoClobberCommand, moveNoClobberCommand, normalizeSetupCommands, repoCloneCommand, repoSetupMarkerPath, setupMarkerCommand, setupMarkerContent, shellQuote, siblingTempPath };\n\n//# sourceMappingURL=index.js.map"],"mappings":";;;;;AAMA,MAAM,mBAAmB;;;;;;;;;;;;;;AAczB,MAAM,oBAAoB;;;;;;;AAO1B,MAAM,8BAA8B;;;;;;;AAOpC,MAAM,2BAA2B;;AAEjC,SAAS,oBAAoB,SAAS;CACrC,OAAO,GAAG,iBAAiB,SAAS;AACrC;;AAEA,SAAS,uBAAuB,cAAc;CAC7C,QAAQ,iBAAiB,KAAK,IAAI,CAAC,IAAI,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAC,YAAY,EAAA,CAAG,QAAQ,YAAY,QAAQ,KAAK,MAAM,EAAE;AAC9I;;AAEA,SAAS,mBAAmB,cAAc;CACzC,OAAO,UAAU,WAAW,QAAQ,CAAC,CAAC,OAAO,uBAAuB,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,KAAK;AAC3G;;;;;;;;AA4CA,MAAM,oBAAoB;CACzB,UAAU;CACV,aAAa;CACb,QAAQ;CACR,cAAc;CACd,iBAAiB;AAClB;AAKA,MAAM,EAAE,UAAU,aAAa,QAAQ,cAAc,oBAAoB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../../../../packages/_internals/workspace/dist/index.js"],"sourcesContent":["import { createHash } from \"crypto\";\nimport { posix } from \"path\";\nimport { Buffer } from \"buffer\";\nimport { DirectoryNotEmptyError, DirectoryNotFoundError, FileExistsError, FileNotFoundError, IsDirectoryError, MastraFilesystem, NotDirectoryError, ProcessHandle, SandboxProcessManager, UnsupportedStdinCloseError, getMimeType } from \"@mastra/core/workspace\";\n// src/setup-marker.ts\n/** Directory, relative to the template's build cwd, that holds every marker below. */\nconst SETUP_MARKER_DIR = \".mastra-sandbox\";\n/**\n* Setup completion marker shared by repo templates and their consumers.\n*\n* A repo template writes this file beside the checkout as its last build\n* step, so it exists only in images where every setup command succeeded. Its\n* content is a digest of the setup commands the image ran, letting a sandbox\n* booted from the image tell whether the setup it is about to run already\n* happened. Relative to the template's build cwd, which is also the runtime\n* working directory the repo was cloned into.\n*\n* This is the single-repository marker. Templates built from a repository\n* list write one marker per repository instead ({@link repoSetupMarkerPath}).\n*/\nconst SETUP_MARKER_PATH = \".mastra-sandbox/setup\";\n/**\n* Marker for the workspace-level setup of a multi-repository template: the\n* commands that ran at the working directory after every repository was set\n* up. Written even when there were no such commands, so a consumer can tell\n* \"ran nothing\" from \"never built\".\n*/\nconst WORKSPACE_SETUP_MARKER_PATH = \".mastra-sandbox/workspace-setup\";\n/**\n* One line per repository directory whose setup failed while the build was\n* told to continue. Absent when nothing failed. Consumers check this before\n* trusting a per-repository marker: that marker means every step ran, not\n* that every step succeeded.\n*/\nconst SETUP_FAILED_MARKER_PATH = \".mastra-sandbox/setup-failed\";\n/** Per-repository marker of a multi-repository template, keyed by the clone directory name. */\nfunction repoSetupMarkerPath(repoDir) {\n\treturn `${SETUP_MARKER_DIR}/repos/${repoDir}`;\n}\n/** Blank entries never become build steps, so they never count toward the digest either. */\nfunction normalizeSetupCommands(setupCommand) {\n\treturn (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== \"\");\n}\n/** The marker content for a setup command list: `sha256:<hex>` over the commands joined by newlines. */\nfunction setupMarkerContent(setupCommand) {\n\treturn `sha256:${createHash(\"sha256\").update(normalizeSetupCommands(setupCommand).join(\"\\n\")).digest(\"hex\")}`;\n}\n/** Shell step that writes the marker relative to the cwd. `content` is a digest, so it is shell-safe. */\nfunction setupMarkerCommand(content, markerPath = SETUP_MARKER_PATH) {\n\treturn `mkdir -p \"$(dirname \"${markerPath}\")\" && printf '%s' '${content}' > \"${markerPath}\"`;\n}\n/** A clone directory name is one path segment of word characters, dots and dashes, so it never needs quoting. */\nconst REPO_DIR_PATTERN = /^[\\w.-]+$/;\n/**\n* Shell step that runs one setup command inside a repository clone.\n*\n* Without the guard this is the plain `cd \"<dir>\" && <cmd>` step. With it,\n* the `cd` sits inside the parenthesized group so that the failure branch\n* runs back at the build cwd and the failure list lands at the workspace\n* level, never inside the repository.\n*/\nfunction guardedSetupCommand({ repoDir, command, continueOnFailure }) {\n\tif (!REPO_DIR_PATTERN.test(repoDir)) throw new TypeError(`Repository directory \"${repoDir}\" must match ${REPO_DIR_PATTERN}.`);\n\tif (!continueOnFailure) return `cd \"${repoDir}\" && ${command}`;\n\treturn `( cd \"${repoDir}\" && sh -c ${shellQuote$2(command)} ) || { mkdir -p \"${SETUP_MARKER_DIR}\" && grep -qxF -- '${repoDir}' \"${SETUP_FAILED_MARKER_PATH}\" 2>/dev/null || printf '%s\\\\n' '${repoDir}' >> \"${SETUP_FAILED_MARKER_PATH}\"; }`;\n}\nfunction shellQuote$2(value) {\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n// src/repo-clone.ts\nfunction repoCloneCommand({ cloneUrl, destination, branch, tokenEnv }) {\n\treturn `git ${tokenEnv ? `${gitAuthFlag(tokenEnv)} ` : \"\"}clone --depth=1 --single-branch ${branch ? `--branch ${shellQuote$1(branch)} ` : \"\"}${shellQuote$1(cloneUrl)} ${shellQuote$1(destination)}`;\n}\n/** Per-invocation auth header; `-c` config never reaches `.git/config`. */\nfunction gitAuthFlag(tokenEnv) {\n\treturn `-c http.extraheader=\"AUTHORIZATION: basic $(printf 'x-access-token:%s' \"$${tokenEnv}\" | base64 -w0)\"`;\n}\nfunction shellQuote$1(value) {\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n// src/file-commands.ts\n/**\n* Shell scripts for the file operations a sandbox can only do atomically (or\n* without moving file bytes over the network) by running a command inside the\n* VM. Each builder takes absolute paths and returns one POSIX `sh` script; the\n* caller runs it with `sh -c` and maps the exit codes below to typed errors.\n*/\n/** Exit codes the scripts use for outcomes the caller turns into typed errors. Any other non-zero code is a plain failure. */\nconst FILE_COMMAND_EXIT = {\n\tnotFound: 20,\n\tisDirectory: 21,\n\texists: 22,\n\tnotDirectory: 23,\n\tdestIsDirectory: 24\n};\n/** Single-quote a string for safe POSIX shell interpolation. */\nfunction shellQuote(value) {\n\treturn `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\nconst { notFound, isDirectory, exists, notDirectory, destIsDirectory } = FILE_COMMAND_EXIT;\n/** `[ -e ]` is false for a broken symlink, which still occupies the path. */\nconst present = (variable) => `{ [ -e \"$${variable}\" ] || [ -L \"$${variable}\" ]; }`;\n/**\n* A sibling temp path for `path`, so a later `ln` / `mv` onto `path` stays on\n* one filesystem. Hidden, and unique per call.\n*/\nfunction siblingTempPath(path) {\n\tconst suffix = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;\n\treturn posix.join(posix.dirname(path), `.${posix.basename(path)}.mastra-tmp-${suffix}`);\n}\n/**\n* Hard-link `$tmp` to `$dest` and delete `$tmp`, failing with `exists` when\n* something is at `$dest`. `ln` onto a directory (or a link to one) would\n* create the link inside it and succeed, so that case is undone and reported\n* as taken too.\n*/\nfunction linkTempIntoPlace() {\n\treturn [\n\t\t`ln \"$tmp\" \"$dest\" 2>/dev/null || { rm -f \"$tmp\"; ${present(\"dest\")} && exit ${exists}; exit 1; }`,\n\t\t`if [ -d \"$dest\" ]; then rm -f \"$dest/$(basename \"$tmp\")\" \"$tmp\"; exit ${exists}; fi`,\n\t\t`rm -f \"$tmp\"`\n\t];\n}\n/**\n* Copy `src` to `dest` inside the sandbox, creating `dest`'s parent.\n*\n* A file source copies its content (a symlinked source is followed). A\n* directory source with `recursive` copies its contents into `dest`, keeping\n* symlinks inside the tree as links. With `overwrite: false` the destination\n* is claimed atomically: a directory with an exclusive `mkdir`, a file by\n* copying to a temp name and hard-linking it into place (`link(2)` fails when\n* the destination exists).\n*\n* Exits: `notFound` (no source), `isDirectory` (directory source without\n* `recursive`), `destIsDirectory` (a file onto an existing directory or a link\n* to one), `exists`.\n*/\nfunction copyCommand(src, dest, options = {}) {\n\tconst noClobber = options.overwrite === false;\n\treturn [\n\t\t`src=${shellQuote(src)}`,\n\t\t`dest=${shellQuote(dest)}`,\n\t\t`[ -e \"$src\" ] || exit ${notFound}`,\n\t\t`mkdir -p \"$(dirname \"$dest\")\" || exit 1`,\n\t\t`if [ -d \"$src\" ]; then`,\n\t\toptions.recursive ? ` :` : ` exit ${isDirectory}`,\n\t\tnoClobber ? ` mkdir \"$dest\" 2>/dev/null || exit ${exists}` : ` mkdir -p \"$dest\" || exit 1`,\n\t\t` cp -R -P \"$src\"/. \"$dest\"/ || exit 1`,\n\t\t` exit 0`,\n\t\t`fi`,\n\t\tnoClobber ? `${present(\"dest\")} && exit ${exists}` : `if [ -d \"$dest\" ]; then exit ${destIsDirectory}; fi`,\n\t\t...noClobber ? [\n\t\t\t`tmp=${shellQuote(siblingTempPath(dest))}`,\n\t\t\t`cp \"$src\" \"$tmp\" || { rm -f \"$tmp\"; exit 1; }`,\n\t\t\t...linkTempIntoPlace()\n\t\t] : [`cp \"$src\" \"$dest\" || exit 1`]\n\t].join(\"\\n\");\n}\n/**\n* Move `src` to `dest` without replacing anything at `dest`, creating `dest`'s\n* parent. A skipped `mv -n` is detected by the source surviving, whatever\n* its exit code. If a directory appears at `dest` between the check and the\n* move, `mv` moves `src` into it (BSD `mv` has no `-T`), and that is reported\n* as success; the no-clobber rename itself is atomic.\n*\n* Exits: `notFound` (no source), `exists`.\n*/\nfunction moveNoClobberCommand(src, dest) {\n\treturn [\n\t\t`src=${shellQuote(src)}`,\n\t\t`dest=${shellQuote(dest)}`,\n\t\t`${present(\"src\")} || exit ${notFound}`,\n\t\t`${present(\"dest\")} && exit ${exists}`,\n\t\t`mkdir -p \"$(dirname \"$dest\")\" || exit 1`,\n\t\t`mv -n \"$src\" \"$dest\" 2>/dev/null || { ${present(\"src\")} && ${present(\"dest\")} && exit ${exists}; exit 1; }`,\n\t\t`${present(\"src\")} && exit ${exists}`,\n\t\t`exit 0`\n\t].join(\"\\n\");\n}\n/**\n* Append the bytes already written to `tmp` onto `dest`, then delete `tmp`.\n* The content travels as a file rather than a command argument, so it has no\n* size limit. Creates `dest` (and its parent) when missing.\n*\n* Exits: `isDirectory` (dest is a directory).\n*/\nfunction appendFromCommand(tmp, dest) {\n\treturn [\n\t\t`tmp=${shellQuote(tmp)}`,\n\t\t`dest=${shellQuote(dest)}`,\n\t\t`if [ -d \"$dest\" ]; then rm -f \"$tmp\"; exit ${isDirectory}; fi`,\n\t\t`mkdir -p \"$(dirname \"$dest\")\" && cat \"$tmp\" >> \"$dest\"`,\n\t\t`status=$?`,\n\t\t`rm -f \"$tmp\"`,\n\t\t`exit $status`\n\t].join(\"\\n\");\n}\n/**\n* Publish the file already written to `tmp` at `dest` only if nothing is\n* there, then delete `tmp`. `ln` (`link(2)`) is the atomic exclusivity check;\n* `tmp` must be on the same filesystem, e.g. from {@link siblingTempPath}.\n*\n* Exits: `exists`, `notDirectory` (dest's parent is not a directory).\n*/\nfunction linkNoClobberCommand(tmp, dest) {\n\treturn [\n\t\t`tmp=${shellQuote(tmp)}`,\n\t\t`dest=${shellQuote(dest)}`,\n\t\t`if [ ! -d \"$(dirname \"$dest\")\" ]; then rm -f \"$tmp\"; exit ${notDirectory}; fi`,\n\t\t`if ${present(\"dest\")}; then rm -f \"$tmp\"; exit ${exists}; fi`,\n\t\t...linkTempIntoPlace()\n\t].join(\"\\n\");\n}\n// src/files-api-filesystem.ts\n/** Default recursion depth for `readdir({ recursive: true })`, matching `LocalFilesystem`. */\nconst DEFAULT_MAX_DEPTH = 100;\nconst toAbsolute = (path) => posix.resolve(\"/\", path);\n/** The entry's own type: a symlink is a link even when it points at a directory. */\nconst isSymlink = (info) => info.symlinkTarget !== void 0;\nfunction toWriteData(content) {\n\tif (typeof content === \"string\") return content;\n\treturn content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength);\n}\nfunction matchesExtension(name, extension) {\n\tif (!extension) return true;\n\tconst ext = posix.extname(name);\n\treturn (Array.isArray(extension) ? extension : [extension]).some((e) => e === ext || e === ext.slice(1));\n}\n/**\n* A sandbox's disk, read and written through E2B's envd files API, with the\n* operations the files API can't do in one call (copy, append, no-clobber\n* writes and moves) run as one shell command inside the VM so they are atomic\n* and file bytes never round-trip through the host. Paths are absolute inside\n* the sandbox; this class does not confine paths or symlinks to any root.\n*\n* Providers supply how to reach the files API and the shell, and how to read\n* their SDK's errors.\n*\n* If a provider's `runCommand` re-runs a command whose first run already\n* finished (e.g. a retry after a dropped connection), the second run sees the\n* first's result: a no-clobber copy, write or move reports `FileExistsError`\n* or `FileNotFoundError`, and an append fails, even though the work was done.\n*/\nvar FilesApiFilesystem = class extends MastraFilesystem {\n\tasync readFile(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tconst bytes = await this.runFiles(\"readFile\", async (files) => {\n\t\t\ttry {\n\t\t\t\treturn await files.read(abs, { format: \"bytes\" });\n\t\t\t} catch (error) {\n\t\t\t\tconst kind = this.errorKind(error);\n\t\t\t\tif (kind === \"not-found\") throw new FileNotFoundError(path);\n\t\t\t\tif (kind === \"invalid-argument\") throw new IsDirectoryError(path);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t\tconst buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n\t\treturn options?.encoding ? buffer.toString(options.encoding) : buffer;\n\t}\n\tasync writeFile(path, content, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tif (options?.overwrite !== false) {\n\t\t\tawait this.runFiles(\"writeFile\", async (files) => {\n\t\t\t\tif (options?.recursive === false) await this.#assertParentDirectory(files, abs, path);\n\t\t\t\tawait this.#write(files, abs, path, toWriteData(content));\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tconst tmp = siblingTempPath(abs);\n\t\tconst upload = async (files) => {\n\t\t\tif (options.recursive === false) await this.#assertParentDirectory(files, abs, path);\n\t\t\tawait this.#write(files, tmp, path, toWriteData(content));\n\t\t};\n\t\tawait this.#publishTemp(tmp, \"writeFile\", upload, linkNoClobberCommand(tmp, abs), path, {\n\t\t\texists: () => new FileExistsError(path),\n\t\t\tnotDirectory: () => new NotDirectoryError(posix.dirname(path))\n\t\t});\n\t}\n\tasync appendFile(path, content) {\n\t\tconst abs = toAbsolute(path);\n\t\tconst tmp = siblingTempPath(abs);\n\t\tconst upload = (files) => this.#write(files, tmp, path, toWriteData(content));\n\t\tawait this.#publishTemp(tmp, \"appendFile\", upload, appendFromCommand(tmp, abs), path, { isDirectory: () => new IsDirectoryError(path) });\n\t}\n\tasync deleteFile(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tawait this.runFiles(\"deleteFile\", async (files) => {\n\t\t\tconst info = await this.#info(files, abs);\n\t\t\tif (!info) {\n\t\t\t\tif (options?.force) return;\n\t\t\t\tthrow new FileNotFoundError(path);\n\t\t\t}\n\t\t\tif (!isSymlink(info) && info.type === \"dir\") throw new IsDirectoryError(path);\n\t\t\tawait files.remove(abs);\n\t\t});\n\t}\n\t/**\n\t* One `cp` inside the VM. A recursive copy keeps symlinks inside the tree as\n\t* links; `overwrite: false` claims the destination atomically.\n\t*/\n\tasync copyFile(src, dest, options) {\n\t\tconst command = copyCommand(toAbsolute(src), toAbsolute(dest), {\n\t\t\trecursive: options?.recursive,\n\t\t\toverwrite: options?.overwrite\n\t\t});\n\t\tawait this.#command(\"copyFile\", command, `${src} -> ${dest}`, {\n\t\t\tnotFound: () => new FileNotFoundError(src),\n\t\t\tisDirectory: () => new IsDirectoryError(src),\n\t\t\tdestIsDirectory: () => new IsDirectoryError(dest),\n\t\t\texists: () => new FileExistsError(dest)\n\t\t});\n\t}\n\tasync moveFile(src, dest, options) {\n\t\tconst srcAbs = toAbsolute(src);\n\t\tconst destAbs = toAbsolute(dest);\n\t\tif (options?.overwrite === false) {\n\t\t\tawait this.#command(\"moveFile\", moveNoClobberCommand(srcAbs, destAbs), `${src} -> ${dest}`, {\n\t\t\t\tnotFound: () => new FileNotFoundError(src),\n\t\t\t\texists: () => new FileExistsError(dest)\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tawait this.runFiles(\"moveFile\", async (files) => {\n\t\t\ttry {\n\t\t\t\tawait files.rename(srcAbs, destAbs);\n\t\t\t} catch (error) {\n\t\t\t\tif (this.errorKind(error) === \"not-found\") throw new FileNotFoundError(src);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t}\n\tasync mkdir(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tawait this.runFiles(\"mkdir\", async (files) => {\n\t\t\tif (options?.recursive === false) await this.#assertParentDirectory(files, abs, path);\n\t\t\ttry {\n\t\t\t\tawait files.makeDir(abs);\n\t\t\t} catch (error) {\n\t\t\t\tif (this.errorKind(error) === \"invalid-argument\") throw new FileExistsError(path);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t}\n\tasync rmdir(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tawait this.runFiles(\"rmdir\", async (files) => {\n\t\t\tconst info = await this.#info(files, abs);\n\t\t\tif (!info) {\n\t\t\t\tif (options?.force) return;\n\t\t\t\tthrow new DirectoryNotFoundError(path);\n\t\t\t}\n\t\t\tif (!options?.recursive) {\n\t\t\t\tif (isSymlink(info) || info.type !== \"dir\") throw new NotDirectoryError(path);\n\t\t\t\tif ((await files.list(abs)).length > 0) throw new DirectoryNotEmptyError(path);\n\t\t\t} else if (!isSymlink(info) && info.type !== \"dir\") throw new NotDirectoryError(path);\n\t\t\tawait files.remove(abs);\n\t\t});\n\t}\n\tasync readdir(path, options) {\n\t\tconst abs = toAbsolute(path);\n\t\tconst depth = options?.recursive ? (options.maxDepth ?? DEFAULT_MAX_DEPTH) + 1 : 1;\n\t\tconst entries = await this.runFiles(\"readdir\", async (files) => {\n\t\t\ttry {\n\t\t\t\treturn await files.list(abs, { depth });\n\t\t\t} catch (error) {\n\t\t\t\tconst kind = this.errorKind(error);\n\t\t\t\tif (kind === \"not-found\") throw new DirectoryNotFoundError(path);\n\t\t\t\tif (kind === \"invalid-argument\") throw new NotDirectoryError(path);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t\tconst result = [];\n\t\tfor (const entry of entries) {\n\t\t\tif (!entry.type) continue;\n\t\t\tconst name = posix.relative(abs, entry.path);\n\t\t\tconst type = entry.type === \"dir\" ? \"directory\" : \"file\";\n\t\t\tif (type === \"file\" && !matchesExtension(name, options?.extension)) continue;\n\t\t\tconst link = isSymlink(entry);\n\t\t\tresult.push({\n\t\t\t\tname,\n\t\t\t\ttype,\n\t\t\t\t...type === \"file\" && !link && { size: entry.size },\n\t\t\t\t...link && {\n\t\t\t\t\tisSymlink: true,\n\t\t\t\t\tsymlinkTarget: entry.symlinkTarget\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t}\n\tasync exists(path) {\n\t\tconst abs = toAbsolute(path);\n\t\treturn this.runFiles(\"exists\", async (files) => Boolean((await this.#info(files, abs))?.type));\n\t}\n\tasync stat(path) {\n\t\tconst abs = toAbsolute(path);\n\t\treturn this.runFiles(\"stat\", async (files) => {\n\t\t\tlet info = await this.#info(files, abs);\n\t\t\tif (info?.type && isSymlink(info)) info = await this.#info(files, posix.resolve(posix.dirname(abs), info.symlinkTarget));\n\t\t\tif (!info?.type) throw new FileNotFoundError(path);\n\t\t\tconst modifiedAt = info.modifiedTime ?? /* @__PURE__ */ new Date(0);\n\t\t\treturn {\n\t\t\t\tname: posix.basename(abs),\n\t\t\t\tpath: abs,\n\t\t\t\ttype: info.type === \"dir\" ? \"directory\" : \"file\",\n\t\t\t\tsize: info.type === \"dir\" ? 0 : info.size,\n\t\t\t\tcreatedAt: modifiedAt,\n\t\t\t\tmodifiedAt,\n\t\t\t\t...info.type !== \"dir\" && { mimeType: getMimeType(abs) }\n\t\t\t};\n\t\t});\n\t}\n\t/** `getInfo` without following the entry itself, or `undefined` when nothing is there. */\n\tasync #info(files, abs) {\n\t\ttry {\n\t\t\treturn await files.getInfo(abs);\n\t\t} catch (error) {\n\t\t\tif (this.errorKind(error) === \"not-found\") return void 0;\n\t\t\tthrow error;\n\t\t}\n\t}\n\tasync #assertParentDirectory(files, abs, path) {\n\t\tconst parent = await this.#info(files, posix.dirname(abs));\n\t\tif (!parent?.type) throw new DirectoryNotFoundError(posix.dirname(path));\n\t\tif (parent.type !== \"dir\") throw new NotDirectoryError(posix.dirname(path));\n\t}\n\tasync #write(files, abs, path, data) {\n\t\ttry {\n\t\t\tawait files.write(abs, data);\n\t\t} catch (error) {\n\t\t\tif (this.errorKind(error) === \"invalid-argument\") throw new IsDirectoryError(path);\n\t\t\tthrow error;\n\t\t}\n\t}\n\t/**\n\t* Upload the temp file `tmp`, then run the command that consumes it. The\n\t* script deletes it on every path it controls; if the upload or the command\n\t* fails part-way, delete it here so it doesn't linger in the user's tree.\n\t*/\n\tasync #publishTemp(tmp, operation, upload, script, subject, errors) {\n\t\ttry {\n\t\t\tawait this.runFiles(operation, upload);\n\t\t\tawait this.#command(operation, script, subject, errors);\n\t\t} catch (error) {\n\t\t\tawait this.runFiles(operation, (files) => files.remove(tmp)).catch(() => {});\n\t\t\tthrow error;\n\t\t}\n\t}\n\t/** Run a file command and turn its known exit codes into typed errors. */\n\tasync #command(operation, script, subject, errors) {\n\t\tconst result = await this.runCommand(operation, script);\n\t\tif (result.exitCode === 0) return;\n\t\tfor (const [outcome, code] of Object.entries(FILE_COMMAND_EXIT)) {\n\t\t\tconst toError = errors[outcome];\n\t\t\tif (code === result.exitCode && toError) throw toError();\n\t\t}\n\t\tthrow new Error(`${operation} ${subject} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);\n\t}\n};\n// src/commands-api-process-manager.ts\n/**\n* Attaching sets no deadline of its own: without one the SDK cuts the stream\n* after 60s, and the process's own timeout was set when it was spawned.\n*/\nconst ATTACH_TIMEOUT_MS = 0;\n/**\n* Output callbacks for `run`/`connect` that hold output until the handle\n* exists. The SDK's handle reads events from construction, before `run()`\n* returns, so output that arrives with the start event reaches the\n* callbacks first.\n*/\nfunction outputUntilHandled() {\n\tlet handle;\n\tconst early = [];\n\treturn {\n\t\tonStdout: (data) => {\n\t\t\tif (handle) handle.emitStdout(data);\n\t\t\telse early.push({\n\t\t\t\tstream: \"stdout\",\n\t\t\t\tdata\n\t\t\t});\n\t\t},\n\t\tonStderr: (data) => {\n\t\t\tif (handle) handle.emitStderr(data);\n\t\t\telse early.push({\n\t\t\t\tstream: \"stderr\",\n\t\t\t\tdata\n\t\t\t});\n\t\t},\n\t\thandledBy(created) {\n\t\t\tfor (const { stream, data } of early) if (stream === \"stdout\") created.emitStdout(data);\n\t\t\telse created.emitStderr(data);\n\t\t\tearly.length = 0;\n\t\t\thandle = created;\n\t\t\treturn created;\n\t\t}\n\t};\n}\n/** envd runs every command as `/bin/bash -l -c <command>`; show the command itself. */\nfunction commandOf(proc) {\n\tconst [flag, c, command] = proc.args;\n\tif (proc.cmd === \"/bin/bash\" && flag === \"-l\" && c === \"-c\" && command !== void 0 && proc.args.length === 3) return command;\n\treturn [proc.cmd, ...proc.args].join(\" \");\n}\n/**\n* A process started or attached through a {@link CommandsApi}. `kill` and\n* `sendStdin` go back through the manager, so they use whatever commands\n* handle the provider currently supplies.\n*/\nvar CommandsApiProcessHandle = class extends ProcessHandle {\n\tinner;\n\tmanager;\n\tstartTime;\n\tstdinMode;\n\tpid;\n\t/** The exit code `wait()` saw, for a handle that doesn't update its own. */\n\twaitedExitCode;\n\tconstructor(inner, manager, startTime, stdinMode, options) {\n\t\tsuper(options);\n\t\tthis.inner = inner;\n\t\tthis.manager = manager;\n\t\tthis.startTime = startTime;\n\t\tthis.stdinMode = stdinMode;\n\t\tthis.pid = String(inner.pid);\n\t}\n\t/** Read from the commands handle, so it reflects server-side state without `wait()`. */\n\tget exitCode() {\n\t\treturn this.inner.exitCode ?? this.waitedExitCode;\n\t}\n\t/** Wait for the process to exit. Providers whose output stream can drop while the process runs override this. */\n\twaitForExit() {\n\t\treturn this.inner.wait();\n\t}\n\tasync wait() {\n\t\ttry {\n\t\t\tconst result = await this.waitForExit();\n\t\t\tthis.waitedExitCode = result.exitCode;\n\t\t\treturn {\n\t\t\t\tsuccess: result.exitCode === 0,\n\t\t\t\texitCode: result.exitCode,\n\t\t\t\tstdout: this.stdout,\n\t\t\t\tstderr: this.stderr,\n\t\t\t\texecutionTimeMs: Date.now() - this.startTime\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconst errorObj = error;\n\t\t\tconst reportedExitCode = errorObj.result?.exitCode ?? errorObj.exitCode;\n\t\t\tif (reportedExitCode !== void 0) this.waitedExitCode = reportedExitCode;\n\t\t\tconst exitCode = reportedExitCode ?? this.exitCode ?? 1;\n\t\t\tconst attachedStdout = errorObj.result?.stdout || errorObj.stdout;\n\t\t\tconst attachedStderr = errorObj.result?.stderr || errorObj.stderr;\n\t\t\tif (!this.stdout && !this.stdoutTruncated && attachedStdout) this.emitStdout(attachedStdout);\n\t\t\tif (!this.stderr && !this.stderrTruncated && attachedStderr) this.emitStderr(attachedStderr);\n\t\t\tconst stdout = this.stdout;\n\t\t\tconst stderr = this.stderr;\n\t\t\tconst terminalError = errorObj.result?.error || errorObj.error || (error instanceof Error ? error.message : String(error));\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\texitCode,\n\t\t\t\tstdout,\n\t\t\t\tstderr: [stderr, terminalError && !stderr.includes(terminalError) ? `Error: ${terminalError}` : \"\"].filter(Boolean).join(\"\\n\"),\n\t\t\t\texecutionTimeMs: Date.now() - this.startTime\n\t\t\t};\n\t\t}\n\t}\n\tasync kill() {\n\t\tif (this.exitCode !== void 0) return false;\n\t\treturn this.manager.runCommands(\"kill\", (commands) => commands.kill(this.inner.pid));\n\t}\n\tasync sendStdin(data) {\n\t\tif (this.exitCode !== void 0) throw new Error(`Process ${this.pid} has already exited with code ${this.exitCode}`);\n\t\tif (this.stdinMode === \"ignore\") throw new Error(`Process ${this.pid} was not started with stdin support`);\n\t\tawait this.manager.runCommands(\"sendStdin\", (commands) => commands.sendStdin(this.inner.pid, data));\n\t}\n\tasync closeStdin() {\n\t\tthrow new UnsupportedStdinCloseError(\"The commands API does not expose a way to close stdin for a running command\");\n\t}\n};\n/**\n* A process manager over E2B's envd commands API. Providers supply the\n* commands handle through {@link runCommands}; everything else is shared.\n*/\nvar CommandsApiProcessManager = class extends SandboxProcessManager {\n\tdefaultTimeout;\n\tconstructor({ defaultTimeout, ...options } = {}) {\n\t\tsuper(options);\n\t\tthis.defaultTimeout = defaultTimeout;\n\t}\n\t/** Wrap a started or attached command. Providers override this to supply their own handle. */\n\tcreateHandle(inner, stdinMode, options) {\n\t\treturn new CommandsApiProcessHandle(inner, this, Date.now(), stdinMode, options);\n\t}\n\t/** Where a command runs when the caller gives no `cwd`. */\n\tdefaultCwd() {\n\t\treturn this.sandbox.workingDirectory;\n\t}\n\tasync spawn(command, options = {}) {\n\t\tconst envs = Object.fromEntries(Object.entries({ ...options.env }).filter((entry) => entry[1] !== void 0));\n\t\treturn this.runCommands(\"spawn\", async (commands) => {\n\t\t\tconst output = outputUntilHandled();\n\t\t\tconst inner = await commands.run(command, {\n\t\t\t\tbackground: true,\n\t\t\t\tstdin: options.stdinMode !== \"ignore\",\n\t\t\t\tcwd: options.cwd ?? this.defaultCwd(),\n\t\t\t\tenvs,\n\t\t\t\ttimeoutMs: options.timeout ?? this.defaultTimeout,\n\t\t\t\tonStdout: output.onStdout,\n\t\t\t\tonStderr: output.onStderr\n\t\t\t});\n\t\t\tconst handle = output.handledBy(this.createHandle(inner, options.stdinMode, options));\n\t\t\tthis._tracked.set(handle.pid, handle);\n\t\t\treturn handle;\n\t\t});\n\t}\n\t/**\n\t* Running processes as the VM reports them, plus exited processes this\n\t* manager still holds, so a finished command stays visible until it is released.\n\t*/\n\tasync list() {\n\t\tconst infos = (await this.runCommands(\"list\", (commands) => commands.list())).map((proc) => ({\n\t\t\tpid: String(proc.pid),\n\t\t\tcommand: commandOf(proc),\n\t\t\trunning: true\n\t\t}));\n\t\tconst listed = new Set(infos.map((info) => info.pid));\n\t\tfor (const [pid, handle] of this._tracked) {\n\t\t\tif (handle.exitCode === void 0 || listed.has(pid)) continue;\n\t\t\tinfos.push({\n\t\t\t\tpid,\n\t\t\t\tcommand: handle.command,\n\t\t\t\trunning: false,\n\t\t\t\texitCode: handle.exitCode\n\t\t\t});\n\t\t}\n\t\treturn infos;\n\t}\n\t/**\n\t* A tracked process, or one this manager didn't start, attached through\n\t* `connect`. Output from before attaching is not available.\n\t*/\n\tasync get(pid, _options) {\n\t\tconst tracked = this._tracked.get(pid);\n\t\tif (tracked) return tracked;\n\t\tconst numericPid = /^\\d+$/.test(pid) ? Number(pid) : void 0;\n\t\tif (numericPid === void 0) return void 0;\n\t\tconst output = outputUntilHandled();\n\t\ttry {\n\t\t\tconst inner = await this.runCommands(\"connect\", (commands) => commands.connect(numericPid, {\n\t\t\t\tonStdout: output.onStdout,\n\t\t\t\tonStderr: output.onStderr,\n\t\t\t\ttimeoutMs: 0\n\t\t\t}));\n\t\t\tconst handle = output.handledBy(this.createHandle(inner, void 0));\n\t\t\tthis._tracked.set(handle.pid, handle);\n\t\t\treturn handle;\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t}\n};\nexport { ATTACH_TIMEOUT_MS, CommandsApiProcessHandle, CommandsApiProcessManager, FILE_COMMAND_EXIT, FilesApiFilesystem, SETUP_FAILED_MARKER_PATH, SETUP_MARKER_DIR, SETUP_MARKER_PATH, WORKSPACE_SETUP_MARKER_PATH, appendFromCommand, copyCommand, guardedSetupCommand, linkNoClobberCommand, moveNoClobberCommand, normalizeSetupCommands, repoCloneCommand, repoSetupMarkerPath, setupMarkerCommand, setupMarkerContent, shellQuote, siblingTempPath };\n\n//# sourceMappingURL=index.js.map"],"mappings":";;;;;AAMA,MAAM,mBAAmB;;;;;;;;;;;;;;AAczB,MAAM,oBAAoB;;;;;;;AAO1B,MAAM,8BAA8B;;;;;;;AAOpC,MAAM,2BAA2B;;AAEjC,SAAS,oBAAoB,SAAS;CACrC,OAAO,GAAG,iBAAiB,SAAS;AACrC;;AAEA,SAAS,uBAAuB,cAAc;CAC7C,QAAQ,iBAAiB,KAAK,IAAI,CAAC,IAAI,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAC,YAAY,EAAA,CAAG,QAAQ,YAAY,QAAQ,KAAK,MAAM,EAAE;AAC9I;;AAEA,SAAS,mBAAmB,cAAc;CACzC,OAAO,UAAU,WAAW,QAAQ,CAAC,CAAC,OAAO,uBAAuB,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,KAAK;AAC3G;;;;;;;;AA0CA,MAAM,oBAAoB;CACzB,UAAU;CACV,aAAa;CACb,QAAQ;CACR,cAAc;CACd,iBAAiB;AAClB;AAKA,MAAM,EAAE,UAAU,aAAa,QAAQ,cAAc,oBAAoB"}