@mastra/factory 0.10.1 → 0.10.2-alpha.1

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 (50) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE.md +6 -4
  3. package/dist/factory.d.ts.map +1 -1
  4. package/dist/factory.js +5 -1
  5. package/dist/factory.js.map +1 -1
  6. package/dist/integrations/base.d.ts +2 -0
  7. package/dist/integrations/base.d.ts.map +1 -1
  8. package/dist/integrations/github/integration.d.ts.map +1 -1
  9. package/dist/integrations/github/integration.js +2 -1
  10. package/dist/integrations/github/integration.js.map +1 -1
  11. package/dist/integrations/github/routes.d.ts +2 -0
  12. package/dist/integrations/github/routes.d.ts.map +1 -1
  13. package/dist/integrations/github/routes.js +9 -3
  14. package/dist/integrations/github/routes.js.map +1 -1
  15. package/dist/integrations/github/webhook.d.ts.map +1 -1
  16. package/dist/integrations/github/webhook.js +9 -0
  17. package/dist/integrations/github/webhook.js.map +1 -1
  18. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  19. package/dist/integrations/platform/github/integration.js +2 -1
  20. package/dist/integrations/platform/github/integration.js.map +1 -1
  21. package/dist/integrations/slack/slack.d.ts.map +1 -1
  22. package/dist/integrations/slack/slack.js +5 -5
  23. package/dist/integrations/slack/slack.js.map +1 -1
  24. package/dist/routes/projects.d.ts +3 -0
  25. package/dist/routes/projects.d.ts.map +1 -1
  26. package/dist/routes/projects.js +1 -0
  27. package/dist/routes/projects.js.map +1 -1
  28. package/dist/routes/surface.d.ts.map +1 -1
  29. package/dist/routes/surface.js +1 -0
  30. package/dist/routes/surface.js.map +1 -1
  31. package/dist/sandbox/session-retirement.d.ts +3 -0
  32. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  33. package/dist/sandbox/session-retirement.js +8 -1
  34. package/dist/sandbox/session-retirement.js.map +1 -1
  35. package/dist/session/factory-session.d.ts.map +1 -1
  36. package/dist/session/factory-session.js +2 -0
  37. package/dist/session/factory-session.js.map +1 -1
  38. package/dist/session/memory-settings-hydration.d.ts +21 -8
  39. package/dist/session/memory-settings-hydration.d.ts.map +1 -1
  40. package/dist/session/memory-settings-hydration.js +27 -9
  41. package/dist/session/memory-settings-hydration.js.map +1 -1
  42. package/dist/session/org-seed.d.ts +67 -0
  43. package/dist/session/org-seed.d.ts.map +1 -0
  44. package/dist/session/org-seed.js +53 -0
  45. package/dist/session/org-seed.js.map +1 -0
  46. package/dist/storage/domains/work-items/base.d.ts +9 -0
  47. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  48. package/dist/storage/domains/work-items/base.js +24 -0
  49. package/dist/storage/domains/work-items/base.js.map +1 -1
  50. package/package.json +5 -5
@@ -10,6 +10,8 @@ export interface SessionRetirementCoordinatorOptions {
10
10
  }
11
11
  export interface RetireSessionInput {
12
12
  sourceControl: SourceControlStorageHandle;
13
+ /** When provided, deleting the session row also strips its work-item refs. */
14
+ workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;
13
15
  orgId: string;
14
16
  sessionId: string;
15
17
  deleteSession: boolean;
@@ -32,6 +34,7 @@ export declare class SessionRetirementCoordinator {
32
34
  }): Promise<void>;
33
35
  retireProjectRepositorySessions(options: {
34
36
  sourceControl: SourceControlStorageHandle;
37
+ workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;
35
38
  orgId: string;
36
39
  projectRepositoryId: string;
37
40
  }): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"session-retirement.d.ts","sourceRoot":"","sources":["../../src/sandbox/session-retirement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAA+C,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5F,KAAK,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,iBAAiB,GAAG,iBAAiB,CAAC,CAAC;AAC9F,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAEjF,MAAM,WAAW,mCAAmC;IAClD,KAAK,EAAE,eAAe,CAAC;IACvB,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChE,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,0BAA0B,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;CACxB;AAQD;;;;;GAKG;AACH,qBAAa,4BAA4B;;gBAM3B,OAAO,EAAE,mCAAmC;IAMlD,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,sBAAsB,CAAC,OAAO,EAAE;QACpC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QACzC,aAAa,EAAE,0BAA0B,CAAC;QAC1C,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBX,+BAA+B,CAAC,OAAO,EAAE;QAC7C,aAAa,EAAE,0BAA0B,CAAC;QAC1C,KAAK,EAAE,MAAM,CAAC;QACd,mBAAmB,EAAE,MAAM,CAAC;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC;CAwLlB"}
1
+ {"version":3,"file":"session-retirement.d.ts","sourceRoot":"","sources":["../../src/sandbox/session-retirement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAA+C,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5F,KAAK,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,iBAAiB,GAAG,iBAAiB,CAAC,CAAC;AAC9F,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAEjF,MAAM,WAAW,mCAAmC;IAClD,KAAK,EAAE,eAAe,CAAC;IACvB,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChE,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,0BAA0B,CAAC;IAC1C,8EAA8E;IAC9E,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;CACxB;AAQD;;;;;GAKG;AACH,qBAAa,4BAA4B;;gBAM3B,OAAO,EAAE,mCAAmC;IAMlD,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,sBAAsB,CAAC,OAAO,EAAE;QACpC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QACzC,aAAa,EAAE,0BAA0B,CAAC;QAC1C,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBX,+BAA+B,CAAC,OAAO,EAAE;QAC7C,aAAa,EAAE,0BAA0B,CAAC;QAC1C,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;QAC7D,KAAK,EAAE,MAAM,CAAC;QACd,mBAAmB,EAAE,MAAM,CAAC;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC;CA6LlB"}
@@ -49,6 +49,7 @@ var SessionRetirementCoordinator = class {
49
49
  const sessions = await options.sourceControl.sessions.listByProjectRepository({ projectRepositoryId: options.projectRepositoryId });
50
50
  await Promise.all(sessions.map((session) => this.retireSession({
51
51
  sourceControl: options.sourceControl,
52
+ ...options.workItems ? { workItems: options.workItems } : {},
52
53
  orgId: options.orgId,
53
54
  sessionId: session.sessionId,
54
55
  deleteSession: true
@@ -113,7 +114,13 @@ var SessionRetirementCoordinator = class {
113
114
  error: boundedError(error)
114
115
  });
115
116
  }
116
- if (input.deleteSession) await input.sourceControl.sessions.delete(session.id);
117
+ if (input.deleteSession) {
118
+ await input.workItems?.clearSessionReferences({
119
+ orgId: input.orgId,
120
+ sessionId: input.sessionId
121
+ });
122
+ await input.sourceControl.sessions.delete(session.id);
123
+ }
117
124
  }
118
125
  }
119
126
  async #releaseRemoteSandbox(sourceControl, session, sandbox) {
@@ -1 +1 @@
1
- {"version":3,"file":"session-retirement.js","names":["#fleet","#invalidateSession","#warn","#locks","#retireSession","#destroyLocalSandbox","#releaseRemoteSandbox"],"sources":["../../src/sandbox/session-retirement.ts"],"sourcesContent":["import { cleanReleasedSandbox } from '../integrations/github/sandbox-release.js';\nimport { DEFAULT_COMMAND_TIMEOUT_MS, runWorktreeTeardown } from '../integrations/github/sandbox.js';\nimport type {\n ProjectRepository,\n SourceControlSession,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { MaterializationSandbox, SandboxBindingStore, SandboxFleet } from './fleet.js';\n\ntype RetirementFleet = Pick<SandboxFleet, 'provider' | 'reattachSandbox' | 'teardownSandbox'>;\ntype WarningLogger = (message: string, details: Record<string, unknown>) => void;\n\nexport interface SessionRetirementCoordinatorOptions {\n fleet: RetirementFleet;\n invalidateSession?: (sessionId: string) => Promise<void> | void;\n warn?: WarningLogger;\n}\n\nexport interface RetireSessionInput {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n sessionId: string;\n deleteSession: boolean;\n}\n\nfunction boundedError(error: unknown): string {\n const detail = error instanceof Error ? error.message : String(error);\n if (detail.length <= 2000) return detail;\n return `${detail.slice(0, 200)}...${detail.slice(-1797)}`;\n}\n\n/**\n * Owns terminal and destructive session cleanup. Each session is serialized so\n * duplicate role bindings and competing deletion/transition requests cannot\n * run teardown concurrently. Once the sandbox binding is cleared, later calls\n * are idempotent no-ops apart from cache invalidation or requested row deletion.\n */\nexport class SessionRetirementCoordinator {\n readonly #fleet: RetirementFleet;\n readonly #invalidateSession: NonNullable<SessionRetirementCoordinatorOptions['invalidateSession']>;\n readonly #warn: WarningLogger;\n readonly #locks = new Map<string, Promise<void>>();\n\n constructor(options: SessionRetirementCoordinatorOptions) {\n this.#fleet = options.fleet;\n this.#invalidateSession = options.invalidateSession ?? (() => {});\n this.#warn = options.warn ?? ((message, details) => console.warn(`[Mastra Factory] ${message}`, details));\n }\n\n async retireSession(input: RetireSessionInput): Promise<void> {\n const previous = this.#locks.get(input.sessionId) ?? Promise.resolve();\n const current = previous.catch(() => {}).then(() => this.#retireSession(input));\n this.#locks.set(input.sessionId, current);\n try {\n await current;\n } finally {\n if (this.#locks.get(input.sessionId) === current) this.#locks.delete(input.sessionId);\n }\n }\n\n async retireWorkItemSessions(options: {\n workItems: Pick<WorkItemsStorage, 'get'>;\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n workItemId: string;\n }): Promise<void> {\n const item = await options.workItems.get({ orgId: options.orgId, id: options.workItemId });\n if (!item) return;\n const sessionIds = [...new Set(Object.values(item.sessions).map(session => session.sessionId))];\n await Promise.all(\n sessionIds.map(sessionId =>\n this.retireSession({\n sourceControl: options.sourceControl,\n orgId: options.orgId,\n sessionId,\n deleteSession: false,\n }),\n ),\n );\n }\n\n async retireProjectRepositorySessions(options: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n projectRepositoryId: string;\n }): Promise<void> {\n const sessions = await options.sourceControl.sessions.listByProjectRepository({\n projectRepositoryId: options.projectRepositoryId,\n });\n await Promise.all(\n sessions.map(session =>\n this.retireSession({\n sourceControl: options.sourceControl,\n orgId: options.orgId,\n sessionId: session.sessionId,\n deleteSession: true,\n }),\n ),\n );\n }\n\n async #retireSession(input: RetireSessionInput): Promise<void> {\n const session = await input.sourceControl.sessions.getBySessionId(input.sessionId);\n if (!session || session.orgId !== input.orgId) return;\n\n try {\n let projectRepository: ProjectRepository | null | undefined;\n try {\n projectRepository = await input.sourceControl.projectRepositories.get({\n orgId: input.orgId,\n id: session.projectRepositoryId,\n });\n } catch (error) {\n this.#warn('Factory repository settings could not be loaded for session retirement', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n let sandbox: MaterializationSandbox | undefined;\n\n if (session.sandboxId && session.sandboxWorkdir) {\n try {\n sandbox = await this.#fleet.reattachSandbox(session.sandboxId, {\n actingUserId: session.userId,\n ...(this.#fleet.provider === 'local' ? { workingDirectory: session.sandboxWorkdir } : {}),\n });\n } catch (error) {\n this.#warn('Factory session sandbox could not be reattached for retirement', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n\n if (sandbox && projectRepository?.teardownCommand) {\n try {\n await runWorktreeTeardown(sandbox, session.sandboxWorkdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (error) {\n this.#warn('Factory worktree teardown failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n }\n\n if (this.#fleet.provider === 'local') {\n await this.#destroyLocalSandbox(input.sourceControl, session, sandbox);\n } else {\n await this.#releaseRemoteSandbox(input.sourceControl, session, sandbox);\n }\n }\n } finally {\n try {\n await this.#invalidateSession(session.sessionId);\n } catch (error) {\n this.#warn('Factory session workspace cache invalidation failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n\n if (input.deleteSession) await input.sourceControl.sessions.delete(session.id);\n }\n }\n\n async #releaseRemoteSandbox(\n sourceControl: SourceControlStorageHandle,\n session: SourceControlSession,\n sandbox: MaterializationSandbox | undefined,\n ): Promise<void> {\n const sandboxId = session.sandboxId;\n const sandboxWorkdir = session.sandboxWorkdir;\n if (!sandboxId || !sandboxWorkdir) return;\n await cleanReleasedSandbox({\n fleet: this.#fleet,\n sourceControl,\n orgId: session.orgId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId,\n sandboxWorkdir,\n actingUserId: session.userId,\n ...(sandbox ? { sandbox } : {}),\n });\n try {\n await sourceControl.sandboxPool.release({\n orgId: session.orgId,\n projectRepositoryId: session.projectRepositoryId,\n userId: session.userId,\n sandboxId,\n sandboxWorkdir,\n });\n } catch (error) {\n this.#warn('Factory remote sandbox release failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId,\n error: boundedError(error),\n });\n }\n try {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir });\n } catch (error) {\n this.#warn('Factory remote sandbox binding could not be cleared', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId,\n error: boundedError(error),\n });\n }\n }\n\n async #destroyLocalSandbox(\n sourceControl: SourceControlStorageHandle,\n session: SourceControlSession,\n sandbox: MaterializationSandbox | undefined,\n ): Promise<void> {\n const sandboxWorkdir = session.sandboxWorkdir ?? '';\n const binding: SandboxBindingStore = {\n get sandboxId() {\n return session.sandboxId;\n },\n setSandboxId: async sandboxId => {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId, sandboxWorkdir });\n session.sandboxId = sandboxId;\n },\n clear: async () => {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir });\n session.sandboxId = null;\n },\n };\n try {\n await this.#fleet.teardownSandbox(binding, sandbox);\n } catch (error) {\n this.#warn('Factory local sandbox destruction failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n if (session.sandboxId) {\n try {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir });\n session.sandboxId = null;\n } catch (error) {\n this.#warn('Factory local sandbox binding could not be cleared', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-retirement.js","names":["#fleet","#invalidateSession","#warn","#locks","#retireSession","#destroyLocalSandbox","#releaseRemoteSandbox"],"sources":["../../src/sandbox/session-retirement.ts"],"sourcesContent":["import { cleanReleasedSandbox } from '../integrations/github/sandbox-release.js';\nimport { DEFAULT_COMMAND_TIMEOUT_MS, runWorktreeTeardown } from '../integrations/github/sandbox.js';\nimport type {\n ProjectRepository,\n SourceControlSession,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { MaterializationSandbox, SandboxBindingStore, SandboxFleet } from './fleet.js';\n\ntype RetirementFleet = Pick<SandboxFleet, 'provider' | 'reattachSandbox' | 'teardownSandbox'>;\ntype WarningLogger = (message: string, details: Record<string, unknown>) => void;\n\nexport interface SessionRetirementCoordinatorOptions {\n fleet: RetirementFleet;\n invalidateSession?: (sessionId: string) => Promise<void> | void;\n warn?: WarningLogger;\n}\n\nexport interface RetireSessionInput {\n sourceControl: SourceControlStorageHandle;\n /** When provided, deleting the session row also strips its work-item refs. */\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n sessionId: string;\n deleteSession: boolean;\n}\n\nfunction boundedError(error: unknown): string {\n const detail = error instanceof Error ? error.message : String(error);\n if (detail.length <= 2000) return detail;\n return `${detail.slice(0, 200)}...${detail.slice(-1797)}`;\n}\n\n/**\n * Owns terminal and destructive session cleanup. Each session is serialized so\n * duplicate role bindings and competing deletion/transition requests cannot\n * run teardown concurrently. Once the sandbox binding is cleared, later calls\n * are idempotent no-ops apart from cache invalidation or requested row deletion.\n */\nexport class SessionRetirementCoordinator {\n readonly #fleet: RetirementFleet;\n readonly #invalidateSession: NonNullable<SessionRetirementCoordinatorOptions['invalidateSession']>;\n readonly #warn: WarningLogger;\n readonly #locks = new Map<string, Promise<void>>();\n\n constructor(options: SessionRetirementCoordinatorOptions) {\n this.#fleet = options.fleet;\n this.#invalidateSession = options.invalidateSession ?? (() => {});\n this.#warn = options.warn ?? ((message, details) => console.warn(`[Mastra Factory] ${message}`, details));\n }\n\n async retireSession(input: RetireSessionInput): Promise<void> {\n const previous = this.#locks.get(input.sessionId) ?? Promise.resolve();\n const current = previous.catch(() => {}).then(() => this.#retireSession(input));\n this.#locks.set(input.sessionId, current);\n try {\n await current;\n } finally {\n if (this.#locks.get(input.sessionId) === current) this.#locks.delete(input.sessionId);\n }\n }\n\n async retireWorkItemSessions(options: {\n workItems: Pick<WorkItemsStorage, 'get'>;\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n workItemId: string;\n }): Promise<void> {\n const item = await options.workItems.get({ orgId: options.orgId, id: options.workItemId });\n if (!item) return;\n const sessionIds = [...new Set(Object.values(item.sessions).map(session => session.sessionId))];\n await Promise.all(\n sessionIds.map(sessionId =>\n this.retireSession({\n sourceControl: options.sourceControl,\n orgId: options.orgId,\n sessionId,\n deleteSession: false,\n }),\n ),\n );\n }\n\n async retireProjectRepositorySessions(options: {\n sourceControl: SourceControlStorageHandle;\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n projectRepositoryId: string;\n }): Promise<void> {\n const sessions = await options.sourceControl.sessions.listByProjectRepository({\n projectRepositoryId: options.projectRepositoryId,\n });\n await Promise.all(\n sessions.map(session =>\n this.retireSession({\n sourceControl: options.sourceControl,\n ...(options.workItems ? { workItems: options.workItems } : {}),\n orgId: options.orgId,\n sessionId: session.sessionId,\n deleteSession: true,\n }),\n ),\n );\n }\n\n async #retireSession(input: RetireSessionInput): Promise<void> {\n const session = await input.sourceControl.sessions.getBySessionId(input.sessionId);\n if (!session || session.orgId !== input.orgId) return;\n\n try {\n let projectRepository: ProjectRepository | null | undefined;\n try {\n projectRepository = await input.sourceControl.projectRepositories.get({\n orgId: input.orgId,\n id: session.projectRepositoryId,\n });\n } catch (error) {\n this.#warn('Factory repository settings could not be loaded for session retirement', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n let sandbox: MaterializationSandbox | undefined;\n\n if (session.sandboxId && session.sandboxWorkdir) {\n try {\n sandbox = await this.#fleet.reattachSandbox(session.sandboxId, {\n actingUserId: session.userId,\n ...(this.#fleet.provider === 'local' ? { workingDirectory: session.sandboxWorkdir } : {}),\n });\n } catch (error) {\n this.#warn('Factory session sandbox could not be reattached for retirement', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n\n if (sandbox && projectRepository?.teardownCommand) {\n try {\n await runWorktreeTeardown(sandbox, session.sandboxWorkdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (error) {\n this.#warn('Factory worktree teardown failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n }\n\n if (this.#fleet.provider === 'local') {\n await this.#destroyLocalSandbox(input.sourceControl, session, sandbox);\n } else {\n await this.#releaseRemoteSandbox(input.sourceControl, session, sandbox);\n }\n }\n } finally {\n try {\n await this.#invalidateSession(session.sessionId);\n } catch (error) {\n this.#warn('Factory session workspace cache invalidation failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n\n if (input.deleteSession) {\n // Refs first: clearing again is a no-op, but refs on a deleted row would dangle forever.\n await input.workItems?.clearSessionReferences({ orgId: input.orgId, sessionId: input.sessionId });\n await input.sourceControl.sessions.delete(session.id);\n }\n }\n }\n\n async #releaseRemoteSandbox(\n sourceControl: SourceControlStorageHandle,\n session: SourceControlSession,\n sandbox: MaterializationSandbox | undefined,\n ): Promise<void> {\n const sandboxId = session.sandboxId;\n const sandboxWorkdir = session.sandboxWorkdir;\n if (!sandboxId || !sandboxWorkdir) return;\n await cleanReleasedSandbox({\n fleet: this.#fleet,\n sourceControl,\n orgId: session.orgId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId,\n sandboxWorkdir,\n actingUserId: session.userId,\n ...(sandbox ? { sandbox } : {}),\n });\n try {\n await sourceControl.sandboxPool.release({\n orgId: session.orgId,\n projectRepositoryId: session.projectRepositoryId,\n userId: session.userId,\n sandboxId,\n sandboxWorkdir,\n });\n } catch (error) {\n this.#warn('Factory remote sandbox release failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId,\n error: boundedError(error),\n });\n }\n try {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir });\n } catch (error) {\n this.#warn('Factory remote sandbox binding could not be cleared', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId,\n error: boundedError(error),\n });\n }\n }\n\n async #destroyLocalSandbox(\n sourceControl: SourceControlStorageHandle,\n session: SourceControlSession,\n sandbox: MaterializationSandbox | undefined,\n ): Promise<void> {\n const sandboxWorkdir = session.sandboxWorkdir ?? '';\n const binding: SandboxBindingStore = {\n get sandboxId() {\n return session.sandboxId;\n },\n setSandboxId: async sandboxId => {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId, sandboxWorkdir });\n session.sandboxId = sandboxId;\n },\n clear: async () => {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir });\n session.sandboxId = null;\n },\n };\n try {\n await this.#fleet.teardownSandbox(binding, sandbox);\n } catch (error) {\n this.#warn('Factory local sandbox destruction failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n if (session.sandboxId) {\n try {\n await sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir });\n session.sandboxId = null;\n } catch (error) {\n this.#warn('Factory local sandbox binding could not be cleared', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n sandboxId: session.sandboxId,\n error: boundedError(error),\n });\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -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 { seedSessionOrg } from "./org-seed.js";
1
2
  import { factoryMemorySettingsUserId } from "../storage/domains/memory-settings/base.js";
2
3
  import { applyStoredMemorySettings } from "./memory-settings-hydration.js";
3
4
  import { randomUUID } from "crypto";
@@ -150,6 +151,7 @@ async function ensureFactorySourceSession(args) {
150
151
  * default it was created with, and the reason is logged.
151
152
  */
152
153
  async function hydrateFactorySession(session, args) {
154
+ await seedSessionOrg(session, args.orgId);
153
155
  try {
154
156
  const record = args.memorySettings && args.factoryProjectId ? await args.memorySettings.get({
155
157
  orgId: args.orgId,
@@ -1 +1 @@
1
- {"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { resolveProviderOMDefault } from '@mastra/code-sdk/onboarding/packs';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { factoryMemorySettingsUserId } from '../storage/domains/memory-settings/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { applyStoredMemorySettings } from './memory-settings-hydration.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\n/**\n * Read the factory project's default model. Best-effort: a missing project or an\n * uninitialized storage domain means \"no default\", never a failed run.\n */\nexport async function resolveFactoryDefaultModelId(\n projects: FactoryProjectsStorage | undefined,\n factoryProjectId: string | undefined,\n): Promise<string | undefined> {\n if (!projects || !factoryProjectId) return undefined;\n try {\n const project = await projects.getById({ id: factoryProjectId });\n return project?.defaultModelId ?? undefined;\n } catch {\n return undefined;\n }\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 /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: 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}\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 resolveFactorySourceRepository}. A miss carries which step\n * failed: callers differ on whether that is an error (an autonomous run cannot\n * proceed) or a routine fallback (a chat integration drops to a chat-only\n * session), and the two steps fail for different reasons worth reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * Resolve which repository a factory project's source-control runs act on: the\n * owner's connection on the project, then one of its linked repositories.\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 resolveFactorySourceRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, repositorySlug } = args;\n\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n const candidates = connections.filter(candidate => candidate.integrationId === sourceControl.integrationId);\n if (candidates.length === 0) return { found: false, reason: 'connection' };\n\n // A project can carry stale connections: a provider-app reinstall leaves the\n // old connection pointing at an installation that no longer exists, and that\n // row can sit ahead of the healthy one. Try every candidate and skip the ones\n // that no longer resolve rather than failing on the first.\n for (const connection of candidates) {\n let resolved;\n try {\n const projectRepositories = await sourceControl.projectRepositories.list({ orgId, connectionId: connection.id });\n const resolvedRepositories = await Promise.all(\n projectRepositories.map(async projectRepository => ({\n projectRepository,\n repository: await sourceControl.repositories.get({ orgId, id: projectRepository.repositoryId }),\n })),\n );\n resolved = resolvedRepositories.find(\n candidate => candidate.repository && (!repositorySlug || candidate.repository.slug === repositorySlug),\n );\n } catch {\n // The connection no longer resolves (e.g. its installation was deleted).\n continue;\n }\n if (!resolved?.repository) continue;\n\n return {\n found: true,\n projectRepositoryId: resolved.projectRepository.id,\n baseBranch: resolved.projectRepository.branch ?? resolved.repository.defaultBranch,\n connectedByUserId: connection.createdByUserId,\n };\n }\n\n return { found: false, reason: 'repository' };\n}\n\n/**\n * Walk a Factory user-session id back to the project it belongs to.\n *\n * Repo-backed channel threads are keyed by their Factory session id, which is\n * the only handle a session-start hook gets. This turns that id back into the\n * project whose configuration the session should adopt. Durable by\n * construction — it reads the same rows the session was created from, so it\n * survives restarts without any in-memory mapping.\n */\nexport async function resolveFactoryProjectForSession(args: {\n sourceControl: SourceControlStorageHandle;\n sessionId: string;\n}): Promise<{ factoryProjectId: string; orgId: string; userId: string } | null> {\n const { sourceControl, sessionId } = args;\n\n const session = await sourceControl.sessions.getBySessionId(sessionId);\n if (!session) return null;\n const projectRepository = await sourceControl.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) return null;\n const connection = await sourceControl.connections.get({ orgId: session.orgId, id: projectRepository.connectionId });\n if (!connection) return null;\n\n return { factoryProjectId: connection.factoryProjectId, orgId: session.orgId, userId: session.userId };\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, one of\n * its linked repositories, and a session on the requested branch with the\n * repository's pinned or default branch as the base.\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, repositorySlug } = args;\n\n const resolved = await resolveFactorySourceRepository({ sourceControl, orgId, factoryProjectId, repositorySlug });\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: randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: 'org',\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 /**\n * The factory project whose shared memory settings apply. Factory sessions\n * never read an individual user's personal memory settings — the project's\n * own row (or the built-in defaults) is what they run with.\n */\n factoryProjectId?: string;\n /** The factory project's default model. Without it the session keeps the SDK's built-in mode default. */\n defaultModelId?: string;\n /**\n * When provided, the factory project's stored memory-settings row is\n * applied. When omitted (or no row exists) the session is reset to the\n * built-in memory defaults.\n */\n memorySettings?: MemorySettingsStorage;\n}\n\n/**\n * Apply a factory project's configuration to a freshly created session:\n * observational-memory settings, then the project's default model.\n *\n * Both steps are best-effort. A retired model id or an unreachable settings row\n * must not sink a run that is otherwise ready — the session simply keeps the\n * default it was created with, and the reason is logged.\n */\nexport async function hydrateFactorySession(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void> {\n try {\n const record =\n args.memorySettings && args.factoryProjectId\n ? await args.memorySettings.get({\n orgId: args.orgId,\n userId: factoryMemorySettingsUserId(args.factoryProjectId),\n })\n : null;\n // Without a stored row, fall back to the low-cost OM model of the factory\n // default model's provider — a factory connected only to Anthropic should\n // not observe with the (uncredentialed) built-in Google default.\n const provider = args.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider ? resolveProviderOMDefault(provider, args.defaultModelId).modelId : undefined;\n await applyStoredMemorySettings(session, record, fallbackOmModelId);\n } catch (error) {\n console.warn('[Factory Start] Failed to apply observational-memory settings', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (args.defaultModelId) {\n try {\n await session.model.switch({ modelId: args.defaultModelId });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default model', {\n modelId: args.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { resolveProviderOMDefault } from '@mastra/code-sdk/onboarding/packs';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { factoryMemorySettingsUserId } from '../storage/domains/memory-settings/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { applyStoredMemorySettings } from './memory-settings-hydration.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\n/**\n * Read the factory project's default model. Best-effort: a missing project or an\n * uninitialized storage domain means \"no default\", never a failed run.\n */\nexport async function resolveFactoryDefaultModelId(\n projects: FactoryProjectsStorage | undefined,\n factoryProjectId: string | undefined,\n): Promise<string | undefined> {\n if (!projects || !factoryProjectId) return undefined;\n try {\n const project = await projects.getById({ id: factoryProjectId });\n return project?.defaultModelId ?? undefined;\n } catch {\n return undefined;\n }\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 /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: 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}\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 resolveFactorySourceRepository}. A miss carries which step\n * failed: callers differ on whether that is an error (an autonomous run cannot\n * proceed) or a routine fallback (a chat integration drops to a chat-only\n * session), and the two steps fail for different reasons worth reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * Resolve which repository a factory project's source-control runs act on: the\n * owner's connection on the project, then one of its linked repositories.\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 resolveFactorySourceRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n /** Pick a specific linked repository by slug. Defaults to the first linked repository. */\n repositorySlug?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, repositorySlug } = args;\n\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n const candidates = connections.filter(candidate => candidate.integrationId === sourceControl.integrationId);\n if (candidates.length === 0) return { found: false, reason: 'connection' };\n\n // A project can carry stale connections: a provider-app reinstall leaves the\n // old connection pointing at an installation that no longer exists, and that\n // row can sit ahead of the healthy one. Try every candidate and skip the ones\n // that no longer resolve rather than failing on the first.\n for (const connection of candidates) {\n let resolved;\n try {\n const projectRepositories = await sourceControl.projectRepositories.list({ orgId, connectionId: connection.id });\n const resolvedRepositories = await Promise.all(\n projectRepositories.map(async projectRepository => ({\n projectRepository,\n repository: await sourceControl.repositories.get({ orgId, id: projectRepository.repositoryId }),\n })),\n );\n resolved = resolvedRepositories.find(\n candidate => candidate.repository && (!repositorySlug || candidate.repository.slug === repositorySlug),\n );\n } catch {\n // The connection no longer resolves (e.g. its installation was deleted).\n continue;\n }\n if (!resolved?.repository) continue;\n\n return {\n found: true,\n projectRepositoryId: resolved.projectRepository.id,\n baseBranch: resolved.projectRepository.branch ?? resolved.repository.defaultBranch,\n connectedByUserId: connection.createdByUserId,\n };\n }\n\n return { found: false, reason: 'repository' };\n}\n\n/**\n * Walk a Factory user-session id back to the project it belongs to.\n *\n * Repo-backed channel threads are keyed by their Factory session id, which is\n * the only handle a session-start hook gets. This turns that id back into the\n * project whose configuration the session should adopt. Durable by\n * construction — it reads the same rows the session was created from, so it\n * survives restarts without any in-memory mapping.\n */\nexport async function resolveFactoryProjectForSession(args: {\n sourceControl: SourceControlStorageHandle;\n sessionId: string;\n}): Promise<{ factoryProjectId: string; orgId: string; userId: string } | null> {\n const { sourceControl, sessionId } = args;\n\n const session = await sourceControl.sessions.getBySessionId(sessionId);\n if (!session) return null;\n const projectRepository = await sourceControl.projectRepositories.get({\n orgId: session.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) return null;\n const connection = await sourceControl.connections.get({ orgId: session.orgId, id: projectRepository.connectionId });\n if (!connection) return null;\n\n return { factoryProjectId: connection.factoryProjectId, orgId: session.orgId, userId: session.userId };\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, one of\n * its linked repositories, and a session on the requested branch with the\n * repository's pinned or default branch as the base.\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, repositorySlug } = args;\n\n const resolved = await resolveFactorySourceRepository({ sourceControl, orgId, factoryProjectId, repositorySlug });\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: randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: 'org',\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 /**\n * The factory project whose shared memory settings apply. Factory sessions\n * never read an individual user's personal memory settings — the project's\n * own row (or the built-in defaults) is what they run with.\n */\n factoryProjectId?: string;\n /** The factory project's default model. Without it the session keeps the SDK's built-in mode default. */\n defaultModelId?: string;\n /**\n * When provided, the factory project's stored memory-settings row is\n * applied. When omitted (or no row exists) the session is reset to the\n * built-in memory defaults.\n */\n memorySettings?: MemorySettingsStorage;\n}\n\n/**\n * Apply a factory project's configuration to a freshly created session:\n * observational-memory settings, then the project's default model.\n *\n * Both steps are best-effort. A retired model id or an unreachable settings row\n * must not sink a run that is otherwise ready — the session simply keeps the\n * default it was created with, and the reason is logged.\n */\nexport async function hydrateFactorySession(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void> {\n // The org rung knowledge capture 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 try {\n const record =\n args.memorySettings && args.factoryProjectId\n ? await args.memorySettings.get({\n orgId: args.orgId,\n userId: factoryMemorySettingsUserId(args.factoryProjectId),\n })\n : null;\n // Without a stored row, fall back to the low-cost OM model of the factory\n // default model's provider — a factory connected only to Anthropic should\n // not observe with the (uncredentialed) built-in Google default.\n const provider = args.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider ? resolveProviderOMDefault(provider, args.defaultModelId).modelId : undefined;\n await applyStoredMemorySettings(session, record, fallbackOmModelId);\n } catch (error) {\n console.warn('[Factory Start] Failed to apply observational-memory settings', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n if (args.defaultModelId) {\n try {\n await session.model.switch({ modelId: args.defaultModelId });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default model', {\n modelId: args.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n"],"mappings":"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"}
@@ -21,6 +21,7 @@ interface OMStateWrites {
21
21
  observationThreshold?: number;
22
22
  reflectionThreshold?: number;
23
23
  observeAttachments?: 'auto' | boolean;
24
+ factoryOrgId?: string;
24
25
  }
25
26
  /** The slice of a session needed to apply stored observational-memory settings. */
26
27
  export interface OMConfigurableSession {
@@ -56,15 +57,27 @@ export interface MemorySettingsHydrationDependencies {
56
57
  memorySettings: Pick<MemorySettingsStorage, 'get'>;
57
58
  }
58
59
  /**
59
- * Seed a freshly created controller session's observational-memory settings
60
- * from the owner's stored `memory-settings` row. Registered as a blocking
61
- * session-created listener so the seed lands before the caller can start a run.
60
+ * Seed a freshly created controller session's tenant org and its
61
+ * observational-memory settings from the owner's source-control row. Registered
62
+ * as a blocking session-created listener so the seed lands before the caller can
63
+ * start a run.
62
64
  *
63
- * Sessions tagged `factoryProjectId` (work/review runs, created with that tag)
64
- * hydrate through the start coordinator; sessions without a GitHub
65
- * source-control row (e.g. chat-only channel sessions) hydrate through
66
- * `hydrateFactorySession` with their own resolved tenant. Both are skipped
67
- * here. Best-effort: failures are logged, never thrown.
65
+ * The org seed matters beyond settings. Subconscious knowledge capture scopes
66
+ * every node and record on `factoryOrgId`; before the SDK refusal guard,
67
+ * missing it made capture substitute the session owner id. For web chat sessions
68
+ * that is the agent controller's own id rather than a tenant, so captured
69
+ * knowledge landed under an org rung no reader ever queries. Same rule as the
70
+ * start coordinator: the org
71
+ * comes from the row the session was created from, never improvised from an
72
+ * owner id.
73
+ *
74
+ * Memory settings for sessions tagged `factoryProjectId` (work/review runs) are
75
+ * owned by the start coordinator, and sessions without a GitHub source-control
76
+ * row (e.g. chat-only channel sessions) hydrate through `hydrateFactorySession`
77
+ * with their own resolved tenant; both are skipped here. The org seed is not
78
+ * skipped on the tag alone: a web chat session persists `factoryProjectId` from
79
+ * its browser seed, so on resume it carries the tag without ever having been
80
+ * through the coordinator. Best-effort: failures are logged, never thrown.
68
81
  */
69
82
  export declare function hydrateSessionMemorySettings(session: MemorySettingsHydrationSession, { sourceControl, memorySettings }: MemorySettingsHydrationDependencies): Promise<void>;
70
83
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"memory-settings-hydration.d.ts","sourceRoot":"","sources":["../../src/session/memory-settings-hydration.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAC9G,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAE5F,yDAAyD;AACzD,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,2DAA2D;AAC3D,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED;;;;GAIG;AACH,UAAU,aAAa;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,mBAAmB,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACvC;AAED,mFAAmF;AACnF,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE;QAAE,QAAQ,EAAE,WAAW,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,CAAC;IACtD,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;QAC/C,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KACvD,CAAC;CACH;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED,MAAM,WAAW,8BAA+B,SAAQ,qBAAqB;IAC3E,QAAQ,CAAC,QAAQ,EAAE;QAAE,aAAa,IAAI,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,MAAM,WAAW,mCAAmC;IAClD,4FAA4F;IAC5F,aAAa,EAAE;QACb,QAAQ,EAAE,IAAI,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;KAC1E,CAAC;IACF,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;CACpD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,EACvC,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,mCAAmC,GACrE,OAAO,CAAC,IAAI,CAAC,CAUf"}
1
+ {"version":3,"file":"memory-settings-hydration.d.ts","sourceRoot":"","sources":["../../src/session/memory-settings-hydration.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAC9G,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAG5F,yDAAyD;AACzD,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,2DAA2D;AAC3D,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED;;;;GAIG;AACH,UAAU,aAAa;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,mBAAmB,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mFAAmF;AACnF,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE;QAAE,QAAQ,EAAE,WAAW,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,CAAC;IACtD,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;QAC/C,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KACvD,CAAC;CACH;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED,MAAM,WAAW,8BAA+B,SAAQ,qBAAqB;IAC3E,QAAQ,CAAC,QAAQ,EAAE;QAAE,aAAa,IAAI,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,MAAM,WAAW,mCAAmC;IAClD,4FAA4F;IAC5F,aAAa,EAAE;QACb,QAAQ,EAAE,IAAI,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;KAC1E,CAAC;IACF,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,EACvC,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,mCAAmC,GACrE,OAAO,CAAC,IAAI,CAAC,CAsBf"}
@@ -1,3 +1,4 @@
1
+ import { hasResolvedOrg, seedSessionOrg } from "./org-seed.js";
1
2
  import { DEFAULT_OM_MODEL_ID } from "@mastra/code-sdk/constants";
2
3
  //#region src/session/memory-settings-hydration.ts
3
4
  /** Default thresholds mirror the TUI `/om` fallbacks. */
@@ -28,27 +29,44 @@ async function applyStoredMemorySettings(session, record, fallbackOmModelId) {
28
29
  if (Object.keys(updates).length > 0) await session.state.set(updates);
29
30
  }
30
31
  /**
31
- * Seed a freshly created controller session's observational-memory settings
32
- * from the owner's stored `memory-settings` row. Registered as a blocking
33
- * session-created listener so the seed lands before the caller can start a run.
32
+ * Seed a freshly created controller session's tenant org and its
33
+ * observational-memory settings from the owner's source-control row. Registered
34
+ * as a blocking session-created listener so the seed lands before the caller can
35
+ * start a run.
34
36
  *
35
- * Sessions tagged `factoryProjectId` (work/review runs, created with that tag)
36
- * hydrate through the start coordinator; sessions without a GitHub
37
- * source-control row (e.g. chat-only channel sessions) hydrate through
38
- * `hydrateFactorySession` with their own resolved tenant. Both are skipped
39
- * here. Best-effort: failures are logged, never thrown.
37
+ * The org seed matters beyond settings. Subconscious knowledge capture scopes
38
+ * every node and record on `factoryOrgId`; before the SDK refusal guard,
39
+ * missing it made capture substitute the session owner id. For web chat sessions
40
+ * that is the agent controller's own id rather than a tenant, so captured
41
+ * knowledge landed under an org rung no reader ever queries. Same rule as the
42
+ * start coordinator: the org
43
+ * comes from the row the session was created from, never improvised from an
44
+ * owner id.
45
+ *
46
+ * Memory settings for sessions tagged `factoryProjectId` (work/review runs) are
47
+ * owned by the start coordinator, and sessions without a GitHub source-control
48
+ * row (e.g. chat-only channel sessions) hydrate through `hydrateFactorySession`
49
+ * with their own resolved tenant; both are skipped here. The org seed is not
50
+ * skipped on the tag alone: a web chat session persists `factoryProjectId` from
51
+ * its browser seed, so on resume it carries the tag without ever having been
52
+ * through the coordinator. Best-effort: failures are logged, never thrown.
40
53
  */
41
54
  async function hydrateSessionMemorySettings(session, { sourceControl, memorySettings }) {
42
- if (session.state.get()?.factoryProjectId) return;
55
+ const state = session.state.get() ?? {};
56
+ const isFactoryRun = Boolean(state.factoryProjectId);
57
+ if (isFactoryRun && hasResolvedOrg(state.factoryOrgId)) return;
43
58
  try {
44
59
  const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());
60
+ await seedSessionOrg(session, record?.orgId);
45
61
  if (!record) return;
62
+ if (isFactoryRun) return;
46
63
  await applyStoredMemorySettings(session, await memorySettings.get({
47
64
  orgId: record.orgId,
48
65
  userId: record.userId
49
66
  }));
50
67
  } catch (error) {
51
68
  console.warn("[Factory memory-settings hydration] Unable to apply stored memory settings.", error);
69
+ if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, void 0);
52
70
  }
53
71
  }
54
72
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"memory-settings-hydration.js","names":[],"sources":["../../src/session/memory-settings-hydration.ts"],"sourcesContent":["import { DEFAULT_OM_MODEL_ID } from '@mastra/code-sdk/constants';\n\nimport type { MemorySettingsRecord, MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\n\n/** Default thresholds mirror the TUI `/om` fallbacks. */\nexport const DEFAULT_OBSERVATION_THRESHOLD = 30_000;\nexport const DEFAULT_REFLECTION_THRESHOLD = 40_000;\n\n/** One observational-memory role's read/switch surface. */\ninterface OMRoleSlice {\n modelId: () => string | undefined;\n switchModel: (args: { modelId: string }) => Promise<unknown>;\n}\n\n/**\n * Session-state fields memory-settings hydration writes. The index signatures\n * mirror `MastraCodeState` so the concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\ninterface OMStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n}\n\n/** The slice of a session needed to apply stored observational-memory settings. */\nexport interface OMConfigurableSession {\n om: { observer: OMRoleSlice; reflector: OMRoleSlice };\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OMStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * Apply a stored memory-settings row onto a session, so the DB — not whatever\n * happens to sit in persisted session state (e.g. a stale boot-time seed from\n * before memory settings moved to the DB) — is what the web surface reads and\n * what the session's OM actually runs with. The row is authoritative: knobs\n * without a stored value reset to the built-in defaults. This is the single\n * application path shared by the settings routes, coordinator hydration, and\n * the web session boot seed.\n */\nexport async function applyStoredMemorySettings(\n session: OMConfigurableSession,\n record: MemorySettingsRecord | null,\n fallbackOmModelId?: string,\n): Promise<void> {\n for (const role of ['observer', 'reflector'] as const) {\n const stored = role === 'observer' ? record?.observerModelId : record?.reflectorModelId;\n const target = stored ?? fallbackOmModelId ?? DEFAULT_OM_MODEL_ID;\n if (session.om[role].modelId() !== target) {\n await session.om[role].switchModel({ modelId: target });\n }\n }\n const state = session.state.get() ?? {};\n const updates: OMStateWrites = {};\n const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;\n if (state.observationThreshold !== observationThreshold) {\n updates.observationThreshold = observationThreshold;\n }\n const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;\n if (state.reflectionThreshold !== reflectionThreshold) {\n updates.reflectionThreshold = reflectionThreshold;\n }\n const observeAttachments = record?.observeAttachments ?? 'auto';\n if ((state.observeAttachments ?? 'auto') !== observeAttachments) {\n updates.observeAttachments = observeAttachments;\n }\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n}\n\nexport interface MemorySettingsHydrationSession extends OMConfigurableSession {\n readonly identity: { getResourceId(): string };\n}\n\nexport interface MemorySettingsHydrationDependencies {\n /** GitHub-integration source-control rows — the only creator of web user sessions today. */\n sourceControl: {\n sessions: Pick<SourceControlStorageHandle['sessions'], 'getBySessionId'>;\n };\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Seed a freshly created controller session's observational-memory settings\n * from the owner's stored `memory-settings` row. Registered as a blocking\n * session-created listener so the seed lands before the caller can start a run.\n *\n * Sessions tagged `factoryProjectId` (work/review runs, created with that tag)\n * hydrate through the start coordinator; sessions without a GitHub\n * source-control row (e.g. chat-only channel sessions) hydrate through\n * `hydrateFactorySession` with their own resolved tenant. Both are skipped\n * here. Best-effort: failures are logged, never thrown.\n */\nexport async function hydrateSessionMemorySettings(\n session: MemorySettingsHydrationSession,\n { sourceControl, memorySettings }: MemorySettingsHydrationDependencies,\n): Promise<void> {\n if (session.state.get()?.factoryProjectId) return;\n try {\n const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());\n if (!record) return;\n const settings = await memorySettings.get({ orgId: record.orgId, userId: record.userId });\n await applyStoredMemorySettings(session, settings);\n } catch (error) {\n console.warn('[Factory memory-settings hydration] Unable to apply stored memory settings.', error);\n }\n}\n"],"mappings":";;;AAMA,MAAa,gCAAgC;AAC7C,MAAa,+BAA+B;;;;;;;;;;AAuC5C,eAAsB,0BACpB,SACA,QACA,mBACe;CACf,KAAK,MAAM,QAAQ,CAAC,YAAY,WAAW,GAAY;EAErD,MAAM,UADS,SAAS,aAAa,QAAQ,kBAAkB,QAAQ,qBAC9C,qBAAqB;EAC9C,IAAI,QAAQ,GAAG,KAAK,CAAC,QAAQ,MAAM,QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,EAAE,SAAS,OAAO,CAAC;CAE1D;CACA,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,UAAyB,CAAC;CAChC,MAAM,uBAAuB,QAAQ,wBAAA;CACrC,IAAI,MAAM,yBAAyB,sBACjC,QAAQ,uBAAuB;CAEjC,MAAM,sBAAsB,QAAQ,uBAAA;CACpC,IAAI,MAAM,wBAAwB,qBAChC,QAAQ,sBAAsB;CAEhC,MAAM,qBAAqB,QAAQ,sBAAsB;CACzD,KAAK,MAAM,sBAAsB,YAAY,oBAC3C,QAAQ,qBAAqB;CAE/B,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,MAAM,QAAQ,MAAM,IAAI,OAAO;AACtE;;;;;;;;;;;;AAyBA,eAAsB,6BACpB,SACA,EAAE,eAAe,kBACF;CACf,IAAI,QAAQ,MAAM,IAAI,CAAC,EAAE,kBAAkB;CAC3C,IAAI;EACF,MAAM,SAAS,MAAM,cAAc,SAAS,eAAe,QAAQ,SAAS,cAAc,CAAC;EAC3F,IAAI,CAAC,QAAQ;EAEb,MAAM,0BAA0B,SAAS,MADlB,eAAe,IAAI;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;EAAO,CAAC,CACvC;CACnD,SAAS,OAAO;EACd,QAAQ,KAAK,+EAA+E,KAAK;CACnG;AACF"}
1
+ {"version":3,"file":"memory-settings-hydration.js","names":[],"sources":["../../src/session/memory-settings-hydration.ts"],"sourcesContent":["import { DEFAULT_OM_MODEL_ID } from '@mastra/code-sdk/constants';\n\nimport type { MemorySettingsRecord, MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { hasResolvedOrg, seedSessionOrg } from './org-seed.js';\n\n/** Default thresholds mirror the TUI `/om` fallbacks. */\nexport const DEFAULT_OBSERVATION_THRESHOLD = 30_000;\nexport const DEFAULT_REFLECTION_THRESHOLD = 40_000;\n\n/** One observational-memory role's read/switch surface. */\ninterface OMRoleSlice {\n modelId: () => string | undefined;\n switchModel: (args: { modelId: string }) => Promise<unknown>;\n}\n\n/**\n * Session-state fields memory-settings hydration writes. The index signatures\n * mirror `MastraCodeState` so the concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\ninterface OMStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n factoryOrgId?: string;\n}\n\n/** The slice of a session needed to apply stored observational-memory settings. */\nexport interface OMConfigurableSession {\n om: { observer: OMRoleSlice; reflector: OMRoleSlice };\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OMStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * Apply a stored memory-settings row onto a session, so the DB — not whatever\n * happens to sit in persisted session state (e.g. a stale boot-time seed from\n * before memory settings moved to the DB) — is what the web surface reads and\n * what the session's OM actually runs with. The row is authoritative: knobs\n * without a stored value reset to the built-in defaults. This is the single\n * application path shared by the settings routes, coordinator hydration, and\n * the web session boot seed.\n */\nexport async function applyStoredMemorySettings(\n session: OMConfigurableSession,\n record: MemorySettingsRecord | null,\n fallbackOmModelId?: string,\n): Promise<void> {\n for (const role of ['observer', 'reflector'] as const) {\n const stored = role === 'observer' ? record?.observerModelId : record?.reflectorModelId;\n const target = stored ?? fallbackOmModelId ?? DEFAULT_OM_MODEL_ID;\n if (session.om[role].modelId() !== target) {\n await session.om[role].switchModel({ modelId: target });\n }\n }\n const state = session.state.get() ?? {};\n const updates: OMStateWrites = {};\n const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;\n if (state.observationThreshold !== observationThreshold) {\n updates.observationThreshold = observationThreshold;\n }\n const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;\n if (state.reflectionThreshold !== reflectionThreshold) {\n updates.reflectionThreshold = reflectionThreshold;\n }\n const observeAttachments = record?.observeAttachments ?? 'auto';\n if ((state.observeAttachments ?? 'auto') !== observeAttachments) {\n updates.observeAttachments = observeAttachments;\n }\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n}\n\nexport interface MemorySettingsHydrationSession extends OMConfigurableSession {\n readonly identity: { getResourceId(): string };\n}\n\nexport interface MemorySettingsHydrationDependencies {\n /** GitHub-integration source-control rows — the only creator of web user sessions today. */\n sourceControl: {\n sessions: Pick<SourceControlStorageHandle['sessions'], 'getBySessionId'>;\n };\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Seed a freshly created controller session's tenant org and its\n * observational-memory settings from the owner's source-control row. Registered\n * as a blocking session-created listener so the seed lands before the caller can\n * start a run.\n *\n * The org seed matters beyond settings. Subconscious knowledge capture scopes\n * every node and record on `factoryOrgId`; before the SDK refusal guard,\n * missing it made capture substitute the session owner id. For web chat sessions\n * that is the agent controller's own id rather than a tenant, so captured\n * knowledge landed under an org rung no reader ever queries. Same rule as the\n * start coordinator: the org\n * comes from the row the session was created from, never improvised from an\n * owner id.\n *\n * Memory settings for sessions tagged `factoryProjectId` (work/review runs) are\n * owned by the start coordinator, and sessions without a GitHub source-control\n * row (e.g. chat-only channel sessions) hydrate through `hydrateFactorySession`\n * with their own resolved tenant; both are skipped here. The org seed is not\n * skipped on the tag alone: a web chat session persists `factoryProjectId` from\n * its browser seed, so on resume it carries the tag without ever having been\n * through the coordinator. Best-effort: failures are logged, never thrown.\n */\nexport async function hydrateSessionMemorySettings(\n session: MemorySettingsHydrationSession,\n { sourceControl, memorySettings }: MemorySettingsHydrationDependencies,\n): Promise<void> {\n const state = session.state.get() ?? {};\n const isFactoryRun = Boolean(state.factoryProjectId);\n // A coordinator-hydrated run already carries both halves. Nothing to add.\n if (isFactoryRun && hasResolvedOrg(state.factoryOrgId)) return;\n try {\n const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());\n // No row, or a row whose org is blank, leaves the session with no tenant.\n // Mark it rather than returning silently: an unmarked projectless factory\n // session is indistinguishable from a local one, and capture would file it\n // under the local scope — the same bug wearing a different rung.\n await seedSessionOrg(session, record?.orgId);\n if (!record) return;\n if (isFactoryRun) return;\n const settings = await memorySettings.get({ orgId: record.orgId, userId: record.userId });\n await applyStoredMemorySettings(session, settings);\n } catch (error) {\n console.warn('[Factory memory-settings hydration] Unable to apply stored memory settings.', error);\n // A failed lookup is an unresolved org, not an absent one — unless the seed\n // already landed and a later step is what threw.\n if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, undefined);\n }\n}\n"],"mappings":";;;;AAOA,MAAa,gCAAgC;AAC7C,MAAa,+BAA+B;;;;;;;;;;AAwC5C,eAAsB,0BACpB,SACA,QACA,mBACe;CACf,KAAK,MAAM,QAAQ,CAAC,YAAY,WAAW,GAAY;EAErD,MAAM,UADS,SAAS,aAAa,QAAQ,kBAAkB,QAAQ,qBAC9C,qBAAqB;EAC9C,IAAI,QAAQ,GAAG,KAAK,CAAC,QAAQ,MAAM,QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,EAAE,SAAS,OAAO,CAAC;CAE1D;CACA,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,UAAyB,CAAC;CAChC,MAAM,uBAAuB,QAAQ,wBAAA;CACrC,IAAI,MAAM,yBAAyB,sBACjC,QAAQ,uBAAuB;CAEjC,MAAM,sBAAsB,QAAQ,uBAAA;CACpC,IAAI,MAAM,wBAAwB,qBAChC,QAAQ,sBAAsB;CAEhC,MAAM,qBAAqB,QAAQ,sBAAsB;CACzD,KAAK,MAAM,sBAAsB,YAAY,oBAC3C,QAAQ,qBAAqB;CAE/B,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,MAAM,QAAQ,MAAM,IAAI,OAAO;AACtE;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,eAAsB,6BACpB,SACA,EAAE,eAAe,kBACF;CACf,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,eAAe,QAAQ,MAAM,gBAAgB;CAEnD,IAAI,gBAAgB,eAAe,MAAM,YAAY,GAAG;CACxD,IAAI;EACF,MAAM,SAAS,MAAM,cAAc,SAAS,eAAe,QAAQ,SAAS,cAAc,CAAC;EAK3F,MAAM,eAAe,SAAS,QAAQ,KAAK;EAC3C,IAAI,CAAC,QAAQ;EACb,IAAI,cAAc;EAElB,MAAM,0BAA0B,SAAS,MADlB,eAAe,IAAI;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;EAAO,CAAC,CACvC;CACnD,SAAS,OAAO;EACd,QAAQ,KAAK,+EAA+E,KAAK;EAGjG,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,EAAE,cAAc,MAAM,eAAe,SAAS,KAAA,CAAS;CACjF;AACF"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The tenant organization a session's knowledge is scoped to.
3
+ *
4
+ * Subconscious knowledge capture scopes every node and record on
5
+ * `factoryOrgId`. A session that reaches the capture seam without one used to
6
+ * fall back to the session owner id — for factory sessions the agent
7
+ * controller's own id — so the knowledge landed under an org rung no reader
8
+ * ever queries. The write succeeded and the read could never see it.
9
+ *
10
+ * The fix is that every session-creation path seeds the org it already holds,
11
+ * and a path that cannot resolve one marks the session `factoryOrgUnresolved`
12
+ * so the capture side refuses loudly instead of inventing an identity. "No
13
+ * project id" is not a proxy for "not a factory session" — chat sessions and
14
+ * Slack channel sessions are factory-owned and carry no project id — which is
15
+ * why the unresolved case needs its own explicit marker.
16
+ */
17
+ /**
18
+ * Session-state fields org seeding writes. The index signatures mirror
19
+ * `MastraCodeState` so a concrete `Session.state.set(Partial<MastraCodeState>)`
20
+ * stays assignable to this minimal surface (contravariant parameter check).
21
+ */
22
+ export interface OrgSeedStateWrites {
23
+ [key: string]: unknown;
24
+ [key: `subagentModelId_${string}`]: string | undefined;
25
+ factoryOrgId?: string;
26
+ factoryOrgUnresolved?: boolean;
27
+ }
28
+ /** The slice of a session needed to seed its organization. */
29
+ export interface OrgSeedableSession {
30
+ state: {
31
+ get: () => Record<string, unknown> | undefined;
32
+ set: (updates: OrgSeedStateWrites) => Promise<void> | void;
33
+ };
34
+ }
35
+ /**
36
+ * A request context carrying the tenant on its `user` key. Slack stamps
37
+ * `{ id, organizationId }` and the GitHub webhook `{ workosId, organizationId }`,
38
+ * so only the shared `organizationId` field may be read here.
39
+ */
40
+ export interface OrgBearingRequestContext {
41
+ get: (key: string) => unknown;
42
+ }
43
+ /** Read the tenant org off a request context's `user` key, if there is one. */
44
+ export declare function readRequestContextOrgId(requestContext: OrgBearingRequestContext | undefined): string | undefined;
45
+ /**
46
+ * Whether a session state value counts as a resolved organization.
47
+ *
48
+ * The capture side trims before deciding (`sdk/src/agents/memory.ts`), so the
49
+ * recovery guards have to agree with it: a whitespace-only value that reads as
50
+ * truthy here would look resolved to a heal path while capture still refuses,
51
+ * and nothing would ever repair it. Not every seam routes its seed through
52
+ * `seedSessionOrg`, so this cannot be assumed away.
53
+ */
54
+ export declare function hasResolvedOrg(orgId: unknown): boolean;
55
+ /**
56
+ * Seed the session's organization, or mark it unresolved when there is none.
57
+ *
58
+ * An absent, empty, or whitespace-only org is a refusal, not a fallback: a
59
+ * blank org rung is not something canonicalization can save. A successful
60
+ * resolve also clears a stale marker, because the session-start hook runs at
61
+ * most once per session per process and nothing else would ever clear it.
62
+ *
63
+ * Best-effort by contract — every caller is a session-created listener that
64
+ * must not sink a run that is otherwise ready.
65
+ */
66
+ export declare function seedSessionOrg(session: OrgSeedableSession, orgId: string | null | undefined): Promise<void>;
67
+ //# sourceMappingURL=org-seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-seed.d.ts","sourceRoot":"","sources":["../../src/session/org-seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,mBAAmB,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;QAC/C,GAAG,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC5D,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAC/B;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,wBAAwB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMhH;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBjH"}
@@ -0,0 +1,53 @@
1
+ //#region src/session/org-seed.ts
2
+ /** Read the tenant org off a request context's `user` key, if there is one. */
3
+ function readRequestContextOrgId(requestContext) {
4
+ if (!requestContext) return void 0;
5
+ const user = requestContext.get("user");
6
+ if (!user || typeof user !== "object") return void 0;
7
+ const orgId = user.organizationId;
8
+ return typeof orgId === "string" ? orgId : void 0;
9
+ }
10
+ /**
11
+ * Whether a session state value counts as a resolved organization.
12
+ *
13
+ * The capture side trims before deciding (`sdk/src/agents/memory.ts`), so the
14
+ * recovery guards have to agree with it: a whitespace-only value that reads as
15
+ * truthy here would look resolved to a heal path while capture still refuses,
16
+ * and nothing would ever repair it. Not every seam routes its seed through
17
+ * `seedSessionOrg`, so this cannot be assumed away.
18
+ */
19
+ function hasResolvedOrg(orgId) {
20
+ return typeof orgId === "string" && orgId.trim().length > 0;
21
+ }
22
+ /**
23
+ * Seed the session's organization, or mark it unresolved when there is none.
24
+ *
25
+ * An absent, empty, or whitespace-only org is a refusal, not a fallback: a
26
+ * blank org rung is not something canonicalization can save. A successful
27
+ * resolve also clears a stale marker, because the session-start hook runs at
28
+ * most once per session per process and nothing else would ever clear it.
29
+ *
30
+ * Best-effort by contract — every caller is a session-created listener that
31
+ * must not sink a run that is otherwise ready.
32
+ */
33
+ async function seedSessionOrg(session, orgId) {
34
+ const resolved = typeof orgId === "string" ? orgId.trim() : "";
35
+ if (typeof session.state?.get !== "function" || typeof session.state?.set !== "function") return;
36
+ try {
37
+ const state = session.state.get() ?? {};
38
+ if (!resolved) {
39
+ if (state.factoryOrgUnresolved !== true) await session.state.set({ factoryOrgUnresolved: true });
40
+ return;
41
+ }
42
+ const updates = {};
43
+ if (state.factoryOrgId !== resolved) updates.factoryOrgId = resolved;
44
+ if (state.factoryOrgUnresolved) updates.factoryOrgUnresolved = false;
45
+ if (Object.keys(updates).length > 0) await session.state.set(updates);
46
+ } catch (error) {
47
+ console.warn("[Factory org seed] Unable to record the session organization.", error);
48
+ }
49
+ }
50
+ //#endregion
51
+ export { hasResolvedOrg, readRequestContextOrgId, seedSessionOrg };
52
+
53
+ //# sourceMappingURL=org-seed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-seed.js","names":[],"sources":["../../src/session/org-seed.ts"],"sourcesContent":["/**\n * The tenant organization a session's knowledge is scoped to.\n *\n * Subconscious knowledge capture scopes every node and record on\n * `factoryOrgId`. A session that reaches the capture seam without one used to\n * fall back to the session owner id — for factory sessions the agent\n * controller's own id — so the knowledge landed under an org rung no reader\n * ever queries. The write succeeded and the read could never see it.\n *\n * The fix is that every session-creation path seeds the org it already holds,\n * and a path that cannot resolve one marks the session `factoryOrgUnresolved`\n * so the capture side refuses loudly instead of inventing an identity. \"No\n * project id\" is not a proxy for \"not a factory session\" — chat sessions and\n * Slack channel sessions are factory-owned and carry no project id — which is\n * why the unresolved case needs its own explicit marker.\n */\n\n/**\n * Session-state fields org seeding writes. The index signatures mirror\n * `MastraCodeState` so a concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\nexport interface OrgSeedStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n factoryOrgId?: string;\n factoryOrgUnresolved?: boolean;\n}\n\n/** The slice of a session needed to seed its organization. */\nexport interface OrgSeedableSession {\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OrgSeedStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * A request context carrying the tenant on its `user` key. Slack stamps\n * `{ id, organizationId }` and the GitHub webhook `{ workosId, organizationId }`,\n * so only the shared `organizationId` field may be read here.\n */\nexport interface OrgBearingRequestContext {\n get: (key: string) => unknown;\n}\n\n/** Read the tenant org off a request context's `user` key, if there is one. */\nexport function readRequestContextOrgId(requestContext: OrgBearingRequestContext | undefined): string | undefined {\n if (!requestContext) return undefined;\n const user = requestContext.get('user');\n if (!user || typeof user !== 'object') return undefined;\n const orgId = (user as { organizationId?: unknown }).organizationId;\n return typeof orgId === 'string' ? orgId : undefined;\n}\n\n/**\n * Whether a session state value counts as a resolved organization.\n *\n * The capture side trims before deciding (`sdk/src/agents/memory.ts`), so the\n * recovery guards have to agree with it: a whitespace-only value that reads as\n * truthy here would look resolved to a heal path while capture still refuses,\n * and nothing would ever repair it. Not every seam routes its seed through\n * `seedSessionOrg`, so this cannot be assumed away.\n */\nexport function hasResolvedOrg(orgId: unknown): boolean {\n return typeof orgId === 'string' && orgId.trim().length > 0;\n}\n\n/**\n * Seed the session's organization, or mark it unresolved when there is none.\n *\n * An absent, empty, or whitespace-only org is a refusal, not a fallback: a\n * blank org rung is not something canonicalization can save. A successful\n * resolve also clears a stale marker, because the session-start hook runs at\n * most once per session per process and nothing else would ever clear it.\n *\n * Best-effort by contract — every caller is a session-created listener that\n * must not sink a run that is otherwise ready.\n */\nexport async function seedSessionOrg(session: OrgSeedableSession, orgId: string | null | undefined): Promise<void> {\n const resolved = typeof orgId === 'string' ? orgId.trim() : '';\n // Some session shapes (approval stubs, lightweight doubles) carry no state at\n // all. There is nothing to seed and nothing to mark, so this is not a warning.\n if (typeof session.state?.get !== 'function' || typeof session.state?.set !== 'function') return;\n try {\n const state = session.state.get() ?? {};\n if (!resolved) {\n if (state.factoryOrgUnresolved !== true) {\n await session.state.set({ factoryOrgUnresolved: true });\n }\n return;\n }\n const updates: OrgSeedStateWrites = {};\n if (state.factoryOrgId !== resolved) updates.factoryOrgId = resolved;\n if (state.factoryOrgUnresolved) updates.factoryOrgUnresolved = false;\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n } catch (error) {\n console.warn('[Factory org seed] Unable to record the session organization.', error);\n }\n}\n"],"mappings":";;AA+CA,SAAgB,wBAAwB,gBAA0E;CAChH,IAAI,CAAC,gBAAgB,OAAO,KAAA;CAC5B,MAAM,OAAO,eAAe,IAAI,MAAM;CACtC,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO,KAAA;CAC9C,MAAM,QAAS,KAAsC;CACrD,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;AAC7C;;;;;;;;;;AAWA,SAAgB,eAAe,OAAyB;CACtD,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS;AAC5D;;;;;;;;;;;;AAaA,eAAsB,eAAe,SAA6B,OAAiD;CACjH,MAAM,WAAW,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI;CAG5D,IAAI,OAAO,QAAQ,OAAO,QAAQ,cAAc,OAAO,QAAQ,OAAO,QAAQ,YAAY;CAC1F,IAAI;EACF,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;EACtC,IAAI,CAAC,UAAU;GACb,IAAI,MAAM,yBAAyB,MACjC,MAAM,QAAQ,MAAM,IAAI,EAAE,sBAAsB,KAAK,CAAC;GAExD;EACF;EACA,MAAM,UAA8B,CAAC;EACrC,IAAI,MAAM,iBAAiB,UAAU,QAAQ,eAAe;EAC5D,IAAI,MAAM,sBAAsB,QAAQ,uBAAuB;EAC/D,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,MAAM,QAAQ,MAAM,IAAI,OAAO;CACtE,SAAS,OAAO;EACd,QAAQ,KAAK,iEAAiE,KAAK;CACrF;AACF"}
@@ -430,6 +430,15 @@ export declare class WorkItemsStorage extends FactoryStorageDomain {
430
430
  factoryProjectId: string;
431
431
  ids: string[];
432
432
  }): Promise<WorkItemRow[]>;
433
+ /**
434
+ * Strip every ref to a retired session; matching happens in app code because
435
+ * `findMany` cannot reach inside the `sessions` JSON column.
436
+ * ponytail: org-wide scan per session delete; JSON-path query if it measures.
437
+ */
438
+ clearSessionReferences({ orgId, sessionId }: {
439
+ orgId: string;
440
+ sessionId: string;
441
+ }): Promise<number>;
433
442
  get({ orgId, id }: {
434
443
  orgId: string;
435
444
  id: string;