@mastra/factory 0.24.0-alpha.9 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +4 -4
  2. package/dist/environment/build-runner.js +2 -1
  3. package/dist/environment/build-runner.js.map +1 -1
  4. package/dist/environment/build-workflow.js +1 -1
  5. package/dist/environment/build-workflow.js.map +1 -1
  6. package/dist/environment/build.js +16 -14
  7. package/dist/environment/build.js.map +1 -1
  8. package/dist/factory.d.ts.map +1 -1
  9. package/dist/factory.js +111 -19
  10. package/dist/factory.js.map +1 -1
  11. package/dist/integrations/base.d.ts +1 -1
  12. package/dist/integrations/base.d.ts.map +1 -1
  13. package/dist/integrations/github/app-identity.js.map +1 -1
  14. package/dist/integrations/github/integration.js +1 -1
  15. package/dist/integrations/github/integration.js.map +1 -1
  16. package/dist/integrations/github/reconcile-worker.js +1 -1
  17. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  18. package/dist/integrations/github/routes.d.ts +1 -1
  19. package/dist/integrations/github/routes.d.ts.map +1 -1
  20. package/dist/integrations/github/routes.js +24 -13
  21. package/dist/integrations/github/routes.js.map +1 -1
  22. package/dist/integrations/github/rules.d.ts.map +1 -1
  23. package/dist/integrations/github/rules.js +53 -9
  24. package/dist/integrations/github/rules.js.map +1 -1
  25. package/dist/integrations/github/sandbox.js +2 -2
  26. package/dist/integrations/github/sandbox.js.map +1 -1
  27. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  28. package/dist/integrations/github/session-subscriptions.js +1 -2
  29. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  30. package/dist/integrations/github/subscriptions.d.ts +7 -4
  31. package/dist/integrations/github/subscriptions.d.ts.map +1 -1
  32. package/dist/integrations/github/subscriptions.js +5 -5
  33. package/dist/integrations/github/subscriptions.js.map +1 -1
  34. package/dist/integrations/gitlab/api.js.map +1 -1
  35. package/dist/integrations/gitlab/integration.js.map +1 -1
  36. package/dist/integrations/gitlab/reconciliation-config.js +2 -1
  37. package/dist/integrations/gitlab/reconciliation-config.js.map +1 -1
  38. package/dist/integrations/gitlab/routes.d.ts.map +1 -1
  39. package/dist/integrations/gitlab/routes.js +10 -6
  40. package/dist/integrations/gitlab/routes.js.map +1 -1
  41. package/dist/integrations/gitlab/rules.js.map +1 -1
  42. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
  43. package/dist/integrations/gitlab/session-subscriptions.js +1 -2
  44. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
  45. package/dist/integrations/gitlab/subscriptions.d.ts +7 -4
  46. package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -1
  47. package/dist/integrations/gitlab/subscriptions.js +5 -5
  48. package/dist/integrations/gitlab/subscriptions.js.map +1 -1
  49. package/dist/integrations/incidentio/api.js.map +1 -1
  50. package/dist/integrations/incidentio/integration.js.map +1 -1
  51. package/dist/integrations/incidentio/rules.js.map +1 -1
  52. package/dist/integrations/issue-reconcile-worker.js +1 -1
  53. package/dist/integrations/issue-reconcile-worker.js.map +1 -1
  54. package/dist/integrations/issue-reconciler.js +1 -1
  55. package/dist/integrations/issue-reconciler.js.map +1 -1
  56. package/dist/integrations/jira/api.js.map +1 -1
  57. package/dist/integrations/jira/integration.js.map +1 -1
  58. package/dist/integrations/jira/rules.js.map +1 -1
  59. package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
  60. package/dist/integrations/linear/agent-tools.js.map +1 -1
  61. package/dist/integrations/linear/integration.js.map +1 -1
  62. package/dist/integrations/linear/rules.js.map +1 -1
  63. package/dist/integrations/platform/api-client.js.map +1 -1
  64. package/dist/integrations/platform/github/event-worker.js +1 -1
  65. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  66. package/dist/integrations/platform/github/integration.js +2 -2
  67. package/dist/integrations/platform/github/integration.js.map +1 -1
  68. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
  69. package/dist/integrations/platform/gitlab/integration.js.map +1 -1
  70. package/dist/integrations/platform/incidentio/integration.js +2 -1
  71. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  72. package/dist/integrations/platform/jira/integration.js.map +1 -1
  73. package/dist/integrations/platform/linear/event-worker.js +1 -1
  74. package/dist/integrations/platform/linear/event-worker.js.map +1 -1
  75. package/dist/integrations/platform/linear/integration.js.map +1 -1
  76. package/dist/integrations/slack/connect-route.js +1 -1
  77. package/dist/integrations/slack/connect-route.js.map +1 -1
  78. package/dist/integrations/slack/feed-publisher.js +1 -1
  79. package/dist/integrations/slack/feed-publisher.js.map +1 -1
  80. package/dist/integrations/slack/integration.js +1 -1
  81. package/dist/integrations/slack/integration.js.map +1 -1
  82. package/dist/integrations/slack/slack.d.ts +16 -5
  83. package/dist/integrations/slack/slack.d.ts.map +1 -1
  84. package/dist/integrations/slack/slack.js +87 -34
  85. package/dist/integrations/slack/slack.js.map +1 -1
  86. package/dist/integrations/subscription-session.d.ts +0 -1
  87. package/dist/integrations/subscription-session.d.ts.map +1 -1
  88. package/dist/integrations/subscription-session.js +1 -4
  89. package/dist/integrations/subscription-session.js.map +1 -1
  90. package/dist/integrations/workos/integration.js.map +1 -1
  91. package/dist/mcp/routes.d.ts.map +1 -1
  92. package/dist/mcp/routes.js +1 -1
  93. package/dist/mcp/routes.js.map +1 -1
  94. package/dist/packages/_internals/workspace/dist/index.js +18 -1
  95. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  96. package/dist/routes/attention-activity.js.map +1 -1
  97. package/dist/routes/attention-parked.js.map +1 -1
  98. package/dist/routes/attention-providers.js.map +1 -1
  99. package/dist/routes/automation-runs.js.map +1 -1
  100. package/dist/routes/config.d.ts +6 -7
  101. package/dist/routes/config.d.ts.map +1 -1
  102. package/dist/routes/config.js +20 -19
  103. package/dist/routes/config.js.map +1 -1
  104. package/dist/routes/contracts.js +1 -1
  105. package/dist/routes/contracts.js.map +1 -1
  106. package/dist/routes/custom-provider-source.js.map +1 -1
  107. package/dist/routes/fs.d.ts +31 -23
  108. package/dist/routes/fs.d.ts.map +1 -1
  109. package/dist/routes/fs.js +218 -64
  110. package/dist/routes/fs.js.map +1 -1
  111. package/dist/routes/intake.js +2 -1
  112. package/dist/routes/intake.js.map +1 -1
  113. package/dist/routes/knowledge.js +1 -1
  114. package/dist/routes/knowledge.js.map +1 -1
  115. package/dist/routes/oauth.js +3 -2
  116. package/dist/routes/oauth.js.map +1 -1
  117. package/dist/routes/projects.d.ts +1 -1
  118. package/dist/routes/projects.d.ts.map +1 -1
  119. package/dist/routes/projects.js +33 -31
  120. package/dist/routes/projects.js.map +1 -1
  121. package/dist/routes/provider-credentials.d.ts.map +1 -1
  122. package/dist/routes/provider-credentials.js +6 -1
  123. package/dist/routes/provider-credentials.js.map +1 -1
  124. package/dist/routes/session-access.d.ts +55 -0
  125. package/dist/routes/session-access.d.ts.map +1 -0
  126. package/dist/routes/session-access.js +165 -0
  127. package/dist/routes/session-access.js.map +1 -0
  128. package/dist/routes/skills.d.ts +2 -4
  129. package/dist/routes/skills.d.ts.map +1 -1
  130. package/dist/routes/skills.js +19 -65
  131. package/dist/routes/skills.js.map +1 -1
  132. package/dist/routes/source-control-sessions.d.ts +6 -1
  133. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  134. package/dist/routes/source-control-sessions.js +95 -12
  135. package/dist/routes/source-control-sessions.js.map +1 -1
  136. package/dist/routes/surface.d.ts +5 -0
  137. package/dist/routes/surface.d.ts.map +1 -1
  138. package/dist/routes/surface.js +10 -4
  139. package/dist/routes/surface.js.map +1 -1
  140. package/dist/routes/tenant-credentials.js.map +1 -1
  141. package/dist/routes/work-items.js.map +1 -1
  142. package/dist/rules/binding-context.js +11 -9
  143. package/dist/rules/binding-context.js.map +1 -1
  144. package/dist/rules/dispatcher.d.ts +1 -1
  145. package/dist/rules/dispatcher.d.ts.map +1 -1
  146. package/dist/rules/dispatcher.js +10 -10
  147. package/dist/rules/dispatcher.js.map +1 -1
  148. package/dist/rules/start-coordinator.d.ts +4 -0
  149. package/dist/rules/start-coordinator.d.ts.map +1 -1
  150. package/dist/rules/start-coordinator.js +36 -4
  151. package/dist/rules/start-coordinator.js.map +1 -1
  152. package/dist/rules/tools.js +2 -2
  153. package/dist/rules/tools.js.map +1 -1
  154. package/dist/rules/transition-service.js.map +1 -1
  155. package/dist/sandbox/callback-factory-sandbox.js.map +1 -1
  156. package/dist/sandbox/local-factory-sandbox.js.map +1 -1
  157. package/dist/sandbox/session-retirement.js.map +1 -1
  158. package/dist/secret-encryption.d.ts.map +1 -1
  159. package/dist/secret-encryption.js +65 -25
  160. package/dist/secret-encryption.js.map +1 -1
  161. package/dist/session/channel-reply-processor.d.ts +7 -6
  162. package/dist/session/channel-reply-processor.d.ts.map +1 -1
  163. package/dist/session/channel-reply-processor.js +9 -9
  164. package/dist/session/channel-reply-processor.js.map +1 -1
  165. package/dist/session/channel-session-link-processor.js +1 -0
  166. package/dist/session/channel-session-link-processor.js.map +1 -1
  167. package/dist/session/credential-policy.d.ts.map +1 -1
  168. package/dist/session/credential-policy.js +7 -6
  169. package/dist/session/credential-policy.js.map +1 -1
  170. package/dist/session/default-model-hydration.d.ts +1 -0
  171. package/dist/session/default-model-hydration.d.ts.map +1 -1
  172. package/dist/session/default-model-hydration.js +3 -2
  173. package/dist/session/default-model-hydration.js.map +1 -1
  174. package/dist/session/environment-state-processor.js +1 -1
  175. package/dist/session/environment-state-processor.js.map +1 -1
  176. package/dist/session/filesystem-capture.d.ts +3 -3
  177. package/dist/session/filesystem-capture.d.ts.map +1 -1
  178. package/dist/session/filesystem-capture.js +16 -17
  179. package/dist/session/filesystem-capture.js.map +1 -1
  180. package/dist/session/first-exec-capture.d.ts +1 -1
  181. package/dist/session/first-exec-capture.d.ts.map +1 -1
  182. package/dist/session/first-exec-capture.js +1 -1
  183. package/dist/session/first-exec-capture.js.map +1 -1
  184. package/dist/session/first-message-capture.d.ts +1 -1
  185. package/dist/session/first-message-capture.d.ts.map +1 -1
  186. package/dist/session/first-message-capture.js +1 -1
  187. package/dist/session/first-message-capture.js.map +1 -1
  188. package/dist/session/live-sessions.d.ts +1 -1
  189. package/dist/session/live-sessions.js +1 -1
  190. package/dist/session/live-sessions.js.map +1 -1
  191. package/dist/session/memory-settings-hydration.d.ts +1 -1
  192. package/dist/session/memory-settings-hydration.d.ts.map +1 -1
  193. package/dist/session/memory-settings-hydration.js +2 -1
  194. package/dist/session/memory-settings-hydration.js.map +1 -1
  195. package/dist/session/resource-migration.d.ts +105 -0
  196. package/dist/session/resource-migration.d.ts.map +1 -0
  197. package/dist/session/resource-migration.js +195 -0
  198. package/dist/session/resource-migration.js.map +1 -0
  199. package/dist/session/session-threads.d.ts +58 -0
  200. package/dist/session/session-threads.d.ts.map +1 -0
  201. package/dist/session/session-threads.js +90 -0
  202. package/dist/session/session-threads.js.map +1 -0
  203. package/dist/session/source-control-tools.js +8 -8
  204. package/dist/session/source-control-tools.js.map +1 -1
  205. package/dist/session/thinking-default.d.ts.map +1 -1
  206. package/dist/session/thinking-default.js +3 -4
  207. package/dist/session/thinking-default.js.map +1 -1
  208. package/dist/session/thread-session-stamp.d.ts +21 -0
  209. package/dist/session/thread-session-stamp.d.ts.map +1 -0
  210. package/dist/session/thread-session-stamp.js +20 -0
  211. package/dist/session/thread-session-stamp.js.map +1 -0
  212. package/dist/session/thread-title-mirror.d.ts +1 -1
  213. package/dist/session/thread-title-mirror.d.ts.map +1 -1
  214. package/dist/session/thread-title-mirror.js +1 -1
  215. package/dist/session/thread-title-mirror.js.map +1 -1
  216. package/dist/skills/service.d.ts +6 -8
  217. package/dist/skills/service.d.ts.map +1 -1
  218. package/dist/skills/service.js +3 -3
  219. package/dist/skills/service.js.map +1 -1
  220. package/dist/spa-static.js.map +1 -1
  221. package/dist/state-signing.js +1 -1
  222. package/dist/state-signing.js.map +1 -1
  223. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
  224. package/dist/storage/domains/audit/agent-audit.js +2 -2
  225. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  226. package/dist/storage/domains/audit/base.js.map +1 -1
  227. package/dist/storage/domains/audit/domain.js.map +1 -1
  228. package/dist/storage/domains/channel-identity/base.js.map +1 -1
  229. package/dist/storage/domains/comments/base.js.map +1 -1
  230. package/dist/storage/domains/comments/domain.js.map +1 -1
  231. package/dist/storage/domains/comments/feed-context.js.map +1 -1
  232. package/dist/storage/domains/credentials/base.js.map +1 -1
  233. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  234. package/dist/storage/domains/intake/base.js.map +1 -1
  235. package/dist/storage/domains/integrations/base.d.ts +8 -0
  236. package/dist/storage/domains/integrations/base.d.ts.map +1 -1
  237. package/dist/storage/domains/integrations/base.js +7 -0
  238. package/dist/storage/domains/integrations/base.js.map +1 -1
  239. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  240. package/dist/storage/domains/model-defaults/base.js.map +1 -1
  241. package/dist/storage/domains/projects/base.js.map +1 -1
  242. package/dist/storage/domains/queue-health/base.js.map +1 -1
  243. package/dist/storage/domains/source-control/base.d.ts +19 -0
  244. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  245. package/dist/storage/domains/source-control/base.js +42 -0
  246. package/dist/storage/domains/source-control/base.js.map +1 -1
  247. package/dist/storage/domains/source-control/inmemory.d.ts +5 -1
  248. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  249. package/dist/storage/domains/source-control/inmemory.js +7 -0
  250. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  251. package/dist/storage/domains/work-items/base.d.ts +14 -0
  252. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  253. package/dist/storage/domains/work-items/base.js +25 -1
  254. package/dist/storage/domains/work-items/base.js.map +1 -1
  255. package/dist/storage/domains/work-items/board-version.js.map +1 -1
  256. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  257. package/dist/tools/approval-tools.js +1 -1
  258. package/dist/tools/approval-tools.js.map +1 -1
  259. package/dist/tools/deliver.d.ts +8 -5
  260. package/dist/tools/deliver.d.ts.map +1 -1
  261. package/dist/tools/deliver.js +22 -8
  262. package/dist/tools/deliver.js.map +1 -1
  263. package/dist/tools/messaging-tools.d.ts +1 -1
  264. package/dist/tools/messaging-tools.d.ts.map +1 -1
  265. package/dist/tools/messaging-tools.js +203 -72
  266. package/dist/tools/messaging-tools.js.map +1 -1
  267. package/dist/tools/permissions.d.ts +1 -1
  268. package/dist/tools/permissions.d.ts.map +1 -1
  269. package/dist/tools/permissions.js +1 -1
  270. package/dist/tools/permissions.js.map +1 -1
  271. package/dist/tools/reports.d.ts.map +1 -1
  272. package/dist/tools/reports.js +10 -3
  273. package/dist/tools/reports.js.map +1 -1
  274. package/dist/tools/run-watch.d.ts +2 -0
  275. package/dist/tools/run-watch.d.ts.map +1 -1
  276. package/dist/tools/run-watch.js +42 -2
  277. package/dist/tools/run-watch.js.map +1 -1
  278. package/dist/tools/scope.d.ts +2 -1
  279. package/dist/tools/scope.d.ts.map +1 -1
  280. package/dist/tools/scope.js +3 -2
  281. package/dist/tools/scope.js.map +1 -1
  282. package/dist/tools/session-tools.d.ts +1 -1
  283. package/dist/tools/session-tools.d.ts.map +1 -1
  284. package/dist/tools/session-tools.js +33 -16
  285. package/dist/tools/session-tools.js.map +1 -1
  286. package/dist/tools/sessions-state-processor.js +2 -2
  287. package/dist/tools/sessions-state-processor.js.map +1 -1
  288. package/dist/tools/side-sessions.d.ts +1 -1
  289. package/dist/tools/side-sessions.d.ts.map +1 -1
  290. package/dist/tools/side-sessions.js +1 -1
  291. package/dist/tools/side-sessions.js.map +1 -1
  292. package/dist/workspace.d.ts +5 -2
  293. package/dist/workspace.d.ts.map +1 -1
  294. package/dist/workspace.js +28 -15
  295. package/dist/workspace.js.map +1 -1
  296. package/package.json +13 -13
@@ -0,0 +1,165 @@
1
+ import { MASTRA_RESOURCE_ID_KEY } from "@mastra/core/request-context";
2
+ //#region src/routes/session-access.ts
3
+ /**
4
+ * Whether the viewer may read a visible session's conversation. A session on its owner's memory is
5
+ * the owner's alone, whatever its row says.
6
+ */
7
+ function canReadSessionResource(session, resourceId, viewer) {
8
+ return viewer.userId === session.userId || resourceId !== session.userId;
9
+ }
10
+ /** Whether a viewer may read or drive a session: same org, and the owner when it is private. */
11
+ function canViewSourceControlSession(session, viewer) {
12
+ if (session.orgId !== viewer.orgId) return false;
13
+ return session.visibility !== "private" || session.userId === viewer.userId;
14
+ }
15
+ const SESSION_PATH = /\/agent-controller\/[^/]+\/sessions\/([^/]+)/;
16
+ const CREATE_SESSION_PATH = /\/agent-controller\/[^/]+\/sessions\/?$/;
17
+ const SET_RESOURCE_PATH = /\/agent-controller\/[^/]+\/sessions\/[^/]+\/resource\/?$/;
18
+ const OM_CONFIG_PATH = /^\/web\/config\/om(\/|$)/;
19
+ const MEMORY_PATH = /^\/api\/memory(\/|$)/;
20
+ const ACTIVE_RUNS_PATH = /\/agent-controller\/[^/]+\/active-runs\/?$/;
21
+ async function bodyField(c, field) {
22
+ const value = (await c.req.raw.clone().json().catch(() => void 0))?.[field];
23
+ return typeof value === "string" && value ? value : void 0;
24
+ }
25
+ function decodeSegment(segment) {
26
+ try {
27
+ return decodeURIComponent(segment);
28
+ } catch {
29
+ return segment;
30
+ }
31
+ }
32
+ /** The value of `field` from the query or the body; `null` when they differ, since handlers read one or the other. */
33
+ async function queryOrBody(c, field) {
34
+ const fromQuery = c.req.query(field) || void 0;
35
+ const fromBody = c.req.method === "GET" ? void 0 : await bodyField(c, field);
36
+ if (fromQuery && fromBody && fromQuery !== fromBody) return null;
37
+ return fromQuery ?? fromBody;
38
+ }
39
+ /** The sessions and memory resources a request addresses. `null` when it names one ambiguously. */
40
+ async function addressed(c) {
41
+ const path = c.req.path;
42
+ const method = c.req.method;
43
+ const sessionMatch = SESSION_PATH.exec(path);
44
+ if (sessionMatch) {
45
+ const sessionId = decodeSegment(sessionMatch[1]);
46
+ const target = method === "POST" && SET_RESOURCE_PATH.test(path) ? await bodyField(c, "newResourceId") : void 0;
47
+ return {
48
+ sessionIds: [sessionId],
49
+ resourceIds: target ? [target] : []
50
+ };
51
+ }
52
+ if (method === "POST" && CREATE_SESSION_PATH.test(path)) {
53
+ const [sessionId, resourceId] = await Promise.all([bodyField(c, "sessionId"), bodyField(c, "resourceId")]);
54
+ const id = sessionId ?? resourceId;
55
+ return {
56
+ sessionIds: id ? [id] : [],
57
+ resourceIds: resourceId ? [resourceId] : []
58
+ };
59
+ }
60
+ if (OM_CONFIG_PATH.test(path)) {
61
+ const sessionId = await queryOrBody(c, "sessionId");
62
+ if (sessionId === null) return null;
63
+ return {
64
+ sessionIds: sessionId ? [sessionId] : [],
65
+ resourceIds: []
66
+ };
67
+ }
68
+ if (MEMORY_PATH.test(path)) {
69
+ const resourceId = await queryOrBody(c, "resourceId");
70
+ if (resourceId === null) return null;
71
+ return {
72
+ sessionIds: [],
73
+ resourceIds: resourceId ? [resourceId] : []
74
+ };
75
+ }
76
+ return {
77
+ sessionIds: [],
78
+ resourceIds: []
79
+ };
80
+ }
81
+ /**
82
+ * Keeps private sessions private. A memory resource says who may read it: a
83
+ * private session runs on its owner's user id, a shared one on its Factory's
84
+ * id, and a session from before resources were shared on its own id. So a
85
+ * caller reaches a resource only when it is their own user id, a Factory in
86
+ * their organization, or a session id whose session they can view; any other
87
+ * resource (another person's user id) answers 404.
88
+ *
89
+ * Session routes (stream, messages, threads, state, OM settings) resolve the
90
+ * session from an in-memory map or its stored thread with no ownership check,
91
+ * so both the session row and the resource it runs on are checked. Memory
92
+ * routes trust a client-supplied resourceId or threadId, so they are bound to
93
+ * the authorized resource, or the caller's own user id when none is named. The
94
+ * controller-wide active-runs list drops runs on unreachable resources.
95
+ * Denials answer 404 so ids do not leak existence. With auth disabled there is
96
+ * no tenant to protect and everything passes through.
97
+ */
98
+ function createSessionAccessGuard({ auth, sessions, projects, controller }) {
99
+ const canReachResource = async (resourceId, viewer) => {
100
+ if (resourceId === viewer.userId) return true;
101
+ const session = await sessions.getBySessionId(resourceId);
102
+ if (session) return canViewSourceControlSession(session, viewer);
103
+ if (!viewer.orgId) return false;
104
+ return await projects.get({
105
+ orgId: viewer.orgId,
106
+ id: resourceId
107
+ }) !== null;
108
+ };
109
+ const sessionResourceId = async (sessionId) => {
110
+ const lookup = controller();
111
+ if (!lookup) return sessionId;
112
+ const live = await lookup.getSession(sessionId);
113
+ if (live) return live.identity.getResourceId();
114
+ return (await lookup.queryThreadById({ threadId: sessionId }))?.resourceId ?? sessionId;
115
+ };
116
+ const canReachSession = async (sessionId, viewer) => {
117
+ const session = await sessions.getBySessionId(sessionId);
118
+ if (session && !canViewSourceControlSession(session, viewer)) return false;
119
+ const resourceId = await sessionResourceId(sessionId);
120
+ if (resourceId === sessionId) return true;
121
+ return canReachResource(resourceId, viewer);
122
+ };
123
+ return async (c, next) => {
124
+ if (!auth.enabled()) return next();
125
+ const ctx = c;
126
+ const target = await addressed(ctx);
127
+ if (!target) return c.json({ error: "The query and body must name the same session" }, 400);
128
+ const isMemory = MEMORY_PATH.test(ctx.req.path);
129
+ const isActiveRuns = ctx.req.method === "GET" && ACTIVE_RUNS_PATH.test(ctx.req.path);
130
+ if (!target.sessionIds.length && !target.resourceIds.length && !isMemory && !isActiveRuns) return next();
131
+ await auth.ensureUser(ctx);
132
+ const viewer = auth.tenant(ctx);
133
+ if (!viewer) return c.json({ error: "Session not found" }, 404);
134
+ for (const sessionId of target.sessionIds) if (!await canReachSession(sessionId, viewer)) return c.json({ error: "Session not found" }, 404);
135
+ for (const resourceId of target.resourceIds) if (!await canReachResource(resourceId, viewer)) return c.json({ error: "Session not found" }, 404);
136
+ if (isMemory) ctx.get("requestContext")?.set(MASTRA_RESOURCE_ID_KEY, target.resourceIds[0] ?? viewer.userId);
137
+ if (!isActiveRuns) return next();
138
+ await next();
139
+ await filterActiveRuns(ctx, (resourceId) => canReachResource(resourceId, viewer));
140
+ };
141
+ }
142
+ /** Drops runs on resources the caller cannot reach from the controller-wide active-runs list. */
143
+ async function filterActiveRuns(c, canReach) {
144
+ if (!c.res.ok) return;
145
+ const body = await c.res.clone().json().catch(() => void 0);
146
+ if (!body || !Array.isArray(body.runs)) return;
147
+ const resourceIds = [...new Set(body.runs.flatMap((run) => typeof run.resourceId === "string" ? [run.resourceId] : []))];
148
+ const reachable = new Map(await Promise.all(resourceIds.map(async (id) => [id, await canReach(id)])));
149
+ const runs = body.runs.filter((run) => typeof run.resourceId !== "string" || reachable.get(run.resourceId));
150
+ if (runs.length === body.runs.length) return;
151
+ const headers = new Headers(c.res.headers);
152
+ headers.delete("content-length");
153
+ c.res.headers.delete("content-length");
154
+ c.res = new Response(JSON.stringify({
155
+ ...body,
156
+ runs
157
+ }), {
158
+ status: c.res.status,
159
+ headers
160
+ });
161
+ }
162
+ //#endregion
163
+ export { canReadSessionResource, canViewSourceControlSession, createSessionAccessGuard };
164
+
165
+ //# sourceMappingURL=session-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-access.js","names":[],"sources":["../../src/routes/session-access.ts"],"sourcesContent":["import { MASTRA_RESOURCE_ID_KEY } from '@mastra/core/request-context';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { Context, MiddlewareHandler } from 'hono';\n\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { RouteAuth } from './route.js';\n\n/**\n * Whether the viewer may read a visible session's conversation. A session on its owner's memory is\n * the owner's alone, whatever its row says.\n */\nexport function canReadSessionResource(\n session: Pick<SourceControlSession, 'userId'>,\n resourceId: string,\n viewer: { userId: string },\n): boolean {\n return viewer.userId === session.userId || resourceId !== session.userId;\n}\n\n/** Whether a viewer may read or drive a session: same org, and the owner when it is private. */\nexport function canViewSourceControlSession(\n session: Pick<SourceControlSession, 'orgId' | 'userId' | 'visibility'>,\n viewer: { orgId?: string; userId: string },\n): boolean {\n if (session.orgId !== viewer.orgId) return false;\n return session.visibility !== 'private' || session.userId === viewer.userId;\n}\n\n/** The controller reads the guard needs to find which resource a session runs on. */\nexport interface SessionResourceLookup {\n getSession(sessionId: string): Promise<{ identity: { getResourceId(): string } } | undefined>;\n queryThreadById(input: { threadId: string }): Promise<{ resourceId: string } | null>;\n}\n\ntype Viewer = { orgId?: string; userId: string };\n\nconst SESSION_PATH = /\\/agent-controller\\/[^/]+\\/sessions\\/([^/]+)/;\nconst CREATE_SESSION_PATH = /\\/agent-controller\\/[^/]+\\/sessions\\/?$/;\nconst SET_RESOURCE_PATH = /\\/agent-controller\\/[^/]+\\/sessions\\/[^/]+\\/resource\\/?$/;\nconst OM_CONFIG_PATH = /^\\/web\\/config\\/om(\\/|$)/;\nconst MEMORY_PATH = /^\\/api\\/memory(\\/|$)/;\nconst ACTIVE_RUNS_PATH = /\\/agent-controller\\/[^/]+\\/active-runs\\/?$/;\n\nasync function bodyField(c: Context, field: string): Promise<string | undefined> {\n // Read a clone: the Mastra adapter clones `c.req.raw` again to parse params,\n // which throws once anything has consumed the original body.\n const body = await c.req.raw\n .clone()\n .json()\n .catch(() => undefined);\n const value = (body as Record<string, unknown> | undefined)?.[field];\n return typeof value === 'string' && value ? value : undefined;\n}\n\nfunction decodeSegment(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\n/** The value of `field` from the query or the body; `null` when they differ, since handlers read one or the other. */\nasync function queryOrBody(c: Context, field: string): Promise<string | undefined | null> {\n const fromQuery = c.req.query(field) || undefined;\n const fromBody = c.req.method === 'GET' ? undefined : await bodyField(c, field);\n if (fromQuery && fromBody && fromQuery !== fromBody) return null;\n return fromQuery ?? fromBody;\n}\n\n/** The sessions and memory resources a request addresses. `null` when it names one ambiguously. */\nasync function addressed(c: Context): Promise<{ sessionIds: string[]; resourceIds: string[] } | null> {\n const path = c.req.path;\n const method = c.req.method;\n const sessionMatch = SESSION_PATH.exec(path);\n if (sessionMatch) {\n const sessionId = decodeSegment(sessionMatch[1]!);\n // Re-keying moves the session onto `newResourceId`, so that resource must be reachable too.\n const target = method === 'POST' && SET_RESOURCE_PATH.test(path) ? await bodyField(c, 'newResourceId') : undefined;\n return { sessionIds: [sessionId], resourceIds: target ? [target] : [] };\n }\n if (method === 'POST' && CREATE_SESSION_PATH.test(path)) {\n // Get-or-create: without a `sessionId` the session is addressed by its resourceId.\n const [sessionId, resourceId] = await Promise.all([bodyField(c, 'sessionId'), bodyField(c, 'resourceId')]);\n const id = sessionId ?? resourceId;\n return { sessionIds: id ? [id] : [], resourceIds: resourceId ? [resourceId] : [] };\n }\n if (OM_CONFIG_PATH.test(path)) {\n const sessionId = await queryOrBody(c, 'sessionId');\n if (sessionId === null) return null;\n return { sessionIds: sessionId ? [sessionId] : [], resourceIds: [] };\n }\n if (MEMORY_PATH.test(path)) {\n const resourceId = await queryOrBody(c, 'resourceId');\n if (resourceId === null) return null;\n return { sessionIds: [], resourceIds: resourceId ? [resourceId] : [] };\n }\n return { sessionIds: [], resourceIds: [] };\n}\n\n/**\n * Keeps private sessions private. A memory resource says who may read it: a\n * private session runs on its owner's user id, a shared one on its Factory's\n * id, and a session from before resources were shared on its own id. So a\n * caller reaches a resource only when it is their own user id, a Factory in\n * their organization, or a session id whose session they can view; any other\n * resource (another person's user id) answers 404.\n *\n * Session routes (stream, messages, threads, state, OM settings) resolve the\n * session from an in-memory map or its stored thread with no ownership check,\n * so both the session row and the resource it runs on are checked. Memory\n * routes trust a client-supplied resourceId or threadId, so they are bound to\n * the authorized resource, or the caller's own user id when none is named. The\n * controller-wide active-runs list drops runs on unreachable resources.\n * Denials answer 404 so ids do not leak existence. With auth disabled there is\n * no tenant to protect and everything passes through.\n */\nexport function createSessionAccessGuard({\n auth,\n sessions,\n projects,\n controller,\n}: {\n auth: RouteAuth;\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n projects: Pick<FactoryProjectsStorage, 'get'>;\n /** Resolved per request: the controller is built after the server config. */\n controller: () => SessionResourceLookup | undefined;\n}): MiddlewareHandler {\n const canReachResource = async (resourceId: string, viewer: Viewer): Promise<boolean> => {\n if (resourceId === viewer.userId) return true;\n const session = await sessions.getBySessionId(resourceId);\n if (session) return canViewSourceControlSession(session, viewer);\n if (!viewer.orgId) return false;\n return (await projects.get({ orgId: viewer.orgId, id: resourceId })) !== null;\n };\n\n const sessionResourceId = async (sessionId: string): Promise<string> => {\n const lookup = controller();\n if (!lookup) return sessionId;\n const live = await lookup.getSession(sessionId);\n if (live) return live.identity.getResourceId();\n return (await lookup.queryThreadById({ threadId: sessionId }))?.resourceId ?? sessionId;\n };\n\n const canReachSession = async (sessionId: string, viewer: Viewer): Promise<boolean> => {\n const session = await sessions.getBySessionId(sessionId);\n if (session && !canViewSourceControlSession(session, viewer)) return false;\n const resourceId = await sessionResourceId(sessionId);\n // A session on its own id was just checked by its row; one without a row is nobody's private session.\n if (resourceId === sessionId) return true;\n return canReachResource(resourceId, viewer);\n };\n\n return async (c, next) => {\n if (!auth.enabled()) return next();\n const ctx = c as unknown as Context;\n const target = await addressed(ctx);\n if (!target) return c.json({ error: 'The query and body must name the same session' }, 400);\n const isMemory = MEMORY_PATH.test(ctx.req.path);\n const isActiveRuns = ctx.req.method === 'GET' && ACTIVE_RUNS_PATH.test(ctx.req.path);\n if (!target.sessionIds.length && !target.resourceIds.length && !isMemory && !isActiveRuns) return next();\n\n await auth.ensureUser(ctx);\n const viewer = auth.tenant(ctx);\n if (!viewer) return c.json({ error: 'Session not found' }, 404);\n for (const sessionId of target.sessionIds) {\n if (!(await canReachSession(sessionId, viewer))) return c.json({ error: 'Session not found' }, 404);\n }\n for (const resourceId of target.resourceIds) {\n if (!(await canReachResource(resourceId, viewer))) return c.json({ error: 'Session not found' }, 404);\n }\n\n if (isMemory) {\n // Memory routes only check that a thread belongs to the request's resource\n // when one is bound server-side. Bind the resource just authorized, or the\n // caller's own user id, so a threadId cannot reach another resource's thread.\n const requestContext = ctx.get('requestContext') as RequestContext | undefined;\n requestContext?.set(MASTRA_RESOURCE_ID_KEY, target.resourceIds[0] ?? viewer.userId);\n }\n if (!isActiveRuns) return next();\n await next();\n await filterActiveRuns(ctx, resourceId => canReachResource(resourceId, viewer));\n };\n}\n\n/** Drops runs on resources the caller cannot reach from the controller-wide active-runs list. */\nasync function filterActiveRuns(c: Context, canReach: (resourceId: string) => Promise<boolean>): Promise<void> {\n if (!c.res.ok) return;\n const body = (await c.res\n .clone()\n .json()\n .catch(() => undefined)) as { runs?: Array<{ resourceId?: unknown }> } | undefined;\n if (!body || !Array.isArray(body.runs)) return;\n const resourceIds = [\n ...new Set(body.runs.flatMap(run => (typeof run.resourceId === 'string' ? [run.resourceId] : []))),\n ];\n const reachable = new Map(await Promise.all(resourceIds.map(async id => [id, await canReach(id)] as const)));\n const runs = body.runs.filter(run => typeof run.resourceId !== 'string' || reachable.get(run.resourceId));\n if (runs.length === body.runs.length) return;\n const headers = new Headers(c.res.headers);\n headers.delete('content-length');\n c.res.headers.delete('content-length');\n c.res = new Response(JSON.stringify({ ...body, runs }), { status: c.res.status, headers });\n}\n"],"mappings":"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"}
@@ -13,13 +13,11 @@ interface SessionAuthorizationResult {
13
13
  }
14
14
  export interface SkillRoutesDeps extends RouteDependencies {
15
15
  controllerId: string;
16
- controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;
16
+ controller: Pick<AgentController<MastraCodeState>, 'getSession'>;
17
17
  sourceControlStorage?: SourceControlStorageHandle;
18
18
  ensureSourceControlReady?: () => Promise<void>;
19
19
  authorizeSessionAddress?: (context: Context, address: {
20
- resourceId: string;
21
- projectRepositoryId?: string;
22
- scope?: string;
20
+ sessionId: string;
23
21
  }) => Promise<SessionAuthorizationResult>;
24
22
  }
25
23
  export declare class SkillRoutes extends Route<SkillRoutesDeps> {
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/routes/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAKpC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAgBnC,UAAU,0BAA0B;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACzB,IAAI,CAAC,EAAE,iBAAiB,GAAG,cAAc,GAAG,mBAAmB,CAAC;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAC;IAC3E,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD,wBAAwB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,uBAAuB,CAAC,EAAE,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1E,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC1C;AAiCD,qBAAa,WAAY,SAAQ,KAAK,CAAC,eAAe,CAAC;;IAoErD,MAAM,IAAI,QAAQ,EAAE;CAiFrB"}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/routes/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAIpC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAanC,UAAU,0BAA0B;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACzB,IAAI,CAAC,EAAE,iBAAiB,GAAG,cAAc,GAAG,mBAAmB,CAAC;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,YAAY,CAAC,CAAC;IACjE,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAClD,wBAAwB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,uBAAuB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC;CACrH;AA6BD,qBAAa,WAAY,SAAQ,KAAK,CAAC,eAAe,CAAC;;IAwCrD,MAAM,IAAI,QAAQ,EAAE;CA4ErB"}
@@ -1,11 +1,9 @@
1
- import { peekSessionSandbox } from "../sandbox/session-sandbox.js";
2
1
  import { Route } from "./route.js";
3
2
  import { listFactorySkills } from "../skills/catalog.js";
4
3
  import { SkillInvocationError, resolveSkillInvocation } from "../skills/service.js";
5
4
  import { registerApiRoute } from "@mastra/core/server";
6
5
  //#region src/routes/skills.ts
7
- const MAX_RESOURCE_ID_LENGTH = 512;
8
- const MAX_SCOPE_LENGTH = 2048;
6
+ const MAX_SESSION_ID_LENGTH = 512;
9
7
  const MAX_ARGUMENTS_LENGTH = 16384;
10
8
  const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
11
9
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -15,22 +13,24 @@ function loose(context) {
15
13
  function parseBody(value) {
16
14
  if (!value || typeof value !== "object") return void 0;
17
15
  const input = value;
18
- if (typeof input.resourceId !== "string" || input.resourceId.length === 0) return void 0;
19
- if (input.resourceId.length > MAX_RESOURCE_ID_LENGTH) return void 0;
20
- if (input.projectRepositoryId !== void 0 && (typeof input.projectRepositoryId !== "string" || !UUID_RE.test(input.projectRepositoryId))) return;
21
- if (input.scope !== void 0 && (typeof input.scope !== "string" || input.scope.length > MAX_SCOPE_LENGTH)) return;
16
+ if (typeof input.sessionId !== "string" || input.sessionId.length === 0) return void 0;
17
+ if (input.sessionId.length > MAX_SESSION_ID_LENGTH) return void 0;
22
18
  if (typeof input.name !== "string" || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return void 0;
23
19
  if (input.arguments !== void 0) {
24
20
  if (typeof input.arguments !== "string" || input.arguments.length > MAX_ARGUMENTS_LENGTH) return void 0;
25
21
  }
26
22
  return {
27
- resourceId: input.resourceId,
28
- ...input.projectRepositoryId ? { projectRepositoryId: input.projectRepositoryId } : {},
29
- ...input.scope ? { scope: input.scope } : {},
23
+ sessionId: input.sessionId,
30
24
  name: input.name,
31
25
  ...input.arguments !== void 0 ? { arguments: input.arguments } : {}
32
26
  };
33
27
  }
28
+ const SESSION_FORBIDDEN = {
29
+ allowed: false,
30
+ status: 403,
31
+ code: "session_forbidden",
32
+ message: "Session access denied."
33
+ };
34
34
  var SkillRoutes = class extends Route {
35
35
  async #authorizeSessionAddress(context, address) {
36
36
  const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;
@@ -43,67 +43,25 @@ var SkillRoutes = class extends Route {
43
43
  code: "unauthorized",
44
44
  message: "Authentication required."
45
45
  };
46
- if (address.resourceId === tenant.userId) return { allowed: true };
47
- if (!UUID_RE.test(address.resourceId) || !address.projectRepositoryId || !UUID_RE.test(address.projectRepositoryId)) return {
46
+ if (address.sessionId === tenant.userId) return { allowed: true };
47
+ if (!UUID_RE.test(address.sessionId)) return {
48
48
  allowed: false,
49
49
  status: 400,
50
50
  code: "invalid_request",
51
51
  message: "Invalid skill invocation request."
52
52
  };
53
- if (!tenant.orgId || !address.scope) return {
54
- allowed: false,
55
- status: 403,
56
- code: "session_forbidden",
57
- message: "Session access denied."
58
- };
59
- if (!storage) return {
60
- allowed: false,
61
- status: 403,
62
- code: "session_forbidden",
63
- message: "Session access denied."
64
- };
53
+ if (!tenant.orgId || !storage) return SESSION_FORBIDDEN;
65
54
  if (ensureSourceControlReady) try {
66
55
  await ensureSourceControlReady();
67
56
  } catch {
68
- return {
69
- allowed: false,
70
- status: 403,
71
- code: "session_forbidden",
72
- message: "Session access denied."
73
- };
57
+ return SESSION_FORBIDDEN;
74
58
  }
75
- const projectRepository = await storage.projectRepositories.get({
76
- orgId: tenant.orgId,
77
- id: address.projectRepositoryId
78
- });
79
- if (!projectRepository) return {
80
- allowed: false,
81
- status: 403,
82
- code: "session_forbidden",
83
- message: "Session access denied."
84
- };
85
- const connection = await storage.connections.get({
86
- orgId: tenant.orgId,
87
- id: projectRepository.connectionId
88
- });
89
- if (!connection || connection.factoryProjectId !== address.resourceId) return {
90
- allowed: false,
91
- status: 403,
92
- code: "session_forbidden",
93
- message: "Session access denied."
94
- };
59
+ const row = await storage.sessions.getBySessionId(address.sessionId);
60
+ if (!row || row.orgId !== tenant.orgId || !row.projectRepositoryId) return SESSION_FORBIDDEN;
95
61
  return (await storage.sessions.list({
96
- projectRepositoryId: address.projectRepositoryId,
62
+ projectRepositoryId: row.projectRepositoryId,
97
63
  viewerUserId: tenant.userId
98
- })).some((row) => {
99
- const liveWorkdir = peekSessionSandbox(row.id)?.workdir;
100
- return liveWorkdir !== void 0 && liveWorkdir === address.scope;
101
- }) ? { allowed: true } : {
102
- allowed: false,
103
- status: 403,
104
- code: "session_forbidden",
105
- message: "Session access denied."
106
- };
64
+ })).some((session) => session.sessionId === address.sessionId) ? { allowed: true } : SESSION_FORBIDDEN;
107
65
  }
108
66
  routes() {
109
67
  const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;
@@ -128,11 +86,7 @@ var SkillRoutes = class extends Route {
128
86
  error: "invalid_request",
129
87
  message: "Invalid skill invocation request."
130
88
  }, 400);
131
- const authorization = await authorize(c, {
132
- resourceId: body.resourceId,
133
- projectRepositoryId: body.projectRepositoryId,
134
- scope: body.scope
135
- });
89
+ const authorization = await authorize(c, { sessionId: body.sessionId });
136
90
  if (!authorization.allowed) return c.json({
137
91
  error: authorization.code,
138
92
  message: authorization.message
@@ -1 +1 @@
1
- {"version":3,"file":"skills.js","names":["#authorizeSessionAddress"],"sources":["../../src/routes/skills.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { peekSessionSandbox } from '../sandbox/session-sandbox.js';\nimport { listFactorySkills } from '../skills/catalog.js';\nimport { resolveSkillInvocation, SkillInvocationError } from '../skills/service.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst MAX_RESOURCE_ID_LENGTH = 512;\nconst MAX_SCOPE_LENGTH = 2048;\nconst MAX_ARGUMENTS_LENGTH = 16_384;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\ninterface SkillInvocationBody {\n resourceId: string;\n projectRepositoryId?: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\ninterface SessionAuthorizationResult {\n allowed: boolean;\n status?: 400 | 401 | 403;\n code?: 'invalid_request' | 'unauthorized' | 'session_forbidden';\n message?: string;\n}\n\nexport interface SkillRoutesDeps extends RouteDependencies {\n controllerId: string;\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;\n sourceControlStorage?: SourceControlStorageHandle;\n ensureSourceControlReady?: () => Promise<void>;\n authorizeSessionAddress?: (\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ) => Promise<SessionAuthorizationResult>;\n}\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nfunction parseBody(value: unknown): SkillInvocationBody | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Record<string, unknown>;\n if (typeof input.resourceId !== 'string' || input.resourceId.length === 0) return undefined;\n if (input.resourceId.length > MAX_RESOURCE_ID_LENGTH) return undefined;\n if (\n input.projectRepositoryId !== undefined &&\n (typeof input.projectRepositoryId !== 'string' || !UUID_RE.test(input.projectRepositoryId))\n ) {\n return undefined;\n }\n if (input.scope !== undefined && (typeof input.scope !== 'string' || input.scope.length > MAX_SCOPE_LENGTH)) {\n return undefined;\n }\n if (typeof input.name !== 'string' || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return undefined;\n if (input.arguments !== undefined) {\n if (typeof input.arguments !== 'string' || input.arguments.length > MAX_ARGUMENTS_LENGTH) return undefined;\n }\n return {\n resourceId: input.resourceId,\n ...(input.projectRepositoryId ? { projectRepositoryId: input.projectRepositoryId } : {}),\n ...(input.scope ? { scope: input.scope } : {}),\n name: input.name,\n ...(input.arguments !== undefined ? { arguments: input.arguments } : {}),\n };\n}\n\nexport class SkillRoutes extends Route<SkillRoutesDeps> {\n async #authorizeSessionAddress(\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ): Promise<SessionAuthorizationResult> {\n const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;\n if (!auth.enabled()) return { allowed: true };\n\n await auth.ensureUser(context);\n const tenant = auth.tenant(context);\n if (!tenant) {\n return { allowed: false, status: 401, code: 'unauthorized', message: 'Authentication required.' };\n }\n\n // Personal sessions are keyed by the authenticated WorkOS user id. Their\n // scope is a client-managed local/user-session worktree and needs no app DB.\n if (address.resourceId === tenant.userId) return { allowed: true };\n\n // Factory sessions are keyed by factoryProjectId and explicitly identify the\n // linked repository whose user-owned worktree supplies the session scope.\n if (\n !UUID_RE.test(address.resourceId) ||\n !address.projectRepositoryId ||\n !UUID_RE.test(address.projectRepositoryId)\n ) {\n return { allowed: false, status: 400, code: 'invalid_request', message: 'Invalid skill invocation request.' };\n }\n if (!tenant.orgId || !address.scope) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (!storage) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (ensureSourceControlReady) {\n try {\n await ensureSourceControlReady();\n } catch {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: tenant.orgId,\n id: address.projectRepositoryId,\n });\n if (!projectRepository) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const connection = await storage.connections.get({ orgId: tenant.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== address.resourceId) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n // The scope must be the live workdir of a session the viewer can see under\n // this repository. The live memoized sandbox entry is the only truth for\n // workdirs (persisted columns are observability); no running/memoized\n // sandbox at that path means no grant (fail closed).\n const sessions = await storage.sessions.list({\n projectRepositoryId: address.projectRepositoryId,\n viewerUserId: tenant.userId,\n });\n const scopeIsLiveSessionWorkdir = sessions.some(row => {\n const liveWorkdir = peekSessionSandbox(row.id)?.workdir;\n return liveWorkdir !== undefined && liveWorkdir === address.scope;\n });\n return scopeIsLiveSessionWorkdir\n ? { allowed: true }\n : { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n\n routes(): ApiRoute[] {\n const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;\n const authorize =\n customAuthorize ??\n ((context: Context, address: { resourceId: string; projectRepositoryId?: string; scope?: string }) =>\n this.#authorizeSessionAddress(context, address));\n const handleSkillRequest = async (context: unknown, dispatch: boolean) => {\n const c = loose(context);\n if (c.req.param('controllerId') !== controllerId) {\n return c.json({ error: 'controller_not_found', message: 'Agent controller not found.' }, 404);\n }\n\n let rawBody: unknown;\n try {\n rawBody = await c.req.json();\n } catch {\n return c.json({ error: 'invalid_request', message: 'Invalid JSON body.' }, 400);\n }\n const body = parseBody(rawBody);\n if (!body) {\n return c.json({ error: 'invalid_request', message: 'Invalid skill invocation request.' }, 400);\n }\n\n const authorization = await authorize(c, {\n resourceId: body.resourceId,\n projectRepositoryId: body.projectRepositoryId,\n scope: body.scope,\n });\n if (!authorization.allowed) {\n return c.json({ error: authorization.code, message: authorization.message }, authorization.status ?? 403);\n }\n\n try {\n const resolved = await resolveSkillInvocation(controller, body);\n if (dispatch) {\n const requestContext = c.get('requestContext' as never) as RequestContext | undefined;\n void resolved.session\n .sendMessage({ content: resolved.message, ...(requestContext ? { requestContext } : {}) })\n .catch((error: unknown) => {\n console.error('Workspace skill dispatch failed after acceptance', error);\n });\n }\n return c.json({ ok: true, skill: resolved.skillName, message: resolved.message });\n } catch (error) {\n if (error instanceof SkillInvocationError) {\n return c.json({ error: error.code, message: error.message }, 404);\n }\n throw error;\n }\n };\n\n const handleFactorySkillsList = async (context: unknown) => {\n const c = loose(context);\n const { auth } = this.deps;\n if (auth.enabled()) {\n await auth.ensureUser(c);\n if (!auth.tenant(c)) {\n return c.json({ error: 'unauthorized', message: 'Authentication required.' }, 401);\n }\n }\n return c.json({ skills: await listFactorySkills() });\n };\n\n return [\n registerApiRoute('/web/factory/skills', {\n method: 'GET',\n requiresAuth: false,\n handler: context => handleFactorySkillsList(context),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/prepare', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, false),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/invoke', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, true),\n }),\n ];\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"skills.js","names":[],"sources":["../../src/routes/skills.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { listFactorySkills } from '../skills/catalog.js';\nimport { resolveSkillInvocation, SkillInvocationError } from '../skills/service.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst MAX_SESSION_ID_LENGTH = 512;\nconst MAX_ARGUMENTS_LENGTH = 16_384;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\ninterface SkillInvocationBody {\n sessionId: string;\n name: string;\n arguments?: string;\n}\n\ninterface SessionAuthorizationResult {\n allowed: boolean;\n status?: 400 | 401 | 403;\n code?: 'invalid_request' | 'unauthorized' | 'session_forbidden';\n message?: string;\n}\n\nexport interface SkillRoutesDeps extends RouteDependencies {\n controllerId: string;\n controller: Pick<AgentController<MastraCodeState>, 'getSession'>;\n sourceControlStorage?: SourceControlStorageHandle;\n ensureSourceControlReady?: () => Promise<void>;\n authorizeSessionAddress?: (context: Context, address: { sessionId: string }) => Promise<SessionAuthorizationResult>;\n}\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nfunction parseBody(value: unknown): SkillInvocationBody | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Record<string, unknown>;\n if (typeof input.sessionId !== 'string' || input.sessionId.length === 0) return undefined;\n if (input.sessionId.length > MAX_SESSION_ID_LENGTH) return undefined;\n if (typeof input.name !== 'string' || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return undefined;\n if (input.arguments !== undefined) {\n if (typeof input.arguments !== 'string' || input.arguments.length > MAX_ARGUMENTS_LENGTH) return undefined;\n }\n return {\n sessionId: input.sessionId,\n name: input.name,\n ...(input.arguments !== undefined ? { arguments: input.arguments } : {}),\n };\n}\n\nconst SESSION_FORBIDDEN: SessionAuthorizationResult = {\n allowed: false,\n status: 403,\n code: 'session_forbidden',\n message: 'Session access denied.',\n};\n\nexport class SkillRoutes extends Route<SkillRoutesDeps> {\n async #authorizeSessionAddress(\n context: Context,\n address: { sessionId: string },\n ): Promise<SessionAuthorizationResult> {\n const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;\n if (!auth.enabled()) return { allowed: true };\n\n await auth.ensureUser(context);\n const tenant = auth.tenant(context);\n if (!tenant) {\n return { allowed: false, status: 401, code: 'unauthorized', message: 'Authentication required.' };\n }\n\n // Personal sessions created without a session id are addressed by the\n // authenticated WorkOS user id and need no app DB.\n if (address.sessionId === tenant.userId) return { allowed: true };\n\n // Factory sessions are addressed by their source-control session id.\n if (!UUID_RE.test(address.sessionId)) {\n return { allowed: false, status: 400, code: 'invalid_request', message: 'Invalid skill invocation request.' };\n }\n if (!tenant.orgId || !storage) return SESSION_FORBIDDEN;\n if (ensureSourceControlReady) {\n try {\n await ensureSourceControlReady();\n } catch {\n return SESSION_FORBIDDEN;\n }\n }\n const row = await storage.sessions.getBySessionId(address.sessionId);\n if (!row || row.orgId !== tenant.orgId || !row.projectRepositoryId) return SESSION_FORBIDDEN;\n // The session must be one the viewer can see under its repository.\n const visible = await storage.sessions.list({\n projectRepositoryId: row.projectRepositoryId,\n viewerUserId: tenant.userId,\n });\n return visible.some(session => session.sessionId === address.sessionId) ? { allowed: true } : SESSION_FORBIDDEN;\n }\n\n routes(): ApiRoute[] {\n const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;\n const authorize =\n customAuthorize ??\n ((context: Context, address: { sessionId: string }) => this.#authorizeSessionAddress(context, address));\n const handleSkillRequest = async (context: unknown, dispatch: boolean) => {\n const c = loose(context);\n if (c.req.param('controllerId') !== controllerId) {\n return c.json({ error: 'controller_not_found', message: 'Agent controller not found.' }, 404);\n }\n\n let rawBody: unknown;\n try {\n rawBody = await c.req.json();\n } catch {\n return c.json({ error: 'invalid_request', message: 'Invalid JSON body.' }, 400);\n }\n const body = parseBody(rawBody);\n if (!body) {\n return c.json({ error: 'invalid_request', message: 'Invalid skill invocation request.' }, 400);\n }\n\n const authorization = await authorize(c, { sessionId: body.sessionId });\n if (!authorization.allowed) {\n return c.json({ error: authorization.code, message: authorization.message }, authorization.status ?? 403);\n }\n\n try {\n const resolved = await resolveSkillInvocation(controller, body);\n if (dispatch) {\n const requestContext = c.get('requestContext' as never) as RequestContext | undefined;\n void resolved.session\n .sendMessage({ content: resolved.message, ...(requestContext ? { requestContext } : {}) })\n .catch((error: unknown) => {\n console.error('Workspace skill dispatch failed after acceptance', error);\n });\n }\n return c.json({ ok: true, skill: resolved.skillName, message: resolved.message });\n } catch (error) {\n if (error instanceof SkillInvocationError) {\n return c.json({ error: error.code, message: error.message }, 404);\n }\n throw error;\n }\n };\n\n const handleFactorySkillsList = async (context: unknown) => {\n const c = loose(context);\n const { auth } = this.deps;\n if (auth.enabled()) {\n await auth.ensureUser(c);\n if (!auth.tenant(c)) {\n return c.json({ error: 'unauthorized', message: 'Authentication required.' }, 401);\n }\n }\n return c.json({ skills: await listFactorySkills() });\n };\n\n return [\n registerApiRoute('/web/factory/skills', {\n method: 'GET',\n requiresAuth: false,\n handler: context => handleFactorySkillsList(context),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/prepare', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, false),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/invoke', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, true),\n }),\n ];\n }\n}\n"],"mappings":"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"}
@@ -23,7 +23,12 @@ export interface SourceControlSessionRoutesOptions {
23
23
  filesystem?: Pick<FilesystemStorage, 'deleteResourceFiles'>;
24
24
  /** How long recreate waits for an aborted run to settle before destroying. */
25
25
  abortSettleTimeoutMs?: number;
26
- workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;
26
+ workItems?: Pick<WorkItemsStorage, 'clearSessionReferences' | 'hasRunBindingForSession'>;
27
+ /** Moves a thread, with everything keyed by its resource, when a session's visibility changes. */
28
+ moveThread?: (input: {
29
+ threadId: string;
30
+ resourceId: string;
31
+ }) => Promise<unknown>;
27
32
  }
28
33
  export declare function buildSourceControlSessionRoutes(options: SourceControlSessionRoutesOptions): ApiRoute[];
29
34
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"source-control-sessions.d.ts","sourceRoot":"","sources":["../../src/routes/source-control-sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOnE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAIrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACxF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAQ5C,eAAO,MAAM,0BAA0B,kBAAkB,CAAC;AAY1D,KAAK,wBAAwB,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;AAE1G,MAAM,WAAW,iCAAiC;IAChD,IAAI,EAAE,SAAS,CAAC;IAChB,cAAc,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACtD,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,wBAAwB,CAAC;IACjC,UAAU,CAAC,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC7C,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IACnD,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,mFAAmF;IACnF,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,sGAAsG;IACtG,UAAU,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,CAAC;IAC5D,8EAA8E;IAC9E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;CAC9D;AAoXD,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,iCAAiC,GAAG,QAAQ,EAAE,CAsMtG"}
1
+ {"version":3,"file":"source-control-sessions.d.ts","sourceRoot":"","sources":["../../src/routes/source-control-sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOnE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAUrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACxF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAS5C,eAAO,MAAM,0BAA0B,kBAAkB,CAAC;AAc1D,KAAK,wBAAwB,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;AAE1G,MAAM,WAAW,iCAAiC;IAChD,IAAI,EAAE,SAAS,CAAC;IAChB,cAAc,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACtD,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,wBAAwB,CAAC;IACjC,UAAU,CAAC,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC7C,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IACnD,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,mFAAmF;IACnF,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,sGAAsG;IACtG,UAAU,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,CAAC;IAC5D,8EAA8E;IAC9E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,GAAG,yBAAyB,CAAC,CAAC;IACzF,kGAAkG;IAClG,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;AA8XD,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,iCAAiC,GAAG,QAAQ,EAAE,CA8TtG"}