@mastra/factory 0.24.0 → 0.25.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +36 -9
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +18 -4
  4. package/dist/auth.js.map +1 -1
  5. package/dist/boards/review.d.ts.map +1 -1
  6. package/dist/boards/review.js +3 -2
  7. package/dist/boards/review.js.map +1 -1
  8. package/dist/factory.d.ts +3 -0
  9. package/dist/factory.d.ts.map +1 -1
  10. package/dist/factory.js +61 -69
  11. package/dist/factory.js.map +1 -1
  12. package/dist/integrations/base.d.ts +14 -1
  13. package/dist/integrations/base.d.ts.map +1 -1
  14. package/dist/{session → integrations/slack}/channel-reply-processor.d.ts +4 -3
  15. package/dist/integrations/slack/channel-reply-processor.d.ts.map +1 -0
  16. package/dist/integrations/slack/channel-reply-processor.js +61 -0
  17. package/dist/integrations/slack/channel-reply-processor.js.map +1 -0
  18. package/dist/integrations/slack/channel-session-link-processor.d.ts.map +1 -0
  19. package/dist/{session → integrations/slack}/channel-session-link-processor.js +13 -4
  20. package/dist/integrations/slack/channel-session-link-processor.js.map +1 -0
  21. package/dist/integrations/slack/integration.d.ts +25 -1
  22. package/dist/integrations/slack/integration.d.ts.map +1 -1
  23. package/dist/integrations/slack/integration.js +55 -5
  24. package/dist/integrations/slack/integration.js.map +1 -1
  25. package/dist/integrations/slack/reply-reminder-processor.d.ts +23 -0
  26. package/dist/integrations/slack/reply-reminder-processor.d.ts.map +1 -0
  27. package/dist/integrations/slack/reply-reminder-processor.js +58 -0
  28. package/dist/integrations/slack/reply-reminder-processor.js.map +1 -0
  29. package/dist/integrations/slack/reply-tool.d.ts +31 -0
  30. package/dist/integrations/slack/reply-tool.d.ts.map +1 -0
  31. package/dist/integrations/slack/reply-tool.js +203 -0
  32. package/dist/integrations/slack/reply-tool.js.map +1 -0
  33. package/dist/integrations/slack/share-file-tool.d.ts +45 -0
  34. package/dist/integrations/slack/share-file-tool.d.ts.map +1 -0
  35. package/dist/integrations/slack/share-file-tool.js +106 -0
  36. package/dist/integrations/slack/share-file-tool.js.map +1 -0
  37. package/dist/integrations/slack/slack.d.ts.map +1 -1
  38. package/dist/integrations/slack/slack.js +54 -6
  39. package/dist/integrations/slack/slack.js.map +1 -1
  40. package/dist/mcp/events.d.ts +73 -0
  41. package/dist/mcp/events.d.ts.map +1 -0
  42. package/dist/mcp/events.js +158 -0
  43. package/dist/mcp/events.js.map +1 -0
  44. package/dist/mcp/routes.d.ts +0 -1
  45. package/dist/mcp/routes.d.ts.map +1 -1
  46. package/dist/mcp/routes.js +30 -3
  47. package/dist/mcp/routes.js.map +1 -1
  48. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  49. package/dist/platform-auth-host.d.ts.map +1 -1
  50. package/dist/platform-auth-host.js +1 -1
  51. package/dist/platform-auth-host.js.map +1 -1
  52. package/dist/routes/config.d.ts +2 -0
  53. package/dist/routes/config.d.ts.map +1 -1
  54. package/dist/routes/config.js +50 -9
  55. package/dist/routes/config.js.map +1 -1
  56. package/dist/routes/contracts.d.ts +2 -0
  57. package/dist/routes/contracts.d.ts.map +1 -1
  58. package/dist/routes/contracts.js +1 -1
  59. package/dist/routes/contracts.js.map +1 -1
  60. package/dist/routes/session-access.d.ts +7 -2
  61. package/dist/routes/session-access.d.ts.map +1 -1
  62. package/dist/routes/session-access.js +41 -4
  63. package/dist/routes/session-access.js.map +1 -1
  64. package/dist/routes/skills.d.ts.map +1 -1
  65. package/dist/routes/skills.js +4 -3
  66. package/dist/routes/skills.js.map +1 -1
  67. package/dist/routes/source-control-sessions.d.ts +2 -0
  68. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  69. package/dist/routes/source-control-sessions.js +161 -24
  70. package/dist/routes/source-control-sessions.js.map +1 -1
  71. package/dist/routes/surface.d.ts +2 -0
  72. package/dist/routes/surface.d.ts.map +1 -1
  73. package/dist/routes/surface.js +10 -4
  74. package/dist/routes/surface.js.map +1 -1
  75. package/dist/rules/dispatcher.d.ts.map +1 -1
  76. package/dist/rules/dispatcher.js +11 -3
  77. package/dist/rules/dispatcher.js.map +1 -1
  78. package/dist/rules/processor.d.ts +2 -2
  79. package/dist/rules/processor.d.ts.map +1 -1
  80. package/dist/rules/processor.js +3 -3
  81. package/dist/rules/processor.js.map +1 -1
  82. package/dist/sandbox/session-retirement.d.ts +11 -0
  83. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  84. package/dist/sandbox/session-retirement.js +38 -1
  85. package/dist/sandbox/session-retirement.js.map +1 -1
  86. package/dist/sandbox/session-sandbox.d.ts +2 -0
  87. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  88. package/dist/sandbox/session-sandbox.js +5 -1
  89. package/dist/sandbox/session-sandbox.js.map +1 -1
  90. package/dist/session/credential-policy.d.ts +4 -2
  91. package/dist/session/credential-policy.d.ts.map +1 -1
  92. package/dist/session/credential-policy.js +11 -1
  93. package/dist/session/credential-policy.js.map +1 -1
  94. package/dist/session/factory-session.d.ts.map +1 -1
  95. package/dist/session/factory-session.js +14 -1
  96. package/dist/session/factory-session.js.map +1 -1
  97. package/dist/session/run-audit.d.ts +1 -0
  98. package/dist/session/run-audit.d.ts.map +1 -1
  99. package/dist/session/run-audit.js +7 -2
  100. package/dist/session/run-audit.js.map +1 -1
  101. package/dist/session/session-threads.d.ts +5 -0
  102. package/dist/session/session-threads.d.ts.map +1 -1
  103. package/dist/session/session-threads.js +8 -1
  104. package/dist/session/session-threads.js.map +1 -1
  105. package/dist/session/thinking-default.d.ts +6 -13
  106. package/dist/session/thinking-default.d.ts.map +1 -1
  107. package/dist/session/thinking-default.js +18 -22
  108. package/dist/session/thinking-default.js.map +1 -1
  109. package/dist/session-owner-signal.d.ts +2 -0
  110. package/dist/session-owner-signal.d.ts.map +1 -0
  111. package/dist/session-owner-signal.js +6 -0
  112. package/dist/session-owner-signal.js.map +1 -0
  113. package/dist/skills/service.d.ts +6 -7
  114. package/dist/skills/service.d.ts.map +1 -1
  115. package/dist/skills/service.js +9 -19
  116. package/dist/skills/service.js.map +1 -1
  117. package/dist/storage/domains/audit/actions.d.ts +1 -0
  118. package/dist/storage/domains/audit/actions.d.ts.map +1 -1
  119. package/dist/storage/domains/audit/actions.js +2 -1
  120. package/dist/storage/domains/audit/actions.js.map +1 -1
  121. package/dist/storage/domains/audit/base.d.ts +18 -2
  122. package/dist/storage/domains/audit/base.d.ts.map +1 -1
  123. package/dist/storage/domains/audit/base.js +22 -4
  124. package/dist/storage/domains/audit/base.js.map +1 -1
  125. package/dist/storage/domains/model-defaults/base.d.ts +8 -0
  126. package/dist/storage/domains/model-defaults/base.d.ts.map +1 -1
  127. package/dist/storage/domains/model-defaults/base.js +18 -0
  128. package/dist/storage/domains/model-defaults/base.js.map +1 -1
  129. package/dist/storage/domains/source-control/base.d.ts +26 -1
  130. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  131. package/dist/storage/domains/source-control/base.js +69 -5
  132. package/dist/storage/domains/source-control/base.js.map +1 -1
  133. package/dist/storage/domains/source-control/inmemory.d.ts +19 -1
  134. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  135. package/dist/storage/domains/source-control/inmemory.js +30 -2
  136. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  137. package/dist/tools/permissions.d.ts.map +1 -1
  138. package/dist/tools/permissions.js +2 -1
  139. package/dist/tools/permissions.js.map +1 -1
  140. package/dist/tools/sandbox-tools.d.ts +12 -0
  141. package/dist/tools/sandbox-tools.d.ts.map +1 -0
  142. package/dist/tools/sandbox-tools.js +40 -0
  143. package/dist/tools/sandbox-tools.js.map +1 -0
  144. package/dist/tools/scope.d.ts.map +1 -1
  145. package/dist/tools/scope.js +2 -5
  146. package/dist/tools/scope.js.map +1 -1
  147. package/dist/tools/session-tools.d.ts.map +1 -1
  148. package/dist/tools/session-tools.js +6 -2
  149. package/dist/tools/session-tools.js.map +1 -1
  150. package/dist/work-item-branch.d.ts +11 -3
  151. package/dist/work-item-branch.d.ts.map +1 -1
  152. package/dist/work-item-branch.js +34 -9
  153. package/dist/work-item-branch.js.map +1 -1
  154. package/dist/workspace.d.ts +2 -1
  155. package/dist/workspace.d.ts.map +1 -1
  156. package/dist/workspace.js +4 -2
  157. package/dist/workspace.js.map +1 -1
  158. package/factory-skills/configure-factory-rules/SKILL.md +2 -2
  159. package/package.json +14 -13
  160. package/dist/session/channel-reply-processor.d.ts.map +0 -1
  161. package/dist/session/channel-reply-processor.js +0 -46
  162. package/dist/session/channel-reply-processor.js.map +0 -1
  163. package/dist/session/channel-session-link-processor.d.ts.map +0 -1
  164. package/dist/session/channel-session-link-processor.js.map +0 -1
  165. /package/dist/{session → integrations/slack}/channel-session-link-processor.d.ts +0 -0
@@ -1,9 +1,10 @@
1
1
  import type { CredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';
2
2
  import type { RequestContext } from '@mastra/core/request-context';
3
+ import type { ModelDefaultRecord, ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';
3
4
  import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
4
5
  import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
5
6
  import type { SourceControlSessionLookup } from './factory-session.js';
6
- interface FactoryModelPolicyDependencies {
7
+ export interface FactoryModelPolicyDependencies {
7
8
  projects: Pick<FactoryProjectsStorage, 'getById'>;
8
9
  sourceControl: Pick<SourceControlSessionLookup, 'getBySessionId'>;
9
10
  workItems: Pick<WorkItemsStorage, 'findRunBindingByThread'>;
@@ -16,5 +17,6 @@ export declare function resolveFactoryModelPolicy(requestContext: RequestContext
16
17
  factoryProjectId?: string;
17
18
  factoryDefaultModelId?: string | null;
18
19
  } | undefined>;
19
- export {};
20
+ /** The personal defaults a policy's tenant runs on; none on Factory routing. */
21
+ export declare function routedPersonalDefaults(tenant: CredentialTenant, modelDefaults: Pick<ModelDefaultsStorage, 'ensureReady' | 'get'>): Promise<ModelDefaultRecord | null>;
20
22
  //# sourceMappingURL=credential-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"credential-policy.d.ts","sourceRoot":"","sources":["../../src/session/credential-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAIvE,UAAU,8BAA8B;IACtC,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;IAClD,aAAa,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAClE,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;CAC7D;AAED,uGAAuG;AACvG,wBAAsB,8BAA8B,CAClD,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAEvC;AAED,+FAA+F;AAC/F,wBAAsB,yBAAyB,CAC7C,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,SAAS,CAAC,CAwDrH"}
1
+ {"version":3,"file":"credential-policy.d.ts","sourceRoot":"","sources":["../../src/session/credential-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAE1G,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAIvE,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;IAClD,aAAa,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAClE,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;CAC7D;AAED,uGAAuG;AACvG,wBAAsB,8BAA8B,CAClD,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAEvC;AAED,+FAA+F;AAC/F,wBAAsB,yBAAyB,CAC7C,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,SAAS,CAAC,CAwDrH;AAED,gFAAgF;AAChF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,gBAAgB,EACxB,aAAa,EAAE,IAAI,CAAC,oBAAoB,EAAE,aAAa,GAAG,KAAK,CAAC,GAC/D,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAIpC"}
@@ -1,3 +1,4 @@
1
+ import { tenantOrgId } from "../routes/provider-credentials.js";
1
2
  import { effectiveModelScope } from "../storage/domains/projects/base.js";
2
3
  import { UUID_PATTERN } from "../routes/contracts.js";
3
4
  import { resolveAuthenticatedCredentialTenant } from "@mastra/code-sdk/agents/credential-resolver";
@@ -50,7 +51,16 @@ async function resolveFactoryModelPolicy(requestContext, deps) {
50
51
  }
51
52
  };
52
53
  }
54
+ /** The personal defaults a policy's tenant runs on; none on Factory routing. */
55
+ async function routedPersonalDefaults(tenant, modelDefaults) {
56
+ if (tenant.credentialScope === "factory") return null;
57
+ await modelDefaults.ensureReady();
58
+ return modelDefaults.get({
59
+ orgId: tenantOrgId(tenant),
60
+ userId: tenant.userId
61
+ });
62
+ }
53
63
  //#endregion
54
- export { resolveFactoryCredentialTenant, resolveFactoryModelPolicy };
64
+ export { resolveFactoryCredentialTenant, resolveFactoryModelPolicy, routedPersonalDefaults };
55
65
 
56
66
  //# sourceMappingURL=credential-policy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"credential-policy.js","names":[],"sources":["../../src/session/credential-policy.ts"],"sourcesContent":["import { resolveAuthenticatedCredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';\nimport type { CredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { UUID_PATTERN } from '../routes/contracts.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { SourceControlSessionLookup } from './factory-session.js';\n\nconst FACTORY_PROJECT_ID = new RegExp(UUID_PATTERN);\n\ninterface FactoryModelPolicyDependencies {\n projects: Pick<FactoryProjectsStorage, 'getById'>;\n sourceControl: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n workItems: Pick<WorkItemsStorage, 'findRunBindingByThread'>;\n}\n\n/** Resolve billing authority from authenticated identity and durable bindings, never session state. */\nexport async function resolveFactoryCredentialTenant(\n requestContext: RequestContext,\n deps: FactoryModelPolicyDependencies,\n): Promise<CredentialTenant | undefined> {\n return (await resolveFactoryModelPolicy(requestContext, deps))?.tenant;\n}\n\n/** Shared invocation and model-picker policy; no client-supplied owner or scope is trusted. */\nexport async function resolveFactoryModelPolicy(\n requestContext: RequestContext,\n deps: FactoryModelPolicyDependencies,\n): Promise<{ tenant: CredentialTenant; factoryProjectId?: string; factoryDefaultModelId?: string | null } | undefined> {\n const principal = resolveAuthenticatedCredentialTenant(requestContext);\n if (!principal) return undefined;\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n if (!controller?.sessionId || !controller.resourceId) return { tenant: principal };\n const { sessionId, resourceId } = controller;\n\n const sourceSession = await deps.sourceControl.getBySessionId(sessionId);\n const binding =\n principal.orgId && controller.threadId\n ? await deps.workItems.findRunBindingByThread({\n orgId: principal.orgId,\n resourceId,\n sessionId,\n threadId: controller.threadId,\n rejectAmbiguous: true,\n })\n : null;\n if (!sourceSession) {\n if (!binding && (resourceId === principal.userId || resourceId.startsWith('channel:')))\n return { tenant: principal };\n // Session-less Factory pages address the Factory itself: session and resource are both the\n // project id. A run session under its Factory's resource whose row is gone is not a page.\n const factoryProject =\n !binding && sessionId === resourceId && FACTORY_PROJECT_ID.test(resourceId)\n ? await deps.projects.getById({ id: resourceId })\n : null;\n if (factoryProject && factoryProject.orgId === principal.orgId) {\n return {\n factoryProjectId: factoryProject.id,\n factoryDefaultModelId: factoryProject.defaultModelId,\n tenant: { ...principal, credentialScope: effectiveModelScope(factoryProject.modelScope, 'session') },\n };\n }\n throw new Error('Factory session ownership is unavailable.');\n }\n if (\n sourceSession.orgId !== principal.orgId ||\n (sourceSession.visibility === 'private' && sourceSession.userId !== principal.userId)\n ) {\n throw new Error('Factory session is not available to the current user.');\n }\n if (!sourceSession.factoryProjectId) throw new Error('Factory session ownership is unavailable.');\n const projectId = binding?.factoryProjectId ?? sourceSession.factoryProjectId;\n const project = await deps.projects.getById({ id: projectId });\n if (!project || project.orgId !== sourceSession.orgId) throw new Error('Factory model policy is unavailable.');\n // Work-item threads keep their billing scope after revocation; history never restores run authority.\n return {\n factoryProjectId: project.id,\n factoryDefaultModelId: project.defaultModelId,\n tenant: {\n orgId: project.orgId,\n userId: sourceSession.userId,\n credentialScope: effectiveModelScope(project.modelScope, binding ? 'run' : 'session'),\n },\n };\n}\n"],"mappings":";;;;AAWA,MAAM,qBAAqB,IAAI,OAAO,YAAY;;AASlD,eAAsB,+BACpB,gBACA,MACuC;CACvC,QAAQ,MAAM,0BAA0B,gBAAgB,IAAI,EAAA,EAAI;AAClE;;AAGA,eAAsB,0BACpB,gBACA,MACqH;CACrH,MAAM,YAAY,qCAAqC,cAAc;CACrE,IAAI,CAAC,WAAW,OAAO,KAAA;CACvB,MAAM,aAAa,eAAe,IAAI,YAAY;CAClD,IAAI,CAAC,YAAY,aAAa,CAAC,WAAW,YAAY,OAAO,EAAE,QAAQ,UAAU;CACjF,MAAM,EAAE,WAAW,eAAe;CAElC,MAAM,gBAAgB,MAAM,KAAK,cAAc,eAAe,SAAS;CACvE,MAAM,UACJ,UAAU,SAAS,WAAW,WAC1B,MAAM,KAAK,UAAU,uBAAuB;EAC1C,OAAO,UAAU;EACjB;EACA;EACA,UAAU,WAAW;EACrB,iBAAiB;CACnB,CAAC,IACD;CACN,IAAI,CAAC,eAAe;EAClB,IAAI,CAAC,YAAY,eAAe,UAAU,UAAU,WAAW,WAAW,UAAU,IAClF,OAAO,EAAE,QAAQ,UAAU;EAG7B,MAAM,iBACJ,CAAC,WAAW,cAAc,cAAc,mBAAmB,KAAK,UAAU,IACtE,MAAM,KAAK,SAAS,QAAQ,EAAE,IAAI,WAAW,CAAC,IAC9C;EACN,IAAI,kBAAkB,eAAe,UAAU,UAAU,OACvD,OAAO;GACL,kBAAkB,eAAe;GACjC,uBAAuB,eAAe;GACtC,QAAQ;IAAE,GAAG;IAAW,iBAAiB,oBAAoB,eAAe,YAAY,SAAS;GAAE;EACrG;EAEF,MAAM,IAAI,MAAM,2CAA2C;CAC7D;CACA,IACE,cAAc,UAAU,UAAU,SACjC,cAAc,eAAe,aAAa,cAAc,WAAW,UAAU,QAE9E,MAAM,IAAI,MAAM,uDAAuD;CAEzE,IAAI,CAAC,cAAc,kBAAkB,MAAM,IAAI,MAAM,2CAA2C;CAChG,MAAM,YAAY,SAAS,oBAAoB,cAAc;CAC7D,MAAM,UAAU,MAAM,KAAK,SAAS,QAAQ,EAAE,IAAI,UAAU,CAAC;CAC7D,IAAI,CAAC,WAAW,QAAQ,UAAU,cAAc,OAAO,MAAM,IAAI,MAAM,sCAAsC;CAE7G,OAAO;EACL,kBAAkB,QAAQ;EAC1B,uBAAuB,QAAQ;EAC/B,QAAQ;GACN,OAAO,QAAQ;GACf,QAAQ,cAAc;GACtB,iBAAiB,oBAAoB,QAAQ,YAAY,UAAU,QAAQ,SAAS;EACtF;CACF;AACF"}
1
+ {"version":3,"file":"credential-policy.js","names":[],"sources":["../../src/session/credential-policy.ts"],"sourcesContent":["import { resolveAuthenticatedCredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';\nimport type { CredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { UUID_PATTERN } from '../routes/contracts.js';\nimport { tenantOrgId } from '../routes/provider-credentials.js';\nimport type { ModelDefaultRecord, ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { SourceControlSessionLookup } from './factory-session.js';\n\nconst FACTORY_PROJECT_ID = new RegExp(UUID_PATTERN);\n\nexport interface FactoryModelPolicyDependencies {\n projects: Pick<FactoryProjectsStorage, 'getById'>;\n sourceControl: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n workItems: Pick<WorkItemsStorage, 'findRunBindingByThread'>;\n}\n\n/** Resolve billing authority from authenticated identity and durable bindings, never session state. */\nexport async function resolveFactoryCredentialTenant(\n requestContext: RequestContext,\n deps: FactoryModelPolicyDependencies,\n): Promise<CredentialTenant | undefined> {\n return (await resolveFactoryModelPolicy(requestContext, deps))?.tenant;\n}\n\n/** Shared invocation and model-picker policy; no client-supplied owner or scope is trusted. */\nexport async function resolveFactoryModelPolicy(\n requestContext: RequestContext,\n deps: FactoryModelPolicyDependencies,\n): Promise<{ tenant: CredentialTenant; factoryProjectId?: string; factoryDefaultModelId?: string | null } | undefined> {\n const principal = resolveAuthenticatedCredentialTenant(requestContext);\n if (!principal) return undefined;\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n if (!controller?.sessionId || !controller.resourceId) return { tenant: principal };\n const { sessionId, resourceId } = controller;\n\n const sourceSession = await deps.sourceControl.getBySessionId(sessionId);\n const binding =\n principal.orgId && controller.threadId\n ? await deps.workItems.findRunBindingByThread({\n orgId: principal.orgId,\n resourceId,\n sessionId,\n threadId: controller.threadId,\n rejectAmbiguous: true,\n })\n : null;\n if (!sourceSession) {\n if (!binding && (resourceId === principal.userId || resourceId.startsWith('channel:')))\n return { tenant: principal };\n // Session-less Factory pages address the Factory itself: session and resource are both the\n // project id. A run session under its Factory's resource whose row is gone is not a page.\n const factoryProject =\n !binding && sessionId === resourceId && FACTORY_PROJECT_ID.test(resourceId)\n ? await deps.projects.getById({ id: resourceId })\n : null;\n if (factoryProject && factoryProject.orgId === principal.orgId) {\n return {\n factoryProjectId: factoryProject.id,\n factoryDefaultModelId: factoryProject.defaultModelId,\n tenant: { ...principal, credentialScope: effectiveModelScope(factoryProject.modelScope, 'session') },\n };\n }\n throw new Error('Factory session ownership is unavailable.');\n }\n if (\n sourceSession.orgId !== principal.orgId ||\n (sourceSession.visibility === 'private' && sourceSession.userId !== principal.userId)\n ) {\n throw new Error('Factory session is not available to the current user.');\n }\n if (!sourceSession.factoryProjectId) throw new Error('Factory session ownership is unavailable.');\n const projectId = binding?.factoryProjectId ?? sourceSession.factoryProjectId;\n const project = await deps.projects.getById({ id: projectId });\n if (!project || project.orgId !== sourceSession.orgId) throw new Error('Factory model policy is unavailable.');\n // Work-item threads keep their billing scope after revocation; history never restores run authority.\n return {\n factoryProjectId: project.id,\n factoryDefaultModelId: project.defaultModelId,\n tenant: {\n orgId: project.orgId,\n userId: sourceSession.userId,\n credentialScope: effectiveModelScope(project.modelScope, binding ? 'run' : 'session'),\n },\n };\n}\n\n/** The personal defaults a policy's tenant runs on; none on Factory routing. */\nexport async function routedPersonalDefaults(\n tenant: CredentialTenant,\n modelDefaults: Pick<ModelDefaultsStorage, 'ensureReady' | 'get'>,\n): Promise<ModelDefaultRecord | null> {\n if (tenant.credentialScope === 'factory') return null;\n await modelDefaults.ensureReady();\n return modelDefaults.get({ orgId: tenantOrgId(tenant), userId: tenant.userId });\n}\n"],"mappings":";;;;;AAaA,MAAM,qBAAqB,IAAI,OAAO,YAAY;;AASlD,eAAsB,+BACpB,gBACA,MACuC;CACvC,QAAQ,MAAM,0BAA0B,gBAAgB,IAAI,EAAA,EAAI;AAClE;;AAGA,eAAsB,0BACpB,gBACA,MACqH;CACrH,MAAM,YAAY,qCAAqC,cAAc;CACrE,IAAI,CAAC,WAAW,OAAO,KAAA;CACvB,MAAM,aAAa,eAAe,IAAI,YAAY;CAClD,IAAI,CAAC,YAAY,aAAa,CAAC,WAAW,YAAY,OAAO,EAAE,QAAQ,UAAU;CACjF,MAAM,EAAE,WAAW,eAAe;CAElC,MAAM,gBAAgB,MAAM,KAAK,cAAc,eAAe,SAAS;CACvE,MAAM,UACJ,UAAU,SAAS,WAAW,WAC1B,MAAM,KAAK,UAAU,uBAAuB;EAC1C,OAAO,UAAU;EACjB;EACA;EACA,UAAU,WAAW;EACrB,iBAAiB;CACnB,CAAC,IACD;CACN,IAAI,CAAC,eAAe;EAClB,IAAI,CAAC,YAAY,eAAe,UAAU,UAAU,WAAW,WAAW,UAAU,IAClF,OAAO,EAAE,QAAQ,UAAU;EAG7B,MAAM,iBACJ,CAAC,WAAW,cAAc,cAAc,mBAAmB,KAAK,UAAU,IACtE,MAAM,KAAK,SAAS,QAAQ,EAAE,IAAI,WAAW,CAAC,IAC9C;EACN,IAAI,kBAAkB,eAAe,UAAU,UAAU,OACvD,OAAO;GACL,kBAAkB,eAAe;GACjC,uBAAuB,eAAe;GACtC,QAAQ;IAAE,GAAG;IAAW,iBAAiB,oBAAoB,eAAe,YAAY,SAAS;GAAE;EACrG;EAEF,MAAM,IAAI,MAAM,2CAA2C;CAC7D;CACA,IACE,cAAc,UAAU,UAAU,SACjC,cAAc,eAAe,aAAa,cAAc,WAAW,UAAU,QAE9E,MAAM,IAAI,MAAM,uDAAuD;CAEzE,IAAI,CAAC,cAAc,kBAAkB,MAAM,IAAI,MAAM,2CAA2C;CAChG,MAAM,YAAY,SAAS,oBAAoB,cAAc;CAC7D,MAAM,UAAU,MAAM,KAAK,SAAS,QAAQ,EAAE,IAAI,UAAU,CAAC;CAC7D,IAAI,CAAC,WAAW,QAAQ,UAAU,cAAc,OAAO,MAAM,IAAI,MAAM,sCAAsC;CAE7G,OAAO;EACL,kBAAkB,QAAQ;EAC1B,uBAAuB,QAAQ;EAC/B,QAAQ;GACN,OAAO,QAAQ;GACf,QAAQ,cAAc;GACtB,iBAAiB,oBAAoB,QAAQ,YAAY,UAAU,QAAQ,SAAS;EACtF;CACF;AACF;;AAGA,eAAsB,uBACpB,QACA,eACoC;CACpC,IAAI,OAAO,oBAAoB,WAAW,OAAO;CACjD,MAAM,cAAc,YAAY;CAChC,OAAO,cAAc,IAAI;EAAE,OAAO,YAAY,MAAM;EAAG,QAAQ,OAAO;CAAO,CAAC;AAChF"}
@@ -1 +1 @@
1
- {"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { legacyGithubWorkItemBranch } from "../work-item-branch.js";
1
2
  import { resolveEnvironmentRepositories } from "./environment-repositories.js";
2
3
  import { resolveTenantProviderDefaultModel } from "../routes/tenant-credentials.js";
3
4
  import { seedSessionOrg } from "./org-seed.js";
@@ -165,13 +166,25 @@ async function ensureFactorySourceSession(args) {
165
166
  });
166
167
  if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);
167
168
  const userId = args.attributeToUserId ?? resolved.connectedByUserId;
169
+ const legacyBranch = legacyGithubWorkItemBranch(branch);
170
+ const lookup = {
171
+ projectRepositoryId: resolved.projectRepositoryId,
172
+ userId
173
+ };
174
+ const existing = legacyBranch ? await sourceControl.sessions.getForBranch({
175
+ ...lookup,
176
+ branch
177
+ }) ?? await sourceControl.sessions.getForBranch({
178
+ ...lookup,
179
+ branch: legacyBranch
180
+ }) : null;
168
181
  const session = await sourceControl.sessions.create({
169
182
  sessionId: args.sessionId ?? globalThis.crypto.randomUUID(),
170
183
  projectRepositoryId: resolved.projectRepositoryId,
171
184
  factoryProjectId,
172
185
  orgId,
173
186
  userId,
174
- branch,
187
+ branch: existing?.branch ?? branch,
175
188
  baseBranch: resolved.baseBranch,
176
189
  visibility: args.visibility ?? "org",
177
190
  ...args.origin ? { origin: args.origin } : {}
@@ -1 +1 @@
1
- {"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { resolveTenantProviderDefaultModel } from '../routes/tenant-credentials.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { EffectiveModelScope, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport { SourceControlConnectionNotFoundError } from '../storage/domains/source-control/base.js';\nimport type {\n SourceControlSession,\n SourceControlSessionVisibility,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport { resolveEnvironmentRepositories } from './environment-repositories.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\nexport class FactorySourceControlConflictError extends Error {}\n\n/** Resolve the model and exclusive credential scope for a Factory session. */\nexport async function resolveFactorySessionModel(args: {\n projects: FactoryProjectsStorage | undefined;\n modelDefaults: ModelDefaultsStorage | undefined;\n factoryProjectId: string | undefined;\n orgId: string;\n ownerUserId: string;\n /** A work-item run or an interactive session; a hybrid Factory resolves a different scope for each. */\n context: 'run' | 'session';\n}): Promise<{ modelScope: EffectiveModelScope; modelId?: string }> {\n if (!args.projects || !args.factoryProjectId) throw new Error('Factory model policy is unavailable.');\n\n const project = await args.projects.getById({ id: args.factoryProjectId });\n if (!project || project.orgId !== args.orgId) throw new Error('Factory model policy is unavailable.');\n if (effectiveModelScope(project.modelScope, args.context) === 'factory') {\n return { modelScope: 'factory', ...(project.defaultModelId ? { modelId: project.defaultModelId } : {}) };\n }\n\n try {\n const userDefault = await args.modelDefaults?.get({ orgId: args.orgId, userId: args.ownerUserId });\n return { modelScope: 'user', ...(userDefault?.modelId ? { modelId: userDefault.modelId } : {}) };\n } catch (error) {\n console.warn('[Factory] Owner default model lookup failed; using the mode default model', {\n orgId: args.orgId,\n ownerUserId: args.ownerUserId,\n error: error instanceof Error ? error.message : String(error),\n });\n return { modelScope: 'user' };\n }\n}\n\nexport interface SourceControlSessionLookup {\n getBySessionId(sessionId: string): Promise<SourceControlSession | null>;\n getSourceControlBySessionId(sessionId: string): Promise<SourceControlStorageHandle | null>;\n /** Side sessions started by `originSessionId`, across every provider. */\n listByOrigin(args: { orgId: string; originSessionId: string }): Promise<SourceControlSession[]>;\n rename(args: { sessionId: string; title: string }): Promise<void>;\n setOriginMuted(args: { sessionId: string; muted: boolean }): Promise<void>;\n markFirstMessage(args: { sessionId: string }): Promise<void>;\n markFirstMeaningfulExec(args: { sessionId: string }): Promise<void>;\n}\n\n/**\n * Read or update a session across every registered source-control partition.\n * Session ids are globally generated, so more than one match is invalid and\n * fails closed instead of mutating an arbitrary provider.\n */\nexport function createSourceControlSessionLookup(\n sourceControls: readonly SourceControlStorageHandle[],\n): SourceControlSessionLookup {\n const resolve = async (\n sessionId: string,\n ): Promise<{ sourceControl: SourceControlStorageHandle; session: SourceControlSession } | null> => {\n const matches = (\n await Promise.all(\n sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { sourceControl: SourceControlStorageHandle; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n return matches[0] ?? null;\n };\n\n return {\n getBySessionId: async sessionId => (await resolve(sessionId))?.session ?? null,\n getSourceControlBySessionId: async sessionId => (await resolve(sessionId))?.sourceControl ?? null,\n listByOrigin: async args =>\n (await Promise.all(sourceControls.map(sourceControl => sourceControl.sessions.listByOrigin(args)))).flat(),\n rename: async args => (await resolve(args.sessionId))?.sourceControl.sessions.rename(args),\n setOriginMuted: async args => (await resolve(args.sessionId))?.sourceControl.sessions.setOriginMuted(args),\n markFirstMessage: async args => (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMessage(args),\n markFirstMeaningfulExec: async args =>\n (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMeaningfulExec(args),\n };\n}\n\n/**\n * Resolve the source-control provider from durable Factory repository links,\n * never from the issue tracker that happened to create the work item.\n */\nexport async function resolveFactorySourceControl(args: {\n sourceControls: readonly SourceControlStorageHandle[];\n orgId: string;\n factoryProjectId: string;\n sessionId?: string;\n}): Promise<SourceControlStorageHandle | undefined> {\n if (args.sessionId) {\n const sessionMatches = (\n await Promise.all(\n args.sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(args.sessionId!),\n })),\n )\n ).filter(match => match.session !== null);\n if (sessionMatches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n if (sessionMatches[0]) return sessionMatches[0].sourceControl;\n }\n\n const linked = [];\n for (const sourceControl of args.sourceControls) {\n const connections = await sourceControl.connections.list({\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n });\n let hasLinkedRepository = false;\n for (const connection of connections) {\n try {\n if (\n (await sourceControl.projectRepositories.list({ orgId: args.orgId, connectionId: connection.id })).length > 0\n ) {\n hasLinkedRepository = true;\n break;\n }\n } catch (error) {\n if (!(error instanceof SourceControlConnectionNotFoundError)) throw error;\n }\n }\n if (hasLinkedRepository) linked.push(sourceControl);\n }\n if (linked.length > 1)\n throw new FactorySourceControlConflictError(\n 'Factory project has repositories linked through multiple source-control providers.',\n );\n return linked[0];\n}\n\nexport interface EnsureFactorySourceSessionArgs {\n /**\n * Storage handle of the integration that owns source control. Nothing here is\n * provider-specific: the connection is matched by the handle's own\n * `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner\n * all resolve through the same traversal.\n */\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n branch: string;\n /**\n * The repository link the session is filed under: the one a work item\n * targets. Defaults to the factory's position-1 environment link (D1), for\n * callers where nothing names a repository (chat threads, user sessions).\n */\n projectRepositoryId?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n /** A pre-generated session id, so a caller can name the branch after it. Defaults to a fresh UUID. */\n sessionId?: string;\n /** Defaults to 'org'. A side session copies its parent's visibility. */\n visibility?: SourceControlSessionVisibility;\n /** The session and thread that started this one as a side session. */\n origin?: { sessionId: string; threadId: string };\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\n}\n\nexport interface ResolvedFactorySourceRepository {\n projectRepositoryId: string;\n /** The repository's pinned branch, else its default branch. */\n baseBranch: string;\n /** Who connected the repository. The attribution for runs with no interactive user. */\n connectedByUserId: string;\n}\n\n/**\n * Outcome of {@link resolvePrimaryEnvironmentRepository}. A miss carries which\n * step failed: callers differ on whether that is an error (an autonomous run\n * cannot proceed) or a routine fallback (a chat integration drops to a\n * chat-only session), and the two steps fail for different reasons worth\n * reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * The repository link a new session is filed under. By default the position-1\n * `inEnvironment` link of the factory (decision D1); a work item that targets\n * a repository names its link through `projectRepositoryId`, which must be an\n * environment link of the same factory. Session start no longer picks a\n * repository to work in, the sandbox holds every environment repository. The\n * PR tools default to this link, and subscriptions, audit and the work item's\n * repository check still read it; moving them to the per-repository\n * `sessionRepositories` rows is a follow-up.\n *\n * The owner is whichever integration owns source control, matched by the\n * handle's own `integrationId` — nothing here is provider-specific.\n */\nexport async function resolvePrimaryEnvironmentRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n projectRepositoryId?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, projectRepositoryId } = args;\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n if (!connections.some(candidate => candidate.integrationId === sourceControl.integrationId)) {\n return { found: false, reason: 'connection' };\n }\n const repositories = await resolveEnvironmentRepositories({ sourceControl, orgId, factoryProjectId });\n const primary = repositories.find(\n candidate => projectRepositoryId === undefined || candidate.link.id === projectRepositoryId,\n );\n if (!primary) return { found: false, reason: 'repository' };\n return {\n found: true,\n projectRepositoryId: primary.link.id,\n baseBranch: primary.link.branch ?? primary.repository.defaultBranch,\n connectedByUserId: primary.connection.createdByUserId,\n };\n}\n\n/**\n * Create the source-control session a repo-backed factory run needs.\n *\n * `FactoryStartCoordinator.prepare` requires this record to already exist —\n * `resolveSourceSession` throws `Factory session not found` otherwise — so every\n * autonomous entry point has to produce one before it can start a run. This is\n * that step, in one place: the owner's connection on the factory project, the\n * work item's repository link (else the position-1 environment link, D1) as\n * the session's link, and a session on the requested branch with that\n * repository's pinned or default branch as the base. The sandbox itself boots\n * every environment repository.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, projectRepositoryId } = args;\n\n const resolved = await resolvePrimaryEnvironmentRepository({\n sourceControl,\n orgId,\n factoryProjectId,\n projectRepositoryId,\n });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: args.sessionId ?? globalThis.crypto.randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: args.visibility ?? 'org',\n ...(args.origin ? { origin: args.origin } : {}),\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n modelScope: EffectiveModelScope;\n ownerUserId: string;\n /**\n * The model selected by the Factory scope. Without it the provider default of a connected\n * provider is applied when the SDK mode default has no credential, else the mode default.\n */\n defaultModelId?: string;\n}\n\n/**\n * Apply a factory project's main model to a session on every run.\n *\n * The model switch is best-effort. A retired model id must not sink a run that\n * is otherwise ready, and the reason is logged. Observational-memory settings are resolved from the\n * authoritative row per invocation before memory processors run.\n */\nexport async function hydrateFactorySession(\n session: FactorySession,\n args: HydrateFactorySessionArgs,\n): Promise<string | undefined> {\n // The org rung knowledge curation scopes on. Seeded first so it lands even if\n // a later best-effort step fails; an empty org marks the session unresolved.\n await seedSessionOrg(session, args.orgId);\n await session.state.set({\n factoryModelScope: args.modelScope,\n factoryOwnerUserId: args.ownerUserId,\n });\n\n let selectedModelId = args.defaultModelId;\n if (!selectedModelId) {\n const modeModelId = session.mode.resolve().defaultModelId;\n const providerModelId = await resolveTenantProviderDefaultModel(\n { orgId: args.orgId, userId: args.ownerUserId, credentialScope: args.modelScope },\n modeModelId,\n ).catch(error => {\n console.warn('[Factory Start] Failed to resolve a provider default model', {\n error: error instanceof Error ? error.message : String(error),\n });\n return undefined;\n });\n selectedModelId = providerModelId ?? modeModelId;\n }\n if (!selectedModelId) return undefined;\n\n let switched = true;\n try {\n await session.model.switch(selectedModelId);\n } catch (error) {\n switched = false;\n console.warn('[Factory Start] Failed to apply scoped default model', {\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n // Subagents otherwise keep the server-wide settings (or the SDK's built-in\n // default), which may name a provider this factory has no credentials for.\n // Each role is independent, so one failure doesn't strand the others.\n for (const agentType of ['explore', 'plan', 'execute']) {\n try {\n await session.subagents.model.set({ modelId: selectedModelId, agentType });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply scoped default subagent model', {\n agentType,\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n // Callers pin this id to the thread, so only report a model the session actually runs on.\n return switched ? selectedModelId : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { resolveTenantProviderDefaultModel } from '../routes/tenant-credentials.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { EffectiveModelScope, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport { SourceControlConnectionNotFoundError } from '../storage/domains/source-control/base.js';\nimport type {\n SourceControlSession,\n SourceControlSessionVisibility,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport { legacyGithubWorkItemBranch } from '../work-item-branch.js';\nimport { resolveEnvironmentRepositories } from './environment-repositories.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\nexport class FactorySourceControlConflictError extends Error {}\n\n/** Resolve the model and exclusive credential scope for a Factory session. */\nexport async function resolveFactorySessionModel(args: {\n projects: FactoryProjectsStorage | undefined;\n modelDefaults: ModelDefaultsStorage | undefined;\n factoryProjectId: string | undefined;\n orgId: string;\n ownerUserId: string;\n /** A work-item run or an interactive session; a hybrid Factory resolves a different scope for each. */\n context: 'run' | 'session';\n}): Promise<{ modelScope: EffectiveModelScope; modelId?: string }> {\n if (!args.projects || !args.factoryProjectId) throw new Error('Factory model policy is unavailable.');\n\n const project = await args.projects.getById({ id: args.factoryProjectId });\n if (!project || project.orgId !== args.orgId) throw new Error('Factory model policy is unavailable.');\n if (effectiveModelScope(project.modelScope, args.context) === 'factory') {\n return { modelScope: 'factory', ...(project.defaultModelId ? { modelId: project.defaultModelId } : {}) };\n }\n\n try {\n const userDefault = await args.modelDefaults?.get({ orgId: args.orgId, userId: args.ownerUserId });\n return { modelScope: 'user', ...(userDefault?.modelId ? { modelId: userDefault.modelId } : {}) };\n } catch (error) {\n console.warn('[Factory] Owner default model lookup failed; using the mode default model', {\n orgId: args.orgId,\n ownerUserId: args.ownerUserId,\n error: error instanceof Error ? error.message : String(error),\n });\n return { modelScope: 'user' };\n }\n}\n\nexport interface SourceControlSessionLookup {\n getBySessionId(sessionId: string): Promise<SourceControlSession | null>;\n getSourceControlBySessionId(sessionId: string): Promise<SourceControlStorageHandle | null>;\n /** Side sessions started by `originSessionId`, across every provider. */\n listByOrigin(args: { orgId: string; originSessionId: string }): Promise<SourceControlSession[]>;\n rename(args: { sessionId: string; title: string }): Promise<void>;\n setOriginMuted(args: { sessionId: string; muted: boolean }): Promise<void>;\n markFirstMessage(args: { sessionId: string }): Promise<void>;\n markFirstMeaningfulExec(args: { sessionId: string }): Promise<void>;\n}\n\n/**\n * Read or update a session across every registered source-control partition.\n * Session ids are globally generated, so more than one match is invalid and\n * fails closed instead of mutating an arbitrary provider.\n */\nexport function createSourceControlSessionLookup(\n sourceControls: readonly SourceControlStorageHandle[],\n): SourceControlSessionLookup {\n const resolve = async (\n sessionId: string,\n ): Promise<{ sourceControl: SourceControlStorageHandle; session: SourceControlSession } | null> => {\n const matches = (\n await Promise.all(\n sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { sourceControl: SourceControlStorageHandle; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n return matches[0] ?? null;\n };\n\n return {\n getBySessionId: async sessionId => (await resolve(sessionId))?.session ?? null,\n getSourceControlBySessionId: async sessionId => (await resolve(sessionId))?.sourceControl ?? null,\n listByOrigin: async args =>\n (await Promise.all(sourceControls.map(sourceControl => sourceControl.sessions.listByOrigin(args)))).flat(),\n rename: async args => (await resolve(args.sessionId))?.sourceControl.sessions.rename(args),\n setOriginMuted: async args => (await resolve(args.sessionId))?.sourceControl.sessions.setOriginMuted(args),\n markFirstMessage: async args => (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMessage(args),\n markFirstMeaningfulExec: async args =>\n (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMeaningfulExec(args),\n };\n}\n\n/**\n * Resolve the source-control provider from durable Factory repository links,\n * never from the issue tracker that happened to create the work item.\n */\nexport async function resolveFactorySourceControl(args: {\n sourceControls: readonly SourceControlStorageHandle[];\n orgId: string;\n factoryProjectId: string;\n sessionId?: string;\n}): Promise<SourceControlStorageHandle | undefined> {\n if (args.sessionId) {\n const sessionMatches = (\n await Promise.all(\n args.sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(args.sessionId!),\n })),\n )\n ).filter(match => match.session !== null);\n if (sessionMatches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n if (sessionMatches[0]) return sessionMatches[0].sourceControl;\n }\n\n const linked = [];\n for (const sourceControl of args.sourceControls) {\n const connections = await sourceControl.connections.list({\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n });\n let hasLinkedRepository = false;\n for (const connection of connections) {\n try {\n if (\n (await sourceControl.projectRepositories.list({ orgId: args.orgId, connectionId: connection.id })).length > 0\n ) {\n hasLinkedRepository = true;\n break;\n }\n } catch (error) {\n if (!(error instanceof SourceControlConnectionNotFoundError)) throw error;\n }\n }\n if (hasLinkedRepository) linked.push(sourceControl);\n }\n if (linked.length > 1)\n throw new FactorySourceControlConflictError(\n 'Factory project has repositories linked through multiple source-control providers.',\n );\n return linked[0];\n}\n\nexport interface EnsureFactorySourceSessionArgs {\n /**\n * Storage handle of the integration that owns source control. Nothing here is\n * provider-specific: the connection is matched by the handle's own\n * `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner\n * all resolve through the same traversal.\n */\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n branch: string;\n /**\n * The repository link the session is filed under: the one a work item\n * targets. Defaults to the factory's position-1 environment link (D1), for\n * callers where nothing names a repository (chat threads, user sessions).\n */\n projectRepositoryId?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n /** A pre-generated session id, so a caller can name the branch after it. Defaults to a fresh UUID. */\n sessionId?: string;\n /** Defaults to 'org'. A side session copies its parent's visibility. */\n visibility?: SourceControlSessionVisibility;\n /** The session and thread that started this one as a side session. */\n origin?: { sessionId: string; threadId: string };\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\n}\n\nexport interface ResolvedFactorySourceRepository {\n projectRepositoryId: string;\n /** The repository's pinned branch, else its default branch. */\n baseBranch: string;\n /** Who connected the repository. The attribution for runs with no interactive user. */\n connectedByUserId: string;\n}\n\n/**\n * Outcome of {@link resolvePrimaryEnvironmentRepository}. A miss carries which\n * step failed: callers differ on whether that is an error (an autonomous run\n * cannot proceed) or a routine fallback (a chat integration drops to a\n * chat-only session), and the two steps fail for different reasons worth\n * reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * The repository link a new session is filed under. By default the position-1\n * `inEnvironment` link of the factory (decision D1); a work item that targets\n * a repository names its link through `projectRepositoryId`, which must be an\n * environment link of the same factory. Session start no longer picks a\n * repository to work in, the sandbox holds every environment repository. The\n * PR tools default to this link, and subscriptions, audit and the work item's\n * repository check still read it; moving them to the per-repository\n * `sessionRepositories` rows is a follow-up.\n *\n * The owner is whichever integration owns source control, matched by the\n * handle's own `integrationId` — nothing here is provider-specific.\n */\nexport async function resolvePrimaryEnvironmentRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n projectRepositoryId?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, projectRepositoryId } = args;\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n if (!connections.some(candidate => candidate.integrationId === sourceControl.integrationId)) {\n return { found: false, reason: 'connection' };\n }\n const repositories = await resolveEnvironmentRepositories({ sourceControl, orgId, factoryProjectId });\n const primary = repositories.find(\n candidate => projectRepositoryId === undefined || candidate.link.id === projectRepositoryId,\n );\n if (!primary) return { found: false, reason: 'repository' };\n return {\n found: true,\n projectRepositoryId: primary.link.id,\n baseBranch: primary.link.branch ?? primary.repository.defaultBranch,\n connectedByUserId: primary.connection.createdByUserId,\n };\n}\n\n/**\n * Create the source-control session a repo-backed factory run needs.\n *\n * `FactoryStartCoordinator.prepare` requires this record to already exist —\n * `resolveSourceSession` throws `Factory session not found` otherwise — so every\n * autonomous entry point has to produce one before it can start a run. This is\n * that step, in one place: the owner's connection on the factory project, the\n * work item's repository link (else the position-1 environment link, D1) as\n * the session's link, and a session on the requested branch with that\n * repository's pinned or default branch as the base. The sandbox itself boots\n * every environment repository.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, projectRepositoryId } = args;\n\n const resolved = await resolvePrimaryEnvironmentRepository({\n sourceControl,\n orgId,\n factoryProjectId,\n projectRepositoryId,\n });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n // Prefer an existing current branch, then the legacy branch on this exact\n // repository and owner. Never reuse another repository's same-number card.\n const legacyBranch = legacyGithubWorkItemBranch(branch);\n const lookup = { projectRepositoryId: resolved.projectRepositoryId, userId };\n const existing = legacyBranch\n ? ((await sourceControl.sessions.getForBranch({ ...lookup, branch })) ??\n (await sourceControl.sessions.getForBranch({ ...lookup, branch: legacyBranch })))\n : null;\n const session = await sourceControl.sessions.create({\n sessionId: args.sessionId ?? globalThis.crypto.randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId,\n branch: existing?.branch ?? branch,\n baseBranch: resolved.baseBranch,\n visibility: args.visibility ?? 'org',\n ...(args.origin ? { origin: args.origin } : {}),\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n modelScope: EffectiveModelScope;\n ownerUserId: string;\n /**\n * The model selected by the Factory scope. Without it the provider default of a connected\n * provider is applied when the SDK mode default has no credential, else the mode default.\n */\n defaultModelId?: string;\n}\n\n/**\n * Apply a factory project's main model to a session on every run.\n *\n * The model switch is best-effort. A retired model id must not sink a run that\n * is otherwise ready, and the reason is logged. Observational-memory settings are resolved from the\n * authoritative row per invocation before memory processors run.\n */\nexport async function hydrateFactorySession(\n session: FactorySession,\n args: HydrateFactorySessionArgs,\n): Promise<string | undefined> {\n // The org rung knowledge curation scopes on. Seeded first so it lands even if\n // a later best-effort step fails; an empty org marks the session unresolved.\n await seedSessionOrg(session, args.orgId);\n await session.state.set({\n factoryModelScope: args.modelScope,\n factoryOwnerUserId: args.ownerUserId,\n });\n\n let selectedModelId = args.defaultModelId;\n if (!selectedModelId) {\n const modeModelId = session.mode.resolve().defaultModelId;\n const providerModelId = await resolveTenantProviderDefaultModel(\n { orgId: args.orgId, userId: args.ownerUserId, credentialScope: args.modelScope },\n modeModelId,\n ).catch(error => {\n console.warn('[Factory Start] Failed to resolve a provider default model', {\n error: error instanceof Error ? error.message : String(error),\n });\n return undefined;\n });\n selectedModelId = providerModelId ?? modeModelId;\n }\n if (!selectedModelId) return undefined;\n\n let switched = true;\n try {\n await session.model.switch(selectedModelId);\n } catch (error) {\n switched = false;\n console.warn('[Factory Start] Failed to apply scoped default model', {\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n // Subagents otherwise keep the server-wide settings (or the SDK's built-in\n // default), which may name a provider this factory has no credentials for.\n // Each role is independent, so one failure doesn't strand the others.\n for (const agentType of ['explore', 'plan', 'execute']) {\n try {\n await session.subagents.model.set({ modelId: selectedModelId, agentType });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply scoped default subagent model', {\n agentType,\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n // Callers pin this id to the thread, so only report a model the session actually runs on.\n return switched ? selectedModelId : undefined;\n}\n"],"mappings":"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"}
@@ -17,6 +17,7 @@ declare const factoryOpenRunSchema: z.ZodObject<{
17
17
  branch: z.ZodString;
18
18
  ownerId: z.ZodOptional<z.ZodString>;
19
19
  heartbeatAt: z.ZodOptional<z.ZodNumber>;
20
+ startedAt: z.ZodOptional<z.ZodNumber>;
20
21
  }, z.core.$strip>;
21
22
  export type FactoryOpenRun = z.infer<typeof factoryOpenRunSchema>;
22
23
  /** A heartbeat older than this means the owner is gone; nothing renews it once the run's dispatcher dies. */
@@ -1 +1 @@
1
- {"version":3,"file":"run-audit.d.ts","sourceRoot":"","sources":["../../src/session/run-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAExE,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAE3D,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;iBAqBxB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,6GAA6G;AAC7G,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,CAOtG;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE;QACf,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAClE,CAAC;IACF,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACxE;AAED,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;AAInF;;;;GAIG;AACH,eAAO,MAAM,mCAAmC,OAAO,CAAC;AAExD,QAAA,MAAM,YAAY,8LASR,CAAC;AAEX,KAAK,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAE1E;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kGAAkG;IAClG,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,GACD;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC;AAoE7B,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAGlG;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzF;AAsED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,EACE,KAAK,EACL,GAAG,EACH,SAAS,EACT,WAAW,GACZ,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,SAAS,EAAE,cAAc,CAAC;IAC1B,WAAW,EAAE,MAAM,YAAY,CAAC;CACjC,GACA,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,4FAA4F;AAC5F,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,EACjB,EAAE,SAAa,GACd,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,GAAG,MAAM,IAAI,CAcnH"}
1
+ {"version":3,"file":"run-audit.d.ts","sourceRoot":"","sources":["../../src/session/run-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAExE,eAAO,MAAM,yBAAyB,oBAAoB,CAAC;AAE3D,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;iBAuBxB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,6GAA6G;AAC7G,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO,CAOtG;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE;QACf,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,UAAU,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAClE,CAAC;IACF,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACxE;AAED,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;AAInF;;;;GAIG;AACH,eAAO,MAAM,mCAAmC,OAAO,CAAC;AAExD,QAAA,MAAM,YAAY,8LASR,CAAC;AAEX,KAAK,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAE1E;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kGAAkG;IAClG,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,GACD;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC;AAoE7B,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAGlG;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzF;AAsED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,EACE,KAAK,EACL,GAAG,EACH,SAAS,EACT,WAAW,GACZ,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,cAAc,CAAC;IACpB,SAAS,EAAE,cAAc,CAAC;IAC1B,WAAW,EAAE,MAAM,YAAY,CAAC;CACjC,GACA,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,4FAA4F;AAC5F,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,EACjB,EAAE,SAAa,GACd,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,GAAG,MAAM,IAAI,CAcnH"}
@@ -21,7 +21,9 @@ const factoryOpenRunSchema = z.object({
21
21
  * was recorded; those read as stale.
22
22
  */
23
23
  ownerId: z.string().optional(),
24
- heartbeatAt: z.number().optional()
24
+ heartbeatAt: z.number().optional(),
25
+ /** When the kickoff was accepted (epoch ms), so the run's end record spans the whole run. Absent on older entries. */
26
+ startedAt: z.number().optional()
25
27
  });
26
28
  /** A heartbeat older than this means the owner is gone; nothing renews it once the run's dispatcher dies. */
27
29
  const FACTORY_OPEN_RUN_STALE_MS = 3e4;
@@ -162,7 +164,10 @@ function recordSessionRunStart(session, { audit, run, actorType, observedEnd })
162
164
  const runs = await listSessionOpenRuns(session);
163
165
  if (!runs.some((existing) => existing.kickoffId === run.kickoffId)) await session.thread.setSetting({
164
166
  key: FACTORY_OPEN_RUNS_SETTING,
165
- value: [...runs, run]
167
+ value: [...runs, {
168
+ startedAt: Date.now(),
169
+ ...run
170
+ }]
166
171
  });
167
172
  await audit.record({
168
173
  ...runAuditInput(run),
@@ -1 +1 @@
1
- {"version":3,"file":"run-audit.js","names":[],"sources":["../../src/session/run-audit.ts"],"sourcesContent":["import type { AgentControllerEvent } from '@mastra/core/agent-controller';\nimport { z } from 'zod';\n\nimport type { AuditActorType } from '../storage/domains/audit/base.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\n\nexport const FACTORY_OPEN_RUNS_SETTING = 'factoryOpenRuns';\n\nconst factoryOpenRunSchema = z.object({\n kickoffId: z.string(),\n bindingId: z.string(),\n role: z.string(),\n startedBy: z.string(),\n orgId: z.string(),\n factoryProjectId: z.string(),\n workItemId: z.string(),\n workItemName: z.string().optional(),\n sessionId: z.string(),\n threadId: z.string(),\n branch: z.string(),\n /**\n * Which dispatcher process is watching this run, and when it last said so\n * (epoch ms). The ledger is a thread setting shared by every replica, so a\n * remote reader needs these to tell a run that is live elsewhere from one\n * that died with its process. Absent on entries written before ownership\n * was recorded; those read as stale.\n */\n ownerId: z.string().optional(),\n heartbeatAt: z.number().optional(),\n});\n\nexport type FactoryOpenRun = z.infer<typeof factoryOpenRunSchema>;\n\n/** A heartbeat older than this means the owner is gone; nothing renews it once the run's dispatcher dies. */\nexport const FACTORY_OPEN_RUN_STALE_MS = 30_000;\n\nexport function isOpenRunLiveElsewhere(run: FactoryOpenRun, ownerId: string, now = Date.now()): boolean {\n return (\n run.ownerId !== undefined &&\n run.ownerId !== ownerId &&\n run.heartbeatAt !== undefined &&\n now - run.heartbeatAt < FACTORY_OPEN_RUN_STALE_MS\n );\n}\n\nexport interface RunEndCaptureSession {\n readonly thread: {\n getSetting(args: { key: string }): Promise<unknown>;\n setSetting(args: { key: string; value: unknown }): Promise<void>;\n };\n subscribe(listener: (event: AgentControllerEvent) => void): () => void;\n}\n\ntype RunEndReason = Extract<AgentControllerEvent, { type: 'agent_end' }>['reason'];\ntype StepUsage = Extract<AgentControllerEvent, { type: 'usage_update' }>['usage'];\nconst pendingWrites = new WeakMap<RunEndCaptureSession, Promise<void>>();\n\n/**\n * JSON budget for the per-step entries on a run's audit record. Audit metadata\n * is capped at 4 KB as a whole and an oversized record loses all of it, so\n * later steps are dropped (and counted) instead; totals always cover every step.\n */\nexport const FACTORY_RUN_USAGE_STEPS_JSON_BUDGET = 2400;\n\nconst USAGE_FIELDS = [\n 'promptTokens',\n 'completionTokens',\n 'totalTokens',\n 'reasoningTokens',\n 'cachedInputTokens',\n 'cacheCreationInputTokens',\n 'cacheCreationInputTokens5m',\n 'cacheCreationInputTokens1h',\n] as const;\n\ntype UsageCounts = Partial<Record<(typeof USAGE_FIELDS)[number], number>>;\n\n/**\n * Token usage attached to a run's `factory.run.ended` audit record.\n * `unreported` means this process saw no usage for the run (it ran elsewhere,\n * the process restarted, or the model reported none). It is never zero spend.\n */\nexport type FactoryRunUsage =\n | {\n status: 'recorded';\n stepCount: number;\n totals: UsageCounts;\n steps: UsageCounts[];\n stepsTruncated?: number;\n /** Other runs that ended in the same agent turn; the turn's usage can't be split between them. */\n sharedWithKickoffIds?: string[];\n }\n | { status: 'unreported' };\n\nfunction usageCounts(usage: StepUsage): UsageCounts {\n const counts: UsageCounts = {};\n for (const field of USAGE_FIELDS) {\n const value = usage[field];\n if (typeof value === 'number') counts[field] = value;\n }\n return counts;\n}\n\nfunction runUsage(steps: UsageCounts[]): FactoryRunUsage {\n if (steps.length === 0) return { status: 'unreported' };\n const totals: UsageCounts = {};\n for (const step of steps) {\n for (const field of USAGE_FIELDS) {\n if (step[field] !== undefined) totals[field] = (totals[field] ?? 0) + step[field];\n }\n }\n const kept: UsageCounts[] = [];\n let length = 2;\n for (const step of steps) {\n length += JSON.stringify(step).length + 1;\n if (length > FACTORY_RUN_USAGE_STEPS_JSON_BUDGET) break;\n kept.push(step);\n }\n return {\n status: 'recorded',\n stepCount: steps.length,\n totals,\n steps: kept,\n ...(steps.length > kept.length ? { stepsTruncated: steps.length - kept.length } : {}),\n };\n}\n\ninterface EndedTurn {\n steps: UsageCounts[];\n claimed: boolean;\n}\n\ninterface SessionUsage {\n /** Steps of the turn in progress; a suspended turn keeps accumulating into its resume. */\n current: UsageCounts[];\n /** The most recently ended turn, which a run whose ledger entry lands after that end may still claim. */\n lastTurn?: EndedTurn;\n /** Usage of runs whose end record failed to persist, kept for the retry. */\n held: Map<string, FactoryRunUsage>;\n}\n\nconst sessionUsage = new WeakMap<RunEndCaptureSession, SessionUsage>();\n\nfunction usageFor(session: RunEndCaptureSession): SessionUsage {\n let usage = sessionUsage.get(session);\n if (!usage) {\n usage = { current: [], held: new Map() };\n sessionUsage.set(session, usage);\n }\n return usage;\n}\n\nfunction serializeRunAudit(session: RunEndCaptureSession, write: () => Promise<void>): Promise<void> {\n const pending = (pendingWrites.get(session) ?? Promise.resolve())\n .then(write)\n .catch(error => console.warn('[Factory run audit] Unable to record run lifecycle.', error));\n pendingWrites.set(session, pending);\n return pending;\n}\n\nexport async function listSessionOpenRuns(session: RunEndCaptureSession): Promise<FactoryOpenRun[]> {\n const stored = await session.thread.getSetting({ key: FACTORY_OPEN_RUNS_SETTING });\n return z.array(factoryOpenRunSchema).parse(stored ?? []);\n}\n\nexport async function waitForSessionRunAudit(session: RunEndCaptureSession): Promise<void> {\n await (pendingWrites.get(session) ?? Promise.resolve());\n}\n\nfunction runAuditInput(run: FactoryOpenRun) {\n // Ownership fields are dispatcher bookkeeping, not part of the run's audit record.\n const { orgId, factoryProjectId, workItemId, workItemName, ownerId, heartbeatAt, ...metadata } = run;\n void ownerId;\n void heartbeatAt;\n return {\n orgId,\n factoryProjectId,\n idempotencyKey: run.kickoffId,\n targets: [{ type: 'work_item', id: workItemId, ...(workItemName ? { name: workItemName } : {}) }],\n metadata,\n };\n}\n\nasync function recordRunEnds(\n session: RunEndCaptureSession,\n audit: AuditRecorder,\n reason: RunEndReason,\n kickoffId?: string,\n endedTurn?: EndedTurn,\n): Promise<void> {\n if (!reason || reason === 'suspended') return;\n const usage = usageFor(session);\n const runs = await listSessionOpenRuns(session);\n if (runs.length === 0) return;\n const ending = runs.filter(run => !kickoffId || run.kickoffId === kickoffId);\n // An end event bills the turn it closed. A run whose ledger entry lands after its turn ended\n // bills the latest ended turn, unless an open run already claimed it.\n const turn = endedTurn ?? usage.lastTurn;\n const steps = turn && !turn.claimed ? turn.steps : [];\n if (turn && ending.length > 0) turn.claimed = true;\n const turnUsage = runUsage(steps);\n const remaining = [];\n for (const run of runs) {\n if (!ending.includes(run)) {\n remaining.push(run);\n continue;\n }\n let runUsageRecord = usage.held.get(run.kickoffId);\n if (!runUsageRecord) {\n const shared = ending.filter(other => other !== run).map(other => other.kickoffId);\n runUsageRecord =\n turnUsage.status === 'recorded' && shared.length > 0\n ? { ...turnUsage, sharedWithKickoffIds: shared }\n : turnUsage;\n usage.held.set(run.kickoffId, runUsageRecord);\n }\n const input = runAuditInput(run);\n const recorded = await audit.record({\n ...input,\n actorId: `agent:${run.threadId}`,\n actorType: 'agent',\n action: 'factory.run.ended',\n metadata: { ...input.metadata, reason, usage: runUsageRecord },\n });\n if (recorded) {\n usage.held.delete(run.kickoffId);\n } else {\n console.warn(\n `[Factory run audit] Token usage for run ${run.kickoffId} (${run.role}) was not persisted; keeping it for the next attempt.`,\n runUsageRecord,\n );\n remaining.push(run);\n }\n }\n await session.thread.setSetting({ key: FACTORY_OPEN_RUNS_SETTING, value: remaining });\n}\n\nexport function recordSessionRunStart(\n session: RunEndCaptureSession,\n {\n audit,\n run,\n actorType,\n observedEnd,\n }: {\n audit: AuditRecorder;\n run: FactoryOpenRun;\n actorType: AuditActorType;\n observedEnd: () => RunEndReason;\n },\n): Promise<void> {\n return serializeRunAudit(session, async () => {\n const runs = await listSessionOpenRuns(session);\n if (!runs.some(existing => existing.kickoffId === run.kickoffId)) {\n await session.thread.setSetting({ key: FACTORY_OPEN_RUNS_SETTING, value: [...runs, run] });\n }\n await audit.record({\n ...runAuditInput(run),\n actorId: run.startedBy,\n actorType,\n action: 'factory.run.started',\n });\n await recordRunEnds(session, audit, observedEnd(), run.kickoffId);\n });\n}\n\n/** Renew this process's claim on an open run so other replicas keep treating it as live. */\nexport function heartbeatSessionOpenRun(\n session: RunEndCaptureSession,\n kickoffId: string,\n at = Date.now(),\n): Promise<void> {\n return serializeRunAudit(session, async () => {\n const runs = await listSessionOpenRuns(session);\n if (!runs.some(run => run.kickoffId === kickoffId)) return;\n await session.thread.setSetting({\n key: FACTORY_OPEN_RUNS_SETTING,\n value: runs.map(run => (run.kickoffId === kickoffId ? { ...run, heartbeatAt: at } : run)),\n });\n });\n}\n\nexport function observeSessionRunEnd(session: RunEndCaptureSession, { audit }: { audit: AuditRecorder }): () => void {\n const usage = usageFor(session);\n return session.subscribe(event => {\n if (event.type === 'usage_update') {\n usage.current.push(usageCounts(event.usage));\n return;\n }\n if (event.type !== 'agent_end' || !event.reason || event.reason === 'suspended') return;\n // Close the turn now so a following turn's steps never land in this run.\n const turn: EndedTurn = { steps: usage.current, claimed: false };\n usage.current = [];\n usage.lastTurn = turn;\n void serializeRunAudit(session, () => recordRunEnds(session, audit, event.reason, undefined, turn));\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"run-audit.js","names":[],"sources":["../../src/session/run-audit.ts"],"sourcesContent":["import type { AgentControllerEvent } from '@mastra/core/agent-controller';\nimport { z } from 'zod';\n\nimport type { AuditActorType } from '../storage/domains/audit/base.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\n\nexport const FACTORY_OPEN_RUNS_SETTING = 'factoryOpenRuns';\n\nconst factoryOpenRunSchema = z.object({\n kickoffId: z.string(),\n bindingId: z.string(),\n role: z.string(),\n startedBy: z.string(),\n orgId: z.string(),\n factoryProjectId: z.string(),\n workItemId: z.string(),\n workItemName: z.string().optional(),\n sessionId: z.string(),\n threadId: z.string(),\n branch: z.string(),\n /**\n * Which dispatcher process is watching this run, and when it last said so\n * (epoch ms). The ledger is a thread setting shared by every replica, so a\n * remote reader needs these to tell a run that is live elsewhere from one\n * that died with its process. Absent on entries written before ownership\n * was recorded; those read as stale.\n */\n ownerId: z.string().optional(),\n heartbeatAt: z.number().optional(),\n /** When the kickoff was accepted (epoch ms), so the run's end record spans the whole run. Absent on older entries. */\n startedAt: z.number().optional(),\n});\n\nexport type FactoryOpenRun = z.infer<typeof factoryOpenRunSchema>;\n\n/** A heartbeat older than this means the owner is gone; nothing renews it once the run's dispatcher dies. */\nexport const FACTORY_OPEN_RUN_STALE_MS = 30_000;\n\nexport function isOpenRunLiveElsewhere(run: FactoryOpenRun, ownerId: string, now = Date.now()): boolean {\n return (\n run.ownerId !== undefined &&\n run.ownerId !== ownerId &&\n run.heartbeatAt !== undefined &&\n now - run.heartbeatAt < FACTORY_OPEN_RUN_STALE_MS\n );\n}\n\nexport interface RunEndCaptureSession {\n readonly thread: {\n getSetting(args: { key: string }): Promise<unknown>;\n setSetting(args: { key: string; value: unknown }): Promise<void>;\n };\n subscribe(listener: (event: AgentControllerEvent) => void): () => void;\n}\n\ntype RunEndReason = Extract<AgentControllerEvent, { type: 'agent_end' }>['reason'];\ntype StepUsage = Extract<AgentControllerEvent, { type: 'usage_update' }>['usage'];\nconst pendingWrites = new WeakMap<RunEndCaptureSession, Promise<void>>();\n\n/**\n * JSON budget for the per-step entries on a run's audit record. Audit metadata\n * is capped at 4 KB as a whole and an oversized record loses all of it, so\n * later steps are dropped (and counted) instead; totals always cover every step.\n */\nexport const FACTORY_RUN_USAGE_STEPS_JSON_BUDGET = 2400;\n\nconst USAGE_FIELDS = [\n 'promptTokens',\n 'completionTokens',\n 'totalTokens',\n 'reasoningTokens',\n 'cachedInputTokens',\n 'cacheCreationInputTokens',\n 'cacheCreationInputTokens5m',\n 'cacheCreationInputTokens1h',\n] as const;\n\ntype UsageCounts = Partial<Record<(typeof USAGE_FIELDS)[number], number>>;\n\n/**\n * Token usage attached to a run's `factory.run.ended` audit record.\n * `unreported` means this process saw no usage for the run (it ran elsewhere,\n * the process restarted, or the model reported none). It is never zero spend.\n */\nexport type FactoryRunUsage =\n | {\n status: 'recorded';\n stepCount: number;\n totals: UsageCounts;\n steps: UsageCounts[];\n stepsTruncated?: number;\n /** Other runs that ended in the same agent turn; the turn's usage can't be split between them. */\n sharedWithKickoffIds?: string[];\n }\n | { status: 'unreported' };\n\nfunction usageCounts(usage: StepUsage): UsageCounts {\n const counts: UsageCounts = {};\n for (const field of USAGE_FIELDS) {\n const value = usage[field];\n if (typeof value === 'number') counts[field] = value;\n }\n return counts;\n}\n\nfunction runUsage(steps: UsageCounts[]): FactoryRunUsage {\n if (steps.length === 0) return { status: 'unreported' };\n const totals: UsageCounts = {};\n for (const step of steps) {\n for (const field of USAGE_FIELDS) {\n if (step[field] !== undefined) totals[field] = (totals[field] ?? 0) + step[field];\n }\n }\n const kept: UsageCounts[] = [];\n let length = 2;\n for (const step of steps) {\n length += JSON.stringify(step).length + 1;\n if (length > FACTORY_RUN_USAGE_STEPS_JSON_BUDGET) break;\n kept.push(step);\n }\n return {\n status: 'recorded',\n stepCount: steps.length,\n totals,\n steps: kept,\n ...(steps.length > kept.length ? { stepsTruncated: steps.length - kept.length } : {}),\n };\n}\n\ninterface EndedTurn {\n steps: UsageCounts[];\n claimed: boolean;\n}\n\ninterface SessionUsage {\n /** Steps of the turn in progress; a suspended turn keeps accumulating into its resume. */\n current: UsageCounts[];\n /** The most recently ended turn, which a run whose ledger entry lands after that end may still claim. */\n lastTurn?: EndedTurn;\n /** Usage of runs whose end record failed to persist, kept for the retry. */\n held: Map<string, FactoryRunUsage>;\n}\n\nconst sessionUsage = new WeakMap<RunEndCaptureSession, SessionUsage>();\n\nfunction usageFor(session: RunEndCaptureSession): SessionUsage {\n let usage = sessionUsage.get(session);\n if (!usage) {\n usage = { current: [], held: new Map() };\n sessionUsage.set(session, usage);\n }\n return usage;\n}\n\nfunction serializeRunAudit(session: RunEndCaptureSession, write: () => Promise<void>): Promise<void> {\n const pending = (pendingWrites.get(session) ?? Promise.resolve())\n .then(write)\n .catch(error => console.warn('[Factory run audit] Unable to record run lifecycle.', error));\n pendingWrites.set(session, pending);\n return pending;\n}\n\nexport async function listSessionOpenRuns(session: RunEndCaptureSession): Promise<FactoryOpenRun[]> {\n const stored = await session.thread.getSetting({ key: FACTORY_OPEN_RUNS_SETTING });\n return z.array(factoryOpenRunSchema).parse(stored ?? []);\n}\n\nexport async function waitForSessionRunAudit(session: RunEndCaptureSession): Promise<void> {\n await (pendingWrites.get(session) ?? Promise.resolve());\n}\n\nfunction runAuditInput(run: FactoryOpenRun) {\n // Ownership fields are dispatcher bookkeeping, not part of the run's audit record.\n const { orgId, factoryProjectId, workItemId, workItemName, ownerId, heartbeatAt, ...metadata } = run;\n void ownerId;\n void heartbeatAt;\n return {\n orgId,\n factoryProjectId,\n idempotencyKey: run.kickoffId,\n targets: [{ type: 'work_item', id: workItemId, ...(workItemName ? { name: workItemName } : {}) }],\n metadata,\n };\n}\n\nasync function recordRunEnds(\n session: RunEndCaptureSession,\n audit: AuditRecorder,\n reason: RunEndReason,\n kickoffId?: string,\n endedTurn?: EndedTurn,\n): Promise<void> {\n if (!reason || reason === 'suspended') return;\n const usage = usageFor(session);\n const runs = await listSessionOpenRuns(session);\n if (runs.length === 0) return;\n const ending = runs.filter(run => !kickoffId || run.kickoffId === kickoffId);\n // An end event bills the turn it closed. A run whose ledger entry lands after its turn ended\n // bills the latest ended turn, unless an open run already claimed it.\n const turn = endedTurn ?? usage.lastTurn;\n const steps = turn && !turn.claimed ? turn.steps : [];\n if (turn && ending.length > 0) turn.claimed = true;\n const turnUsage = runUsage(steps);\n const remaining = [];\n for (const run of runs) {\n if (!ending.includes(run)) {\n remaining.push(run);\n continue;\n }\n let runUsageRecord = usage.held.get(run.kickoffId);\n if (!runUsageRecord) {\n const shared = ending.filter(other => other !== run).map(other => other.kickoffId);\n runUsageRecord =\n turnUsage.status === 'recorded' && shared.length > 0\n ? { ...turnUsage, sharedWithKickoffIds: shared }\n : turnUsage;\n usage.held.set(run.kickoffId, runUsageRecord);\n }\n const input = runAuditInput(run);\n const recorded = await audit.record({\n ...input,\n actorId: `agent:${run.threadId}`,\n actorType: 'agent',\n action: 'factory.run.ended',\n metadata: { ...input.metadata, reason, usage: runUsageRecord },\n });\n if (recorded) {\n usage.held.delete(run.kickoffId);\n } else {\n console.warn(\n `[Factory run audit] Token usage for run ${run.kickoffId} (${run.role}) was not persisted; keeping it for the next attempt.`,\n runUsageRecord,\n );\n remaining.push(run);\n }\n }\n await session.thread.setSetting({ key: FACTORY_OPEN_RUNS_SETTING, value: remaining });\n}\n\nexport function recordSessionRunStart(\n session: RunEndCaptureSession,\n {\n audit,\n run,\n actorType,\n observedEnd,\n }: {\n audit: AuditRecorder;\n run: FactoryOpenRun;\n actorType: AuditActorType;\n observedEnd: () => RunEndReason;\n },\n): Promise<void> {\n return serializeRunAudit(session, async () => {\n const runs = await listSessionOpenRuns(session);\n if (!runs.some(existing => existing.kickoffId === run.kickoffId)) {\n await session.thread.setSetting({\n key: FACTORY_OPEN_RUNS_SETTING,\n value: [...runs, { startedAt: Date.now(), ...run }],\n });\n }\n await audit.record({\n ...runAuditInput(run),\n actorId: run.startedBy,\n actorType,\n action: 'factory.run.started',\n });\n await recordRunEnds(session, audit, observedEnd(), run.kickoffId);\n });\n}\n\n/** Renew this process's claim on an open run so other replicas keep treating it as live. */\nexport function heartbeatSessionOpenRun(\n session: RunEndCaptureSession,\n kickoffId: string,\n at = Date.now(),\n): Promise<void> {\n return serializeRunAudit(session, async () => {\n const runs = await listSessionOpenRuns(session);\n if (!runs.some(run => run.kickoffId === kickoffId)) return;\n await session.thread.setSetting({\n key: FACTORY_OPEN_RUNS_SETTING,\n value: runs.map(run => (run.kickoffId === kickoffId ? { ...run, heartbeatAt: at } : run)),\n });\n });\n}\n\nexport function observeSessionRunEnd(session: RunEndCaptureSession, { audit }: { audit: AuditRecorder }): () => void {\n const usage = usageFor(session);\n return session.subscribe(event => {\n if (event.type === 'usage_update') {\n usage.current.push(usageCounts(event.usage));\n return;\n }\n if (event.type !== 'agent_end' || !event.reason || event.reason === 'suspended') return;\n // Close the turn now so a following turn's steps never land in this run.\n const turn: EndedTurn = { steps: usage.current, claimed: false };\n usage.current = [];\n usage.lastTurn = turn;\n void serializeRunAudit(session, () => recordRunEnds(session, audit, event.reason, undefined, turn));\n });\n}\n"],"mappings":"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"}
@@ -24,6 +24,11 @@ type ThreadLookup = Pick<AgentController<any>, 'queryThreadById'>;
24
24
  */
25
25
  export declare function sessionMemoryResourceId(controller: ThreadLookup | undefined, session: FactorySessionOwnership, runs: SessionRunBindings | undefined): Promise<string>;
26
26
  export declare function isRunBound(session: FactorySessionOwnership, runs: SessionRunBindings | undefined): Promise<boolean>;
27
+ /**
28
+ * A side session works for the session that started it. Otherwise any binding,
29
+ * active or revoked, on any thread: a session that ever ran a role stays a worker.
30
+ */
31
+ export declare function isWorkerSession(session: FactorySessionOwnership, runs: SessionRunBindings | undefined): Promise<boolean>;
27
32
  /** {@link sessionMemoryResourceId} for a caller that already loaded the session's own thread. */
28
33
  export declare function memoryResourceOf(session: FactorySessionOwnership, own: {
29
34
  resourceId?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"session-threads.d.ts","sourceRoot":"","sources":["../../src/session/session-threads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE5F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;AAEnF,KAAK,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAElE;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,YAAY,GAAG,SAAS,EACpC,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,kBAAkB,GAAG,SAAS,GACnC,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,kBAAkB,GAAG,SAAS,oBAOtG;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,uBAAuB,EAChC,GAAG,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACtD,QAAQ,EAAE,OAAO,UA4BlB;AAED,yHAAyH;AACzH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,GAAG,kBAAkB,CAAC,EACrE,UAAU,EAAE,KAAK,GAAG,SAAS,GAC5B,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,cAAc,GAAG,iBAAiB,CAAC,EAC1E,OAAO,EAAE,uBAAuB,EAChC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CAUnB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEnH;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,cAAc,GAAG,iBAAiB,CAAC,EAC1E,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,kBAAkB,GAAG,SAAS,GACnC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAgBlC"}
1
+ {"version":3,"file":"session-threads.d.ts","sourceRoot":"","sources":["../../src/session/session-threads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE5F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;AAEnF,KAAK,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAElE;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,YAAY,GAAG,SAAS,EACpC,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,kBAAkB,GAAG,SAAS,GACnC,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,kBAAkB,GAAG,SAAS,oBAOtG;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,kBAAkB,GAAG,SAAS,oBAE3G;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,uBAAuB,EAChC,GAAG,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACtD,QAAQ,EAAE,OAAO,UA4BlB;AAED,yHAAyH;AACzH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,GAAG,kBAAkB,CAAC,EACrE,UAAU,EAAE,KAAK,GAAG,SAAS,GAC5B,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,cAAc,GAAG,iBAAiB,CAAC,EAC1E,OAAO,EAAE,uBAAuB,EAChC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CAUnB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEnH;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,cAAc,GAAG,iBAAiB,CAAC,EAC1E,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,kBAAkB,GAAG,SAAS,GACnC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAgBlC"}
@@ -20,6 +20,13 @@ async function isRunBound(session, runs) {
20
20
  sessionId: session.sessionId
21
21
  });
22
22
  }
23
+ /**
24
+ * A side session works for the session that started it. Otherwise any binding,
25
+ * active or revoked, on any thread: a session that ever ran a role stays a worker.
26
+ */
27
+ async function isWorkerSession(session, runs) {
28
+ return Boolean(session.originSessionId) || isRunBound(session, runs);
29
+ }
23
30
  /** {@link sessionMemoryResourceId} for a caller that already loaded the session's own thread. */
24
31
  function memoryResourceOf(session, own, runBound) {
25
32
  const factoryProjectId = session.factoryProjectId ?? void 0;
@@ -85,6 +92,6 @@ async function listFactorySessionThreads(controller, session, runs) {
85
92
  return [...threads.values()];
86
93
  }
87
94
  //#endregion
88
- export { isRunBound, listFactorySessionThreads, memoryResourceOf, sessionMemoryResourceId, sessionStampCounts, sessionThreadsToMove, userSessionResourceFor };
95
+ export { isRunBound, isWorkerSession, listFactorySessionThreads, memoryResourceOf, sessionMemoryResourceId, sessionStampCounts, sessionThreadsToMove, userSessionResourceFor };
89
96
 
90
97
  //# sourceMappingURL=session-threads.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-threads.js","names":[],"sources":["../../src/session/session-threads.ts"],"sourcesContent":["import type { AgentController, AgentControllerThread } from '@mastra/core/agent-controller';\n\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\n/** The Factory session row fields that say which memory resource it uses. */\nexport interface FactorySessionOwnership {\n sessionId: string;\n userId: string;\n orgId?: string;\n factoryProjectId?: string | null;\n branch?: string;\n visibility?: string;\n originSessionId?: string | null;\n}\n\n/** Run bindings: the server-side record that a session runs work for its Factory. */\nexport type SessionRunBindings = Pick<WorkItemsStorage, 'hasRunBindingForSession'>;\n\ntype ThreadLookup = Pick<AgentController<any>, 'queryThreadById'>;\n\n/**\n * The one memory resource a Factory session runs under. A Slack session uses\n * its Factory's, or its sender's for a DM; a side session uses its Factory's,\n * or its owner's when private; any other session uses its owner's. A session's own thread can say otherwise\n * only within what the server recorded for it: its own id (created before\n * resources were shared, or a run kept on its own resource), and the Factory's\n * only for a session with a run binding. A client can create a thread under the\n * session's id, so the thread alone never makes a session the Factory's.\n */\nexport async function sessionMemoryResourceId(\n controller: ThreadLookup | undefined,\n session: FactorySessionOwnership,\n runs: SessionRunBindings | undefined,\n): Promise<string> {\n const [own, runBound] = await Promise.all([\n controller?.queryThreadById({ threadId: session.sessionId }),\n isRunBound(session, runs),\n ]);\n return memoryResourceOf(session, own, runBound);\n}\n\nexport async function isRunBound(session: FactorySessionOwnership, runs: SessionRunBindings | undefined) {\n if (!runs || !session.orgId || !session.factoryProjectId) return false;\n return runs.hasRunBindingForSession({\n orgId: session.orgId,\n factoryProjectId: session.factoryProjectId,\n sessionId: session.sessionId,\n });\n}\n\n/** {@link sessionMemoryResourceId} for a caller that already loaded the session's own thread. */\nexport function memoryResourceOf(\n session: FactorySessionOwnership,\n own: { resourceId?: string | null } | null | undefined,\n runBound: boolean,\n) {\n const factoryProjectId = session.factoryProjectId ?? undefined;\n const slack = session.branch?.startsWith('slack/') ?? false;\n // A private Slack DM or side session stays with its owner.\n const shared =\n slack || session.originSessionId\n ? session.visibility === 'private'\n ? session.userId\n : factoryProjectId\n : undefined;\n if (shared) {\n if (own?.resourceId && [session.sessionId, shared].includes(own.resourceId)) return own.resourceId;\n return shared;\n }\n // A session a person started runs on their resource while private and its\n // Factory's once its owner shares it, which moves its threads there. Its own\n // thread says where it is; a client cannot make a private session the\n // Factory's by writing that thread, since the Factory is only allowed for a\n // shared or run-bound session.\n const allowed = [\n session.sessionId,\n session.userId,\n ...((runBound || session.visibility === 'org') && factoryProjectId ? [factoryProjectId] : []),\n ];\n if (own?.resourceId && allowed.includes(own.resourceId)) return own.resourceId;\n // No thread yet: where a session with this visibility starts.\n return userSessionResourceFor(session, session.visibility === 'org' ? 'org' : 'private');\n}\n\n/** The resource a person's session belongs on for a visibility: its Factory's once shared, its owner's while private. */\nexport function userSessionResourceFor(\n session: Pick<FactorySessionOwnership, 'userId' | 'factoryProjectId'>,\n visibility: 'org' | 'private',\n): string {\n return visibility === 'org' && session.factoryProjectId ? session.factoryProjectId : session.userId;\n}\n\n/**\n * The threads to move when a person's session changes visibility and so moves\n * onto `resourceId`: its own thread and the threads stamped with its id that\n * sit on its owner's or Factory's resource. Threads under the session's own id\n * (from before resources were shared) stay where they are.\n */\nexport async function sessionThreadsToMove(\n controller: Pick<AgentController<any>, 'queryThreads' | 'queryThreadById'>,\n session: FactorySessionOwnership,\n resourceId: string,\n): Promise<string[]> {\n const movable = new Set([session.userId, ...(session.factoryProjectId ? [session.factoryProjectId] : [])]);\n const [own, stamped] = await Promise.all([\n controller.queryThreadById({ threadId: session.sessionId }),\n controller.queryThreads({ metadata: { factorySessionId: session.sessionId } }),\n ]);\n const threads = [...(own ? [own] : []), ...stamped].filter(\n thread => thread.resourceId !== resourceId && movable.has(thread.resourceId),\n );\n return [...new Set(threads.map(thread => thread.id))];\n}\n\n/**\n * Whether a `factorySessionId` stamp naming this session counts on a thread\n * under `resourceId`. Any org member can write a stamped thread under the\n * Factory's resource, so a private session's stamp never counts there: only\n * its own thread (`thread.id === sessionId`) does.\n */\nexport function sessionStampCounts(session: FactorySessionOwnership, resourceId: string | null | undefined): boolean {\n return !(session.visibility === 'private' && !!session.factoryProjectId && resourceId === session.factoryProjectId);\n}\n\n/**\n * The threads of one Factory session. Sessions share their Factory's or user's\n * resource, so listing by resource would return every session's threads. A\n * session's own thread carries its id, threads it opens are stamped with\n * `factorySessionId`, and a session created before resources were shared keeps\n * its threads under its own id as the resource. Thread ids and stamps are\n * client-writable, so the own and stamped threads only count under the\n * session's memory resource.\n */\nexport async function listFactorySessionThreads(\n controller: Pick<AgentController<any>, 'queryThreads' | 'queryThreadById'>,\n session: FactorySessionOwnership,\n runs: SessionRunBindings | undefined,\n): Promise<AgentControllerThread[]> {\n const { sessionId } = session;\n const [own, runBound, stamped, legacy] = await Promise.all([\n controller.queryThreadById({ threadId: sessionId }),\n isRunBound(session, runs),\n controller.queryThreads({ metadata: { factorySessionId: sessionId } }),\n controller.queryThreads({ resourceId: sessionId }),\n ]);\n const resourceId = memoryResourceOf(session, own, runBound);\n const threads = new Map<string, AgentControllerThread>();\n const owned = [\n ...(own?.resourceId === resourceId ? [own] : []),\n ...(sessionStampCounts(session, resourceId) ? stamped.filter(thread => thread.resourceId === resourceId) : []),\n ];\n for (const thread of [...owned, ...legacy]) threads.set(thread.id, thread);\n return [...threads.values()];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-threads.js","names":[],"sources":["../../src/session/session-threads.ts"],"sourcesContent":["import type { AgentController, AgentControllerThread } from '@mastra/core/agent-controller';\n\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\n/** The Factory session row fields that say which memory resource it uses. */\nexport interface FactorySessionOwnership {\n sessionId: string;\n userId: string;\n orgId?: string;\n factoryProjectId?: string | null;\n branch?: string;\n visibility?: string;\n originSessionId?: string | null;\n}\n\n/** Run bindings: the server-side record that a session runs work for its Factory. */\nexport type SessionRunBindings = Pick<WorkItemsStorage, 'hasRunBindingForSession'>;\n\ntype ThreadLookup = Pick<AgentController<any>, 'queryThreadById'>;\n\n/**\n * The one memory resource a Factory session runs under. A Slack session uses\n * its Factory's, or its sender's for a DM; a side session uses its Factory's,\n * or its owner's when private; any other session uses its owner's. A session's own thread can say otherwise\n * only within what the server recorded for it: its own id (created before\n * resources were shared, or a run kept on its own resource), and the Factory's\n * only for a session with a run binding. A client can create a thread under the\n * session's id, so the thread alone never makes a session the Factory's.\n */\nexport async function sessionMemoryResourceId(\n controller: ThreadLookup | undefined,\n session: FactorySessionOwnership,\n runs: SessionRunBindings | undefined,\n): Promise<string> {\n const [own, runBound] = await Promise.all([\n controller?.queryThreadById({ threadId: session.sessionId }),\n isRunBound(session, runs),\n ]);\n return memoryResourceOf(session, own, runBound);\n}\n\nexport async function isRunBound(session: FactorySessionOwnership, runs: SessionRunBindings | undefined) {\n if (!runs || !session.orgId || !session.factoryProjectId) return false;\n return runs.hasRunBindingForSession({\n orgId: session.orgId,\n factoryProjectId: session.factoryProjectId,\n sessionId: session.sessionId,\n });\n}\n\n/**\n * A side session works for the session that started it. Otherwise any binding,\n * active or revoked, on any thread: a session that ever ran a role stays a worker.\n */\nexport async function isWorkerSession(session: FactorySessionOwnership, runs: SessionRunBindings | undefined) {\n return Boolean(session.originSessionId) || isRunBound(session, runs);\n}\n\n/** {@link sessionMemoryResourceId} for a caller that already loaded the session's own thread. */\nexport function memoryResourceOf(\n session: FactorySessionOwnership,\n own: { resourceId?: string | null } | null | undefined,\n runBound: boolean,\n) {\n const factoryProjectId = session.factoryProjectId ?? undefined;\n const slack = session.branch?.startsWith('slack/') ?? false;\n // A private Slack DM or side session stays with its owner.\n const shared =\n slack || session.originSessionId\n ? session.visibility === 'private'\n ? session.userId\n : factoryProjectId\n : undefined;\n if (shared) {\n if (own?.resourceId && [session.sessionId, shared].includes(own.resourceId)) return own.resourceId;\n return shared;\n }\n // A session a person started runs on their resource while private and its\n // Factory's once its owner shares it, which moves its threads there. Its own\n // thread says where it is; a client cannot make a private session the\n // Factory's by writing that thread, since the Factory is only allowed for a\n // shared or run-bound session.\n const allowed = [\n session.sessionId,\n session.userId,\n ...((runBound || session.visibility === 'org') && factoryProjectId ? [factoryProjectId] : []),\n ];\n if (own?.resourceId && allowed.includes(own.resourceId)) return own.resourceId;\n // No thread yet: where a session with this visibility starts.\n return userSessionResourceFor(session, session.visibility === 'org' ? 'org' : 'private');\n}\n\n/** The resource a person's session belongs on for a visibility: its Factory's once shared, its owner's while private. */\nexport function userSessionResourceFor(\n session: Pick<FactorySessionOwnership, 'userId' | 'factoryProjectId'>,\n visibility: 'org' | 'private',\n): string {\n return visibility === 'org' && session.factoryProjectId ? session.factoryProjectId : session.userId;\n}\n\n/**\n * The threads to move when a person's session changes visibility and so moves\n * onto `resourceId`: its own thread and the threads stamped with its id that\n * sit on its owner's or Factory's resource. Threads under the session's own id\n * (from before resources were shared) stay where they are.\n */\nexport async function sessionThreadsToMove(\n controller: Pick<AgentController<any>, 'queryThreads' | 'queryThreadById'>,\n session: FactorySessionOwnership,\n resourceId: string,\n): Promise<string[]> {\n const movable = new Set([session.userId, ...(session.factoryProjectId ? [session.factoryProjectId] : [])]);\n const [own, stamped] = await Promise.all([\n controller.queryThreadById({ threadId: session.sessionId }),\n controller.queryThreads({ metadata: { factorySessionId: session.sessionId } }),\n ]);\n const threads = [...(own ? [own] : []), ...stamped].filter(\n thread => thread.resourceId !== resourceId && movable.has(thread.resourceId),\n );\n return [...new Set(threads.map(thread => thread.id))];\n}\n\n/**\n * Whether a `factorySessionId` stamp naming this session counts on a thread\n * under `resourceId`. Any org member can write a stamped thread under the\n * Factory's resource, so a private session's stamp never counts there: only\n * its own thread (`thread.id === sessionId`) does.\n */\nexport function sessionStampCounts(session: FactorySessionOwnership, resourceId: string | null | undefined): boolean {\n return !(session.visibility === 'private' && !!session.factoryProjectId && resourceId === session.factoryProjectId);\n}\n\n/**\n * The threads of one Factory session. Sessions share their Factory's or user's\n * resource, so listing by resource would return every session's threads. A\n * session's own thread carries its id, threads it opens are stamped with\n * `factorySessionId`, and a session created before resources were shared keeps\n * its threads under its own id as the resource. Thread ids and stamps are\n * client-writable, so the own and stamped threads only count under the\n * session's memory resource.\n */\nexport async function listFactorySessionThreads(\n controller: Pick<AgentController<any>, 'queryThreads' | 'queryThreadById'>,\n session: FactorySessionOwnership,\n runs: SessionRunBindings | undefined,\n): Promise<AgentControllerThread[]> {\n const { sessionId } = session;\n const [own, runBound, stamped, legacy] = await Promise.all([\n controller.queryThreadById({ threadId: sessionId }),\n isRunBound(session, runs),\n controller.queryThreads({ metadata: { factorySessionId: sessionId } }),\n controller.queryThreads({ resourceId: sessionId }),\n ]);\n const resourceId = memoryResourceOf(session, own, runBound);\n const threads = new Map<string, AgentControllerThread>();\n const owned = [\n ...(own?.resourceId === resourceId ? [own] : []),\n ...(sessionStampCounts(session, resourceId) ? stamped.filter(thread => thread.resourceId === resourceId) : []),\n ];\n for (const thread of [...owned, ...legacy]) threads.set(thread.id, thread);\n return [...threads.values()];\n}\n"],"mappings":"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"}
@@ -1,18 +1,11 @@
1
1
  import type { ThinkingLevelSetting } from '@mastra/code-sdk/thinking';
2
2
  import type { RequestContext } from '@mastra/core/request-context';
3
- import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
4
- import type { SourceControlSessionLookup } from './factory-session.js';
5
- interface FactoryThinkingDefaultDependencies {
6
- projects: Pick<FactoryProjectsStorage, 'getById'>;
7
- sourceControl: Pick<SourceControlSessionLookup, 'getBySessionId'>;
3
+ import type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';
4
+ import type { FactoryModelPolicyDependencies } from './credential-policy.js';
5
+ interface FactoryThinkingDefaultDependencies extends FactoryModelPolicyDependencies {
6
+ modelDefaults: Pick<ModelDefaultsStorage, 'ensureReady' | 'get'>;
8
7
  }
9
- /**
10
- * The thinking level a Factory request uses when its session has not picked
11
- * one: the default stored on the session's Factory. Requests outside any
12
- * Factory get the built-in default; the host's settings.json never applies.
13
- */
14
- export declare function resolveFactoryThinkingLevel(requestContext: RequestContext, deps: FactoryThinkingDefaultDependencies): Promise<ThinkingLevelSetting>;
15
- /** The thinking default stored on one Factory, for callers that already know which Factory a run belongs to. */
16
- export declare function readFactoryThinkingLevel(projects: Pick<FactoryProjectsStorage, 'getById'>, factoryProjectId: string): Promise<ThinkingLevelSetting>;
8
+ /** A session's unpicked thinking level: its routed personal level, else its Factory's; unsigned requests get the Factory's. */
9
+ export declare function resolveFactoryThinkingLevel(requestContext: RequestContext | undefined, deps: FactoryThinkingDefaultDependencies): Promise<ThinkingLevelSetting>;
17
10
  export {};
18
11
  //# sourceMappingURL=thinking-default.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"thinking-default.d.ts","sourceRoot":"","sources":["../../src/session/thinking-default.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAkB,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClG,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAIvE,UAAU,kCAAkC;IAC1C,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;IAClD,aAAa,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;CACnE;AAeD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,kCAAkC,GACvC,OAAO,CAAC,oBAAoB,CAAC,CAO/B;AAED,gHAAgH;AAChH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,EACjD,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,oBAAoB,CAAC,CAE/B"}
1
+ {"version":3,"file":"thinking-default.d.ts","sourceRoot":"","sources":["../../src/session/thinking-default.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAGtF,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AAI7E,UAAU,kCAAmC,SAAQ,8BAA8B;IACjF,aAAa,EAAE,IAAI,CAAC,oBAAoB,EAAE,aAAa,GAAG,KAAK,CAAC,CAAC;CAClE;AAkBD,+HAA+H;AAC/H,wBAAsB,2BAA2B,CAC/C,cAAc,EAAE,cAAc,GAAG,SAAS,EAC1C,IAAI,EAAE,kCAAkC,GACvC,OAAO,CAAC,oBAAoB,CAAC,CAsB/B"}
@@ -1,41 +1,37 @@
1
1
  import { DEFAULT_FACTORY_THINKING_LEVEL } from "../storage/domains/projects/base.js";
2
2
  import { UUID_PATTERN } from "../routes/contracts.js";
3
+ import { resolveFactoryModelPolicy, routedPersonalDefaults } from "./credential-policy.js";
3
4
  //#region src/session/thinking-default.ts
4
5
  const FACTORY_PROJECT_ID = new RegExp(UUID_PATTERN);
5
- async function resolveFactoryProjectId(controller, deps) {
6
- const { sessionId, resourceId } = controller;
7
- const sourceSession = sessionId ? await deps.sourceControl.getBySessionId(sessionId) : null;
8
- if (sourceSession?.factoryProjectId) return sourceSession.factoryProjectId;
6
+ function sessionlessFactoryProjectId(controller) {
9
7
  const stateProjectId = (controller.getState?.())?.factoryProjectId;
10
8
  if (typeof stateProjectId === "string" && stateProjectId) return stateProjectId;
9
+ const { resourceId } = controller;
11
10
  return resourceId && FACTORY_PROJECT_ID.test(resourceId) ? resourceId : void 0;
12
11
  }
13
- /**
14
- * The thinking level a Factory request uses when its session has not picked
15
- * one: the default stored on the session's Factory. Requests outside any
16
- * Factory get the built-in default; the host's settings.json never applies.
17
- */
12
+ async function unsignedFactoryProjectId(controller, deps) {
13
+ return (controller.sessionId ? await deps.sourceControl.getBySessionId(controller.sessionId) : null)?.factoryProjectId ?? sessionlessFactoryProjectId(controller);
14
+ }
15
+ /** A session's unpicked thinking level: its routed personal level, else its Factory's; unsigned requests get the Factory's. */
18
16
  async function resolveFactoryThinkingLevel(requestContext, deps) {
17
+ if (!requestContext) return DEFAULT_FACTORY_THINKING_LEVEL;
19
18
  const controller = requestContext.get("controller");
20
19
  if (!controller) return DEFAULT_FACTORY_THINKING_LEVEL;
21
- return readThinkingDefault(async () => {
22
- const projectId = await resolveFactoryProjectId(controller, deps);
23
- return projectId ? deps.projects.getById({ id: projectId }) : null;
24
- });
25
- }
26
- /** The thinking default stored on one Factory, for callers that already know which Factory a run belongs to. */
27
- function readFactoryThinkingLevel(projects, factoryProjectId) {
28
- return readThinkingDefault(() => projects.getById({ id: factoryProjectId }));
29
- }
30
- async function readThinkingDefault(load) {
31
20
  try {
32
- return (await load())?.defaultThinkingLevel ?? "medium";
21
+ const policy = await resolveFactoryModelPolicy(requestContext, deps);
22
+ const factoryProjectId = policy ? policy.factoryProjectId ?? sessionlessFactoryProjectId(controller) : await unsignedFactoryProjectId(controller, deps);
23
+ const factoryLevel = (factoryProjectId ? await deps.projects.getById({ id: factoryProjectId }) : null)?.defaultThinkingLevel ?? "medium";
24
+ if (!policy) return factoryLevel;
25
+ return (await routedPersonalDefaults(policy.tenant, deps.modelDefaults).catch((error) => {
26
+ console.warn("[factory] Could not read the personal thinking default; using the Factory level.", error);
27
+ return null;
28
+ }))?.thinkingLevel ?? factoryLevel;
33
29
  } catch (error) {
34
- console.warn("[factory] Could not read the Factory thinking default; using medium.", error);
30
+ console.warn("[factory] Could not read the thinking default; using medium.", error);
35
31
  return DEFAULT_FACTORY_THINKING_LEVEL;
36
32
  }
37
33
  }
38
34
  //#endregion
39
- export { readFactoryThinkingLevel, resolveFactoryThinkingLevel };
35
+ export { resolveFactoryThinkingLevel };
40
36
 
41
37
  //# sourceMappingURL=thinking-default.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"thinking-default.js","names":[],"sources":["../../src/session/thinking-default.ts"],"sourcesContent":["import type { ThinkingLevelSetting } from '@mastra/code-sdk/thinking';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { UUID_PATTERN } from '../routes/contracts.js';\nimport { DEFAULT_FACTORY_THINKING_LEVEL } from '../storage/domains/projects/base.js';\nimport type { FactoryProject, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlSessionLookup } from './factory-session.js';\n\nconst FACTORY_PROJECT_ID = new RegExp(UUID_PATTERN);\n\ninterface FactoryThinkingDefaultDependencies {\n projects: Pick<FactoryProjectsStorage, 'getById'>;\n sourceControl: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n}\n\nasync function resolveFactoryProjectId(\n controller: AgentControllerRequestContext,\n deps: FactoryThinkingDefaultDependencies,\n): Promise<string | undefined> {\n const { sessionId, resourceId } = controller;\n const sourceSession = sessionId ? await deps.sourceControl.getBySessionId(sessionId) : null;\n if (sourceSession?.factoryProjectId) return sourceSession.factoryProjectId;\n const stateProjectId = (controller.getState?.() as { factoryProjectId?: unknown } | undefined)?.factoryProjectId;\n if (typeof stateProjectId === 'string' && stateProjectId) return stateProjectId;\n // Session-less Factory pages address the Factory itself: the resourceId is the project id.\n return resourceId && FACTORY_PROJECT_ID.test(resourceId) ? resourceId : undefined;\n}\n\n/**\n * The thinking level a Factory request uses when its session has not picked\n * one: the default stored on the session's Factory. Requests outside any\n * Factory get the built-in default; the host's settings.json never applies.\n */\nexport async function resolveFactoryThinkingLevel(\n requestContext: RequestContext,\n deps: FactoryThinkingDefaultDependencies,\n): Promise<ThinkingLevelSetting> {\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n if (!controller) return DEFAULT_FACTORY_THINKING_LEVEL;\n return readThinkingDefault(async () => {\n const projectId = await resolveFactoryProjectId(controller, deps);\n return projectId ? deps.projects.getById({ id: projectId }) : null;\n });\n}\n\n/** The thinking default stored on one Factory, for callers that already know which Factory a run belongs to. */\nexport function readFactoryThinkingLevel(\n projects: Pick<FactoryProjectsStorage, 'getById'>,\n factoryProjectId: string,\n): Promise<ThinkingLevelSetting> {\n return readThinkingDefault(() => projects.getById({ id: factoryProjectId }));\n}\n\nasync function readThinkingDefault(\n load: () => Promise<Pick<FactoryProject, 'defaultThinkingLevel'> | null>,\n): Promise<ThinkingLevelSetting> {\n try {\n return (await load())?.defaultThinkingLevel ?? DEFAULT_FACTORY_THINKING_LEVEL;\n } catch (error) {\n // A missed lookup costs reasoning depth, not the run.\n console.warn('[factory] Could not read the Factory thinking default; using medium.', error);\n return DEFAULT_FACTORY_THINKING_LEVEL;\n }\n}\n"],"mappings":";;;AASA,MAAM,qBAAqB,IAAI,OAAO,YAAY;AAOlD,eAAe,wBACb,YACA,MAC6B;CAC7B,MAAM,EAAE,WAAW,eAAe;CAClC,MAAM,gBAAgB,YAAY,MAAM,KAAK,cAAc,eAAe,SAAS,IAAI;CACvF,IAAI,eAAe,kBAAkB,OAAO,cAAc;CAC1D,MAAM,kBAAkB,WAAW,WAAW,EAAA,EAAkD;CAChG,IAAI,OAAO,mBAAmB,YAAY,gBAAgB,OAAO;CAEjE,OAAO,cAAc,mBAAmB,KAAK,UAAU,IAAI,aAAa,KAAA;AAC1E;;;;;;AAOA,eAAsB,4BACpB,gBACA,MAC+B;CAC/B,MAAM,aAAa,eAAe,IAAI,YAAY;CAClD,IAAI,CAAC,YAAY,OAAO;CACxB,OAAO,oBAAoB,YAAY;EACrC,MAAM,YAAY,MAAM,wBAAwB,YAAY,IAAI;EAChE,OAAO,YAAY,KAAK,SAAS,QAAQ,EAAE,IAAI,UAAU,CAAC,IAAI;CAChE,CAAC;AACH;;AAGA,SAAgB,yBACd,UACA,kBAC+B;CAC/B,OAAO,0BAA0B,SAAS,QAAQ,EAAE,IAAI,iBAAiB,CAAC,CAAC;AAC7E;AAEA,eAAe,oBACb,MAC+B;CAC/B,IAAI;EACF,QAAQ,MAAM,KAAK,EAAA,EAAI,wBAAA;CACzB,SAAS,OAAO;EAEd,QAAQ,KAAK,wEAAwE,KAAK;EAC1F,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"thinking-default.js","names":[],"sources":["../../src/session/thinking-default.ts"],"sourcesContent":["import type { ThinkingLevelSetting } from '@mastra/code-sdk/thinking';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { UUID_PATTERN } from '../routes/contracts.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { DEFAULT_FACTORY_THINKING_LEVEL } from '../storage/domains/projects/base.js';\nimport { resolveFactoryModelPolicy, routedPersonalDefaults } from './credential-policy.js';\nimport type { FactoryModelPolicyDependencies } from './credential-policy.js';\n\nconst FACTORY_PROJECT_ID = new RegExp(UUID_PATTERN);\n\ninterface FactoryThinkingDefaultDependencies extends FactoryModelPolicyDependencies {\n modelDefaults: Pick<ModelDefaultsStorage, 'ensureReady' | 'get'>;\n}\n\nfunction sessionlessFactoryProjectId(controller: AgentControllerRequestContext): string | undefined {\n const stateProjectId = (controller.getState?.() as { factoryProjectId?: unknown } | undefined)?.factoryProjectId;\n if (typeof stateProjectId === 'string' && stateProjectId) return stateProjectId;\n // Session-less Factory pages address the Factory itself: the resourceId is the project id.\n const { resourceId } = controller;\n return resourceId && FACTORY_PROJECT_ID.test(resourceId) ? resourceId : undefined;\n}\n\nasync function unsignedFactoryProjectId(\n controller: AgentControllerRequestContext,\n deps: FactoryThinkingDefaultDependencies,\n): Promise<string | undefined> {\n const sourceSession = controller.sessionId ? await deps.sourceControl.getBySessionId(controller.sessionId) : null;\n return sourceSession?.factoryProjectId ?? sessionlessFactoryProjectId(controller);\n}\n\n/** A session's unpicked thinking level: its routed personal level, else its Factory's; unsigned requests get the Factory's. */\nexport async function resolveFactoryThinkingLevel(\n requestContext: RequestContext | undefined,\n deps: FactoryThinkingDefaultDependencies,\n): Promise<ThinkingLevelSetting> {\n if (!requestContext) return DEFAULT_FACTORY_THINKING_LEVEL;\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n if (!controller) return DEFAULT_FACTORY_THINKING_LEVEL;\n try {\n const policy = await resolveFactoryModelPolicy(requestContext, deps);\n const factoryProjectId = policy\n ? (policy.factoryProjectId ?? sessionlessFactoryProjectId(controller))\n : await unsignedFactoryProjectId(controller, deps);\n const project = factoryProjectId ? await deps.projects.getById({ id: factoryProjectId }) : null;\n const factoryLevel = project?.defaultThinkingLevel ?? DEFAULT_FACTORY_THINKING_LEVEL;\n if (!policy) return factoryLevel;\n const personalDefaults = await routedPersonalDefaults(policy.tenant, deps.modelDefaults).catch(error => {\n console.warn('[factory] Could not read the personal thinking default; using the Factory level.', error);\n return null;\n });\n return personalDefaults?.thinkingLevel ?? factoryLevel;\n } catch (error) {\n // A missed lookup costs reasoning depth, not the run.\n console.warn('[factory] Could not read the thinking default; using medium.', error);\n return DEFAULT_FACTORY_THINKING_LEVEL;\n }\n}\n"],"mappings":";;;;AAUA,MAAM,qBAAqB,IAAI,OAAO,YAAY;AAMlD,SAAS,4BAA4B,YAA+D;CAClG,MAAM,kBAAkB,WAAW,WAAW,EAAA,EAAkD;CAChG,IAAI,OAAO,mBAAmB,YAAY,gBAAgB,OAAO;CAEjE,MAAM,EAAE,eAAe;CACvB,OAAO,cAAc,mBAAmB,KAAK,UAAU,IAAI,aAAa,KAAA;AAC1E;AAEA,eAAe,yBACb,YACA,MAC6B;CAE7B,QADsB,WAAW,YAAY,MAAM,KAAK,cAAc,eAAe,WAAW,SAAS,IAAI,KAAA,EACvF,oBAAoB,4BAA4B,UAAU;AAClF;;AAGA,eAAsB,4BACpB,gBACA,MAC+B;CAC/B,IAAI,CAAC,gBAAgB,OAAO;CAC5B,MAAM,aAAa,eAAe,IAAI,YAAY;CAClD,IAAI,CAAC,YAAY,OAAO;CACxB,IAAI;EACF,MAAM,SAAS,MAAM,0BAA0B,gBAAgB,IAAI;EACnE,MAAM,mBAAmB,SACpB,OAAO,oBAAoB,4BAA4B,UAAU,IAClE,MAAM,yBAAyB,YAAY,IAAI;EAEnD,MAAM,gBADU,mBAAmB,MAAM,KAAK,SAAS,QAAQ,EAAE,IAAI,iBAAiB,CAAC,IAAI,KAAA,EAC7D,wBAAA;EAC9B,IAAI,CAAC,QAAQ,OAAO;EAKpB,QAAO,MAJwB,uBAAuB,OAAO,QAAQ,KAAK,aAAa,CAAC,CAAC,OAAM,UAAS;GACtG,QAAQ,KAAK,oFAAoF,KAAK;GACtG,OAAO;EACT,CAAC,EAAA,EACwB,iBAAiB;CAC5C,SAAS,OAAO;EAEd,QAAQ,KAAK,gEAAgE,KAAK;EAClF,OAAO;CACT;AACF"}
@@ -0,0 +1,2 @@
1
+ export declare const SESSION_OWNER_SIGNAL_TAG = "factory-session-owner";
2
+ //# sourceMappingURL=session-owner-signal.d.ts.map