@mastra/factory 0.21.0-alpha.2 → 0.21.0-alpha.4
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.
- package/dist/auth.d.ts +7 -12
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +20 -1
- package/dist/auth.js.map +1 -1
- package/dist/factory.d.ts +9 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +56 -23
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/routes.js +1 -0
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/linear/issue-reconciler.d.ts +3 -1
- package/dist/integrations/linear/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/linear/issue-reconciler.js +1 -1
- package/dist/integrations/linear/issue-reconciler.js.map +1 -1
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +10 -9
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +0 -1
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +0 -17
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +8 -40
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/packages/_internals/workspace/dist/index.js +3 -0
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
- package/dist/routes/config.d.ts +15 -36
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +72 -123
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/knowledge.d.ts +115 -21
- package/dist/routes/knowledge.d.ts.map +1 -1
- package/dist/routes/knowledge.js +715 -30
- package/dist/routes/knowledge.js.map +1 -1
- package/dist/routes/oauth.d.ts +0 -3
- package/dist/routes/oauth.d.ts.map +1 -1
- package/dist/routes/oauth.js +2 -10
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/provider-credentials.d.ts.map +1 -1
- package/dist/routes/provider-credentials.js +2 -1
- package/dist/routes/provider-credentials.js.map +1 -1
- package/dist/routes/surface.d.ts +1 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +6 -3
- package/dist/routes/surface.js.map +1 -1
- package/dist/rules/binding-context.d.ts +19 -0
- package/dist/rules/binding-context.d.ts.map +1 -1
- package/dist/rules/binding-context.js +39 -1
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +2 -13
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +0 -6
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts +35 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +80 -4
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts +1 -2
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +2 -6
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/session/factory-session.d.ts +5 -43
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +6 -47
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/memory-settings-hydration.d.ts +17 -89
- package/dist/session/memory-settings-hydration.d.ts.map +1 -1
- package/dist/session/memory-settings-hydration.js +25 -111
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/storage/domains/memory-settings/base.d.ts +5 -17
- package/dist/storage/domains/memory-settings/base.d.ts.map +1 -1
- package/dist/storage/domains/memory-settings/base.js +8 -15
- package/dist/storage/domains/memory-settings/base.js.map +1 -1
- package/dist/supervisor/session.d.ts +2 -4
- package/dist/supervisor/session.d.ts.map +1 -1
- package/dist/supervisor/session.js +3 -5
- package/dist/supervisor/session.js.map +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +2 -2
- package/dist/workspace.js.map +1 -1
- package/package.json +8 -8
- package/dist/routes/om-seed.d.ts +0 -22
- package/dist/routes/om-seed.d.ts.map +0 -1
- package/dist/routes/om-seed.js +0 -40
- package/dist/routes/om-seed.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import { 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 {\n SourceControlConnectionNotFoundError,\n type SourceControlSession,\n type SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport { applyStoredMemorySettings, type OMConfigurableSession } from './memory-settings-hydration.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\nexport class FactorySourceControlConflictError extends Error {}\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 SourceControlSessionLookup {\n getBySessionId(sessionId: string): Promise<SourceControlSession | null>;\n getSourceControlBySessionId(sessionId: string): Promise<SourceControlStorageHandle | null>;\n rename(args: { sessionId: string; title: string }): 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 rename: async args => (await resolve(args.sessionId))?.sourceControl.sessions.rename(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 /** Pick the linked repository that the work item targets. */\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(\n args:\n | {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n repositorySlug: string;\n }\n | {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n firstLinkedRepository: true;\n },\n): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId } = args;\n const repositorySlug = 'repositorySlug' in args ? args.repositorySlug : undefined;\n const firstLinkedRepository = 'firstLinkedRepository' in 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 && (firstLinkedRepository || candidate.repository.slug === repositorySlug),\n );\n } catch (error) {\n // A deleted installation invalidates a stale connection; storage failures must propagate.\n if (!(error instanceof SourceControlConnectionNotFoundError)) throw error;\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: globalThis.crypto.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 * Model whose provider supplies the observational-memory fallback. Defaults\n * to the factory model, but channel sessions can use the sender's model so OM\n * resolves against that sender's credentials.\n */\n observationalMemoryModelId?: 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 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 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 observationalMemoryModelId = args.observationalMemoryModelId ?? args.defaultModelId;\n const provider = observationalMemoryModelId?.split('/')[0];\n const fallbackOmModelId = provider\n ? resolveProviderOMDefault(provider, observationalMemoryModelId).modelId\n : 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(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 // 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: args.defaultModelId, agentType });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default subagent model', {\n agentType,\n modelId: args.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n}\n\nexport interface RefreshFactorySessionMemorySettingsArgs {\n orgId: string;\n factoryProjectId: string;\n projects: Pick<FactoryProjectsStorage, 'get'>;\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Re-apply a factory project's stored observational-memory settings to an\n * already-running session that automation is about to reuse. Session creation\n * hydrates these settings once (`hydrateFactorySession`), but a reused binding\n * keeps whatever observer/reflector models it was created with — so a project\n * whose OM models changed since would keep observing with the stale (and\n * possibly since-rejected) models. This reads the project's current row with the\n * same provider-aware fallback as initial hydration and applies it, mirroring\n * the `GET /web/config/om` refresh. Best-effort: a settings lookup failure must\n * never sink an otherwise-ready run, so it is logged and swallowed.\n */\nexport async function refreshFactorySessionMemorySettings(\n session: OMConfigurableSession,\n args: RefreshFactorySessionMemorySettingsArgs,\n): Promise<void> {\n try {\n const record = await args.memorySettings.get({\n orgId: args.orgId,\n userId: factoryMemorySettingsUserId(args.factoryProjectId),\n });\n const project = await args.projects.get({ orgId: args.orgId, id: args.factoryProjectId });\n const provider = project?.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider\n ? resolveProviderOMDefault(provider, project?.defaultModelId ?? undefined).modelId\n : undefined;\n await applyStoredMemorySettings(session, record, fallbackOmModelId);\n } catch (error) {\n console.warn('[Factory dispatch] Failed to reapply observational-memory settings on session reuse', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n"],"mappings":";;;;;;AAiBA,IAAa,oCAAb,cAAuD,MAAM,CAAC;;;;;AAM9D,eAAsB,6BACpB,UACA,kBAC6B;CAC7B,IAAI,CAAC,YAAY,CAAC,kBAAkB,OAAO,KAAA;CAC3C,IAAI;EAEF,QAAO,MADe,SAAS,QAAQ,EAAE,IAAI,iBAAiB,CAAC,EAAA,EAC/C,kBAAkB,KAAA;CACpC,QAAQ;EACN;CACF;AACF;;;;;;AAeA,SAAgB,iCACd,gBAC4B;CAC5B,MAAM,UAAU,OACd,cACiG;EACjG,MAAM,WACJ,MAAM,QAAQ,IACZ,eAAe,IAAI,OAAM,mBAAkB;GACzC;GACA,SAAS,MAAM,cAAc,SAAS,eAAe,SAAS;EAChE,EAAE,CACJ,EAAA,CACA,QACC,UACC,MAAM,YAAY,IACtB;EACA,IAAI,QAAQ,SAAS,GAAG,MAAM,IAAI,MAAM,8DAA8D;EACtG,OAAO,QAAQ,MAAM;CACvB;CAEA,OAAO;EACL,gBAAgB,OAAM,eAAc,MAAM,QAAQ,SAAS,EAAA,EAAI,WAAW;EAC1E,6BAA6B,OAAM,eAAc,MAAM,QAAQ,SAAS,EAAA,EAAI,iBAAiB;EAC7F,QAAQ,OAAM,UAAS,MAAM,QAAQ,KAAK,SAAS,EAAA,EAAI,cAAc,SAAS,OAAO,IAAI;EACzF,kBAAkB,OAAM,UAAS,MAAM,QAAQ,KAAK,SAAS,EAAA,EAAI,cAAc,SAAS,iBAAiB,IAAI;EAC7G,yBAAyB,OAAM,UAC5B,MAAM,QAAQ,KAAK,SAAS,EAAA,EAAI,cAAc,SAAS,wBAAwB,IAAI;CACxF;AACF;;;;;AAMA,eAAsB,4BAA4B,MAKE;CAClD,IAAI,KAAK,WAAW;EAClB,MAAM,kBACJ,MAAM,QAAQ,IACZ,KAAK,eAAe,IAAI,OAAM,mBAAkB;GAC9C;GACA,SAAS,MAAM,cAAc,SAAS,eAAe,KAAK,SAAU;EACtE,EAAE,CACJ,EAAA,CACA,QAAO,UAAS,MAAM,YAAY,IAAI;EACxC,IAAI,eAAe,SAAS,GAAG,MAAM,IAAI,MAAM,8DAA8D;EAC7G,IAAI,eAAe,IAAI,OAAO,eAAe,EAAE,CAAC;CAClD;CAEA,MAAM,SAAS,CAAC;CAChB,KAAK,MAAM,iBAAiB,KAAK,gBAAgB;EAC/C,MAAM,cAAc,MAAM,cAAc,YAAY,KAAK;GACvD,OAAO,KAAK;GACZ,kBAAkB,KAAK;EACzB,CAAC;EACD,IAAI,sBAAsB;EAC1B,KAAK,MAAM,cAAc,aACvB,IAAI;GACF,KACG,MAAM,cAAc,oBAAoB,KAAK;IAAE,OAAO,KAAK;IAAO,cAAc,WAAW;GAAG,CAAC,EAAA,CAAG,SAAS,GAC5G;IACA,sBAAsB;IACtB;GACF;EACF,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,uCAAuC,MAAM;EACtE;EAEF,IAAI,qBAAqB,OAAO,KAAK,aAAa;CACpD;CACA,IAAI,OAAO,SAAS,GAClB,MAAM,IAAI,kCACR,oFACF;CACF,OAAO,OAAO;AAChB;AA8BA,IAAa,sCAAb,cAAyD,MAAM;CACxC;CAArB,YAAY,QAA8C;EACxD,MACE,WAAW,eACP,iDACA,8CACN;EALmB,KAAA,SAAA;EAMnB,KAAK,OAAO;CACd;AACF;;;;;;;;AA2BA,eAAsB,+BACpB,MAawC;CACxC,MAAM,EAAE,eAAe,OAAO,qBAAqB;CACnD,MAAM,iBAAiB,oBAAoB,OAAO,KAAK,iBAAiB,KAAA;CACxE,MAAM,wBAAwB,2BAA2B;CAGzD,MAAM,cAAa,MADO,cAAc,YAAY,KAAK;EAAE;EAAO;CAAiB,CAAC,EAAA,CACrD,QAAO,cAAa,UAAU,kBAAkB,cAAc,aAAa;CAC1G,IAAI,WAAW,WAAW,GAAG,OAAO;EAAE,OAAO;EAAO,QAAQ;CAAa;CAMzE,KAAK,MAAM,cAAc,YAAY;EACnC,IAAI;EACJ,IAAI;GACF,MAAM,sBAAsB,MAAM,cAAc,oBAAoB,KAAK;IAAE;IAAO,cAAc,WAAW;GAAG,CAAC;GAO/G,YAAW,MANwB,QAAQ,IACzC,oBAAoB,IAAI,OAAM,uBAAsB;IAClD;IACA,YAAY,MAAM,cAAc,aAAa,IAAI;KAAE;KAAO,IAAI,kBAAkB;IAAa,CAAC;GAChG,EAAE,CACJ,EAAA,CACgC,MAC9B,cAAa,UAAU,eAAe,yBAAyB,UAAU,WAAW,SAAS,eAC/F;EACF,SAAS,OAAO;GAEd,IAAI,EAAE,iBAAiB,uCAAuC,MAAM;GACpE;EACF;EACA,IAAI,CAAC,UAAU,YAAY;EAE3B,OAAO;GACL,OAAO;GACP,qBAAqB,SAAS,kBAAkB;GAChD,YAAY,SAAS,kBAAkB,UAAU,SAAS,WAAW;GACrE,mBAAmB,WAAW;EAChC;CACF;CAEA,OAAO;EAAE,OAAO;EAAO,QAAQ;CAAa;AAC9C;;;;;;;;;;AAWA,eAAsB,gCAAgC,MAG0B;CAC9E,MAAM,EAAE,eAAe,cAAc;CAErC,MAAM,UAAU,MAAM,cAAc,SAAS,eAAe,SAAS;CACrE,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,oBAAoB,MAAM,cAAc,oBAAoB,IAAI;EACpE,OAAO,QAAQ;EACf,IAAI,QAAQ;CACd,CAAC;CACD,IAAI,CAAC,mBAAmB,OAAO;CAC/B,MAAM,aAAa,MAAM,cAAc,YAAY,IAAI;EAAE,OAAO,QAAQ;EAAO,IAAI,kBAAkB;CAAa,CAAC;CACnH,IAAI,CAAC,YAAY,OAAO;CAExB,OAAO;EAAE,kBAAkB,WAAW;EAAkB,OAAO,QAAQ;EAAO,QAAQ,QAAQ;CAAO;AACvG;;;;;;;;;;;;;;;;AAiBA,eAAsB,2BACpB,MACsC;CACtC,MAAM,EAAE,eAAe,OAAO,kBAAkB,QAAQ,mBAAmB;CAE3E,MAAM,WAAW,MAAM,+BAA+B;EAAE;EAAe;EAAO;EAAkB;CAAe,CAAC;CAChH,IAAI,CAAC,SAAS,OAAO,MAAM,IAAI,oCAAoC,SAAS,MAAM;CAElF,MAAM,SAAS,KAAK,qBAAqB,SAAS;CAClD,MAAM,UAAU,MAAM,cAAc,SAAS,OAAO;EAClD,WAAW,WAAW,OAAO,WAAW;EACxC,qBAAqB,SAAS;EAC9B;EACA;EACA;EACA,YAAY,SAAS;EACrB,YAAY;CACd,CAAC;CACD,OAAO;EACL,WAAW,QAAQ;EACnB;EACA,qBAAqB,SAAS;EAC9B,QAAQ,QAAQ;EAChB,YAAY,SAAS;CACvB;AACF;;;;;;;;;AAkCA,eAAsB,sBAAsB,SAAyB,MAAgD;CAGnH,MAAM,eAAe,SAAS,KAAK,KAAK;CACxC,IAAI;EACF,MAAM,SACJ,KAAK,kBAAkB,KAAK,mBACxB,MAAM,KAAK,eAAe,IAAI;GAC5B,OAAO,KAAK;GACZ,QAAQ,4BAA4B,KAAK,gBAAgB;EAC3D,CAAC,IACD;EAIN,MAAM,6BAA6B,KAAK,8BAA8B,KAAK;EAC3E,MAAM,WAAW,4BAA4B,MAAM,GAAG,CAAC,CAAC;EAIxD,MAAM,0BAA0B,SAAS,QAHf,WACtB,yBAAyB,UAAU,0BAA0B,CAAC,CAAC,UAC/D,KAAA,CAC8D;CACpE,SAAS,OAAO;EACd,QAAQ,KAAK,iEAAiE,EAC5E,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC9D,CAAC;CACH;CACA,IAAI,KAAK,gBAAgB;EACvB,IAAI;GACF,MAAM,QAAQ,MAAM,OAAO,KAAK,cAAc;EAChD,SAAS,OAAO;GACd,QAAQ,KAAK,yDAAyD;IACpE,SAAS,KAAK;IACd,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D,CAAC;EACH;EAIA,KAAK,MAAM,aAAa;GAAC;GAAW;GAAQ;EAAS,GACnD,IAAI;GACF,MAAM,QAAQ,UAAU,MAAM,IAAI;IAAE,SAAS,KAAK;IAAgB;GAAU,CAAC;EAC/E,SAAS,OAAO;GACd,QAAQ,KAAK,kEAAkE;IAC7E;IACA,SAAS,KAAK;IACd,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D,CAAC;EACH;CAEJ;AACF;;;;;;;;;;;;AAoBA,eAAsB,oCACpB,SACA,MACe;CACf,IAAI;EACF,MAAM,SAAS,MAAM,KAAK,eAAe,IAAI;GAC3C,OAAO,KAAK;GACZ,QAAQ,4BAA4B,KAAK,gBAAgB;EAC3D,CAAC;EACD,MAAM,UAAU,MAAM,KAAK,SAAS,IAAI;GAAE,OAAO,KAAK;GAAO,IAAI,KAAK;EAAiB,CAAC;EACxF,MAAM,WAAW,SAAS,gBAAgB,MAAM,GAAG,CAAC,CAAC;EAIrD,MAAM,0BAA0B,SAAS,QAHf,WACtB,yBAAyB,UAAU,SAAS,kBAAkB,KAAA,CAAS,CAAC,CAAC,UACzE,KAAA,CAC8D;CACpE,SAAS,OAAO;EACd,QAAQ,KAAK,uFAAuF,EAClG,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC9D,CAAC;CACH;AACF"}
|
|
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 type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport {\n SourceControlConnectionNotFoundError,\n type SourceControlSession,\n type SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\nexport class FactorySourceControlConflictError extends Error {}\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 SourceControlSessionLookup {\n getBySessionId(sessionId: string): Promise<SourceControlSession | null>;\n getSourceControlBySessionId(sessionId: string): Promise<SourceControlStorageHandle | null>;\n rename(args: { sessionId: string; title: string }): 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 rename: async args => (await resolve(args.sessionId))?.sourceControl.sessions.rename(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 /** Pick the linked repository that the work item targets. */\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(\n args:\n | {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n repositorySlug: string;\n }\n | {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n firstLinkedRepository: true;\n },\n): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId } = args;\n const repositorySlug = 'repositorySlug' in args ? args.repositorySlug : undefined;\n const firstLinkedRepository = 'firstLinkedRepository' in 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 && (firstLinkedRepository || candidate.repository.slug === repositorySlug),\n );\n } catch (error) {\n // A deleted installation invalidates a stale connection; storage failures must propagate.\n if (!(error instanceof SourceControlConnectionNotFoundError)) throw error;\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: globalThis.crypto.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 /** The factory project's default model. Without it the session keeps the SDK's built-in mode default. */\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 — the session simply keeps the default it was created with,\n * 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(session: FactorySession, args: HydrateFactorySessionArgs): Promise<void> {\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\n if (args.defaultModelId) {\n try {\n await session.model.switch(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 // 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: args.defaultModelId, agentType });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply factory default subagent model', {\n agentType,\n modelId: args.defaultModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n}\n"],"mappings":";;;AAaA,IAAa,oCAAb,cAAuD,MAAM,CAAC;;;;;AAM9D,eAAsB,6BACpB,UACA,kBAC6B;CAC7B,IAAI,CAAC,YAAY,CAAC,kBAAkB,OAAO,KAAA;CAC3C,IAAI;EAEF,QAAO,MADe,SAAS,QAAQ,EAAE,IAAI,iBAAiB,CAAC,EAAA,EAC/C,kBAAkB,KAAA;CACpC,QAAQ;EACN;CACF;AACF;;;;;;AAeA,SAAgB,iCACd,gBAC4B;CAC5B,MAAM,UAAU,OACd,cACiG;EACjG,MAAM,WACJ,MAAM,QAAQ,IACZ,eAAe,IAAI,OAAM,mBAAkB;GACzC;GACA,SAAS,MAAM,cAAc,SAAS,eAAe,SAAS;EAChE,EAAE,CACJ,EAAA,CACA,QACC,UACC,MAAM,YAAY,IACtB;EACA,IAAI,QAAQ,SAAS,GAAG,MAAM,IAAI,MAAM,8DAA8D;EACtG,OAAO,QAAQ,MAAM;CACvB;CAEA,OAAO;EACL,gBAAgB,OAAM,eAAc,MAAM,QAAQ,SAAS,EAAA,EAAI,WAAW;EAC1E,6BAA6B,OAAM,eAAc,MAAM,QAAQ,SAAS,EAAA,EAAI,iBAAiB;EAC7F,QAAQ,OAAM,UAAS,MAAM,QAAQ,KAAK,SAAS,EAAA,EAAI,cAAc,SAAS,OAAO,IAAI;EACzF,kBAAkB,OAAM,UAAS,MAAM,QAAQ,KAAK,SAAS,EAAA,EAAI,cAAc,SAAS,iBAAiB,IAAI;EAC7G,yBAAyB,OAAM,UAC5B,MAAM,QAAQ,KAAK,SAAS,EAAA,EAAI,cAAc,SAAS,wBAAwB,IAAI;CACxF;AACF;;;;;AAMA,eAAsB,4BAA4B,MAKE;CAClD,IAAI,KAAK,WAAW;EAClB,MAAM,kBACJ,MAAM,QAAQ,IACZ,KAAK,eAAe,IAAI,OAAM,mBAAkB;GAC9C;GACA,SAAS,MAAM,cAAc,SAAS,eAAe,KAAK,SAAU;EACtE,EAAE,CACJ,EAAA,CACA,QAAO,UAAS,MAAM,YAAY,IAAI;EACxC,IAAI,eAAe,SAAS,GAAG,MAAM,IAAI,MAAM,8DAA8D;EAC7G,IAAI,eAAe,IAAI,OAAO,eAAe,EAAE,CAAC;CAClD;CAEA,MAAM,SAAS,CAAC;CAChB,KAAK,MAAM,iBAAiB,KAAK,gBAAgB;EAC/C,MAAM,cAAc,MAAM,cAAc,YAAY,KAAK;GACvD,OAAO,KAAK;GACZ,kBAAkB,KAAK;EACzB,CAAC;EACD,IAAI,sBAAsB;EAC1B,KAAK,MAAM,cAAc,aACvB,IAAI;GACF,KACG,MAAM,cAAc,oBAAoB,KAAK;IAAE,OAAO,KAAK;IAAO,cAAc,WAAW;GAAG,CAAC,EAAA,CAAG,SAAS,GAC5G;IACA,sBAAsB;IACtB;GACF;EACF,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,uCAAuC,MAAM;EACtE;EAEF,IAAI,qBAAqB,OAAO,KAAK,aAAa;CACpD;CACA,IAAI,OAAO,SAAS,GAClB,MAAM,IAAI,kCACR,oFACF;CACF,OAAO,OAAO;AAChB;AA8BA,IAAa,sCAAb,cAAyD,MAAM;CACxC;CAArB,YAAY,QAA8C;EACxD,MACE,WAAW,eACP,iDACA,8CACN;EALmB,KAAA,SAAA;EAMnB,KAAK,OAAO;CACd;AACF;;;;;;;;AA2BA,eAAsB,+BACpB,MAawC;CACxC,MAAM,EAAE,eAAe,OAAO,qBAAqB;CACnD,MAAM,iBAAiB,oBAAoB,OAAO,KAAK,iBAAiB,KAAA;CACxE,MAAM,wBAAwB,2BAA2B;CAGzD,MAAM,cAAa,MADO,cAAc,YAAY,KAAK;EAAE;EAAO;CAAiB,CAAC,EAAA,CACrD,QAAO,cAAa,UAAU,kBAAkB,cAAc,aAAa;CAC1G,IAAI,WAAW,WAAW,GAAG,OAAO;EAAE,OAAO;EAAO,QAAQ;CAAa;CAMzE,KAAK,MAAM,cAAc,YAAY;EACnC,IAAI;EACJ,IAAI;GACF,MAAM,sBAAsB,MAAM,cAAc,oBAAoB,KAAK;IAAE;IAAO,cAAc,WAAW;GAAG,CAAC;GAO/G,YAAW,MANwB,QAAQ,IACzC,oBAAoB,IAAI,OAAM,uBAAsB;IAClD;IACA,YAAY,MAAM,cAAc,aAAa,IAAI;KAAE;KAAO,IAAI,kBAAkB;IAAa,CAAC;GAChG,EAAE,CACJ,EAAA,CACgC,MAC9B,cAAa,UAAU,eAAe,yBAAyB,UAAU,WAAW,SAAS,eAC/F;EACF,SAAS,OAAO;GAEd,IAAI,EAAE,iBAAiB,uCAAuC,MAAM;GACpE;EACF;EACA,IAAI,CAAC,UAAU,YAAY;EAE3B,OAAO;GACL,OAAO;GACP,qBAAqB,SAAS,kBAAkB;GAChD,YAAY,SAAS,kBAAkB,UAAU,SAAS,WAAW;GACrE,mBAAmB,WAAW;EAChC;CACF;CAEA,OAAO;EAAE,OAAO;EAAO,QAAQ;CAAa;AAC9C;;;;;;;;;;AAWA,eAAsB,gCAAgC,MAG0B;CAC9E,MAAM,EAAE,eAAe,cAAc;CAErC,MAAM,UAAU,MAAM,cAAc,SAAS,eAAe,SAAS;CACrE,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,oBAAoB,MAAM,cAAc,oBAAoB,IAAI;EACpE,OAAO,QAAQ;EACf,IAAI,QAAQ;CACd,CAAC;CACD,IAAI,CAAC,mBAAmB,OAAO;CAC/B,MAAM,aAAa,MAAM,cAAc,YAAY,IAAI;EAAE,OAAO,QAAQ;EAAO,IAAI,kBAAkB;CAAa,CAAC;CACnH,IAAI,CAAC,YAAY,OAAO;CAExB,OAAO;EAAE,kBAAkB,WAAW;EAAkB,OAAO,QAAQ;EAAO,QAAQ,QAAQ;CAAO;AACvG;;;;;;;;;;;;;;;;AAiBA,eAAsB,2BACpB,MACsC;CACtC,MAAM,EAAE,eAAe,OAAO,kBAAkB,QAAQ,mBAAmB;CAE3E,MAAM,WAAW,MAAM,+BAA+B;EAAE;EAAe;EAAO;EAAkB;CAAe,CAAC;CAChH,IAAI,CAAC,SAAS,OAAO,MAAM,IAAI,oCAAoC,SAAS,MAAM;CAElF,MAAM,SAAS,KAAK,qBAAqB,SAAS;CAClD,MAAM,UAAU,MAAM,cAAc,SAAS,OAAO;EAClD,WAAW,WAAW,OAAO,WAAW;EACxC,qBAAqB,SAAS;EAC9B;EACA;EACA;EACA,YAAY,SAAS;EACrB,YAAY;CACd,CAAC;CACD,OAAO;EACL,WAAW,QAAQ;EACnB;EACA,qBAAqB,SAAS;EAC9B,QAAQ,QAAQ;EAChB,YAAY,SAAS;CACvB;AACF;;;;;;;;;AAgBA,eAAsB,sBAAsB,SAAyB,MAAgD;CAGnH,MAAM,eAAe,SAAS,KAAK,KAAK;CAExC,IAAI,KAAK,gBAAgB;EACvB,IAAI;GACF,MAAM,QAAQ,MAAM,OAAO,KAAK,cAAc;EAChD,SAAS,OAAO;GACd,QAAQ,KAAK,yDAAyD;IACpE,SAAS,KAAK;IACd,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D,CAAC;EACH;EAIA,KAAK,MAAM,aAAa;GAAC;GAAW;GAAQ;EAAS,GACnD,IAAI;GACF,MAAM,QAAQ,UAAU,MAAM,IAAI;IAAE,SAAS,KAAK;IAAgB;GAAU,CAAC;EAC/E,SAAS,OAAO;GACd,QAAQ,KAAK,kEAAkE;IAC7E;IACA,SAAS,KAAK;IACd,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D,CAAC;EACH;CAEJ;AACF"}
|
|
@@ -1,108 +1,36 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
1
|
+
import type { MastraCodeState } from '@mastra/code-sdk/schema';
|
|
2
|
+
import type { MemorySettingsRecord } from '../storage/domains/memory-settings/base.js';
|
|
3
3
|
import type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
|
|
4
4
|
/** Default thresholds mirror the TUI `/om` fallbacks. */
|
|
5
5
|
export declare const DEFAULT_OBSERVATION_THRESHOLD = 30000;
|
|
6
6
|
export declare const DEFAULT_REFLECTION_THRESHOLD = 40000;
|
|
7
|
-
/** One observational-memory role's read/switch surface. */
|
|
8
|
-
interface OMRoleSlice {
|
|
9
|
-
modelId: () => string | undefined;
|
|
10
|
-
switchModel: (args: {
|
|
11
|
-
modelId: string;
|
|
12
|
-
}) => Promise<unknown>;
|
|
13
|
-
}
|
|
14
7
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
8
|
+
* Layer a channel sender's own row over the project's shared row. The sender
|
|
9
|
+
* wins only for knobs they saved; an unsaved (`null`) knob keeps the project's
|
|
10
|
+
* value rather than resetting to Auto or the built-in default, because the
|
|
11
|
+
* project's choice may be the only credentialed one.
|
|
18
12
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
[key: `subagentModelId_${string}`]: string | undefined;
|
|
22
|
-
observationThreshold?: number;
|
|
23
|
-
reflectionThreshold?: number;
|
|
24
|
-
observeAttachments?: 'auto' | boolean;
|
|
25
|
-
factoryOrgId?: string;
|
|
26
|
-
}
|
|
27
|
-
/** The slice of a session needed to apply stored observational-memory settings. */
|
|
28
|
-
export interface OMConfigurableSession {
|
|
29
|
-
om: {
|
|
30
|
-
observer: OMRoleSlice;
|
|
31
|
-
reflector: OMRoleSlice;
|
|
32
|
-
};
|
|
33
|
-
state: {
|
|
34
|
-
get: () => Record<string, unknown> | undefined;
|
|
35
|
-
set: (updates: OMStateWrites) => Promise<void> | void;
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Apply a stored memory-settings row onto a session, so the DB — not whatever
|
|
40
|
-
* happens to sit in persisted session state (e.g. a stale boot-time seed from
|
|
41
|
-
* before memory settings moved to the DB) — is what the web surface reads and
|
|
42
|
-
* what the session's OM actually runs with. The row is authoritative: knobs
|
|
43
|
-
* without a stored value reset to the built-in defaults. This is the single
|
|
44
|
-
* application path shared by the settings routes, coordinator hydration, and
|
|
45
|
-
* the web session boot seed.
|
|
46
|
-
*/
|
|
47
|
-
export declare function applyStoredMemorySettings(session: OMConfigurableSession, record: MemorySettingsRecord | null, fallbackOmModelId?: string): Promise<void>;
|
|
48
|
-
export interface PersonalMemorySettingsArgs {
|
|
49
|
-
/** Without the domain there is no personal preference to read. */
|
|
50
|
-
memorySettings: Pick<MemorySettingsStorage, 'get'> | undefined;
|
|
51
|
-
orgId: string;
|
|
52
|
-
/** The human whose settings apply — for a channel session, the linked sender. */
|
|
53
|
-
userId: string;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Layer a user's own memory-settings row over whatever the session already runs
|
|
57
|
-
* with, so a personal preference beats the one the project path resolved.
|
|
58
|
-
*
|
|
59
|
-
* The row wins only for the knobs the user has actually saved: an unsaved knob
|
|
60
|
-
* keeps the value already on the session (the project's row, or the
|
|
61
|
-
* provider-aware fallback resolved from the factory default model). That
|
|
62
|
-
* distinction matters because `applyStoredMemorySettings` treats a null knob as
|
|
63
|
-
* "reset to the built-in default", and switching observation onto an
|
|
64
|
-
* uncredentialed provider would fail every observe cycle.
|
|
65
|
-
*
|
|
66
|
-
* Best-effort: a missing row, an uninitialized domain, or a read failure all
|
|
67
|
-
* mean "no personal preference" and leave the session exactly as it was.
|
|
68
|
-
*/
|
|
69
|
-
export declare function applyPersonalMemorySettings(session: OMConfigurableSession, { memorySettings, orgId, userId }: PersonalMemorySettingsArgs): Promise<void>;
|
|
70
|
-
export interface MemorySettingsHydrationSession extends OMConfigurableSession {
|
|
13
|
+
export declare function layerPersonalMemorySettings(project: MemorySettingsRecord | null, personal: MemorySettingsRecord | null): MemorySettingsRecord | null;
|
|
14
|
+
export interface MemorySettingsHydrationSession {
|
|
71
15
|
readonly identity: {
|
|
72
16
|
getResourceId(): string;
|
|
73
17
|
};
|
|
18
|
+
state: {
|
|
19
|
+
get: () => MastraCodeState | undefined;
|
|
20
|
+
set: (updates: Partial<MastraCodeState>) => Promise<void> | void;
|
|
21
|
+
};
|
|
74
22
|
}
|
|
75
23
|
export interface MemorySettingsHydrationDependencies {
|
|
76
24
|
/** GitHub-integration source-control rows — the only creator of web user sessions today. */
|
|
77
25
|
sourceControl: {
|
|
78
26
|
sessions: Pick<SourceControlStorageHandle['sessions'], 'getBySessionId'>;
|
|
79
27
|
};
|
|
80
|
-
projects: Pick<FactoryProjectsStorage, 'get'>;
|
|
81
|
-
memorySettings: Pick<MemorySettingsStorage, 'get'>;
|
|
82
28
|
}
|
|
83
29
|
/**
|
|
84
|
-
* Seed a freshly created controller session's tenant org
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* The org seed matters beyond settings. Subconscious knowledge curation scopes
|
|
90
|
-
* every node and record on `factoryOrgId`; before the SDK refusal guard,
|
|
91
|
-
* missing it made curation substitute the session owner id. For web chat sessions
|
|
92
|
-
* that is the agent controller's own id rather than a tenant, so curated
|
|
93
|
-
* knowledge landed under an org rung no reader ever queries. Same rule as the
|
|
94
|
-
* start coordinator: the org
|
|
95
|
-
* comes from the row the session was created from, never improvised from an
|
|
96
|
-
* owner id.
|
|
97
|
-
*
|
|
98
|
-
* Tagged sessions may be coordinator-owned runs or web sessions that only carry
|
|
99
|
-
* browser-seeded Factory state. This path resolves their source-control row and
|
|
100
|
-
* reapplies a stored project settings row when one exists. If no project row
|
|
101
|
-
* exists, it preserves the coordinator's provider-aware fallback.
|
|
102
|
-
* Sessions without a GitHub source-control row (e.g. chat-only channel sessions)
|
|
103
|
-
* hydrate through `hydrateFactorySession` with their own resolved tenant.
|
|
104
|
-
* Best-effort: failures are logged, never thrown.
|
|
30
|
+
* Seed a freshly created controller session's tenant org from its source-control
|
|
31
|
+
* row. Memory settings remain DB-authoritative and are loaded into request
|
|
32
|
+
* context for each invocation instead of being copied into mutable session
|
|
33
|
+
* state.
|
|
105
34
|
*/
|
|
106
|
-
export declare function hydrateSessionMemorySettings(session: MemorySettingsHydrationSession, { sourceControl
|
|
107
|
-
export {};
|
|
35
|
+
export declare function hydrateSessionMemorySettings(session: MemorySettingsHydrationSession, { sourceControl }: MemorySettingsHydrationDependencies): Promise<void>;
|
|
108
36
|
//# sourceMappingURL=memory-settings-hydration.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-settings-hydration.d.ts","sourceRoot":"","sources":["../../src/session/memory-settings-hydration.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"memory-settings-hydration.d.ts","sourceRoot":"","sources":["../../src/session/memory-settings-hydration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAG5F,yDAAyD;AACzD,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,oBAAoB,GAAG,IAAI,EACpC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,GACpC,oBAAoB,GAAG,IAAI,CAW7B;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,QAAQ,EAAE;QAAE,aAAa,IAAI,MAAM,CAAA;KAAE,CAAC;IAC/C,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,eAAe,GAAG,SAAS,CAAC;QACvC,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAClE,CAAC;CACH;AAED,MAAM,WAAW,mCAAmC;IAClD,4FAA4F;IAC5F,aAAa,EAAE;QACb,QAAQ,EAAE,IAAI,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;KAC1E,CAAC;CACH;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,EACvC,EAAE,aAAa,EAAE,EAAE,mCAAmC,GACrD,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
|
@@ -1,129 +1,43 @@
|
|
|
1
|
-
import { seedSessionOrg } from "./org-seed.js";
|
|
2
|
-
import { factoryMemorySettingsUserId } from "../storage/domains/memory-settings/base.js";
|
|
3
|
-
import { DEFAULT_OM_MODEL_ID } from "@mastra/code-sdk/constants";
|
|
4
|
-
import { resolveProviderOMDefault } from "@mastra/code-sdk/onboarding/packs";
|
|
1
|
+
import { hasResolvedOrg, seedSessionOrg } from "./org-seed.js";
|
|
5
2
|
//#region src/session/memory-settings-hydration.ts
|
|
6
3
|
/** Default thresholds mirror the TUI `/om` fallbacks. */
|
|
7
4
|
const DEFAULT_OBSERVATION_THRESHOLD = 3e4;
|
|
8
5
|
const DEFAULT_REFLECTION_THRESHOLD = 4e4;
|
|
9
6
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* without a stored value reset to the built-in defaults. This is the single
|
|
15
|
-
* application path shared by the settings routes, coordinator hydration, and
|
|
16
|
-
* the web session boot seed.
|
|
7
|
+
* Layer a channel sender's own row over the project's shared row. The sender
|
|
8
|
+
* wins only for knobs they saved; an unsaved (`null`) knob keeps the project's
|
|
9
|
+
* value rather than resetting to Auto or the built-in default, because the
|
|
10
|
+
* project's choice may be the only credentialed one.
|
|
17
11
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const observeAttachments = record?.observeAttachments ?? "auto";
|
|
30
|
-
if ((state.observeAttachments ?? "auto") !== observeAttachments) updates.observeAttachments = observeAttachments;
|
|
31
|
-
if (Object.keys(updates).length > 0) await session.state.set(updates);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Layer a user's own memory-settings row over whatever the session already runs
|
|
35
|
-
* with, so a personal preference beats the one the project path resolved.
|
|
36
|
-
*
|
|
37
|
-
* The row wins only for the knobs the user has actually saved: an unsaved knob
|
|
38
|
-
* keeps the value already on the session (the project's row, or the
|
|
39
|
-
* provider-aware fallback resolved from the factory default model). That
|
|
40
|
-
* distinction matters because `applyStoredMemorySettings` treats a null knob as
|
|
41
|
-
* "reset to the built-in default", and switching observation onto an
|
|
42
|
-
* uncredentialed provider would fail every observe cycle.
|
|
43
|
-
*
|
|
44
|
-
* Best-effort: a missing row, an uninitialized domain, or a read failure all
|
|
45
|
-
* mean "no personal preference" and leave the session exactly as it was.
|
|
46
|
-
*/
|
|
47
|
-
async function applyPersonalMemorySettings(session, { memorySettings, orgId, userId }) {
|
|
48
|
-
if (!memorySettings) return;
|
|
49
|
-
try {
|
|
50
|
-
const record = await memorySettings.get({
|
|
51
|
-
orgId,
|
|
52
|
-
userId
|
|
53
|
-
});
|
|
54
|
-
if (!record) return;
|
|
55
|
-
const state = session.state.get() ?? {};
|
|
56
|
-
await applyStoredMemorySettings(session, {
|
|
57
|
-
...record,
|
|
58
|
-
observerModelId: record.observerModelId ?? session.om.observer.modelId() ?? null,
|
|
59
|
-
reflectorModelId: record.reflectorModelId ?? session.om.reflector.modelId() ?? null,
|
|
60
|
-
observationThreshold: record.observationThreshold ?? storedNumber(state.observationThreshold),
|
|
61
|
-
reflectionThreshold: record.reflectionThreshold ?? storedNumber(state.reflectionThreshold),
|
|
62
|
-
observeAttachments: record.observeAttachments ?? storedObserveAttachments(state.observeAttachments)
|
|
63
|
-
});
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.warn("[Factory memory-settings hydration] Unable to apply the user's memory settings.", error);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function storedNumber(value) {
|
|
69
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
70
|
-
}
|
|
71
|
-
function storedObserveAttachments(value) {
|
|
72
|
-
return value === "auto" || typeof value === "boolean" ? value : null;
|
|
12
|
+
function layerPersonalMemorySettings(project, personal) {
|
|
13
|
+
if (!personal) return project;
|
|
14
|
+
if (!project) return personal;
|
|
15
|
+
return {
|
|
16
|
+
...project,
|
|
17
|
+
observerModelId: personal.observerModelId ?? project.observerModelId,
|
|
18
|
+
reflectorModelId: personal.reflectorModelId ?? project.reflectorModelId,
|
|
19
|
+
observationThreshold: personal.observationThreshold ?? project.observationThreshold,
|
|
20
|
+
reflectionThreshold: personal.reflectionThreshold ?? project.reflectionThreshold,
|
|
21
|
+
observeAttachments: personal.observeAttachments ?? project.observeAttachments
|
|
22
|
+
};
|
|
73
23
|
}
|
|
74
24
|
/**
|
|
75
|
-
* Seed a freshly created controller session's tenant org
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* The org seed matters beyond settings. Subconscious knowledge curation scopes
|
|
81
|
-
* every node and record on `factoryOrgId`; before the SDK refusal guard,
|
|
82
|
-
* missing it made curation substitute the session owner id. For web chat sessions
|
|
83
|
-
* that is the agent controller's own id rather than a tenant, so curated
|
|
84
|
-
* knowledge landed under an org rung no reader ever queries. Same rule as the
|
|
85
|
-
* start coordinator: the org
|
|
86
|
-
* comes from the row the session was created from, never improvised from an
|
|
87
|
-
* owner id.
|
|
88
|
-
*
|
|
89
|
-
* Tagged sessions may be coordinator-owned runs or web sessions that only carry
|
|
90
|
-
* browser-seeded Factory state. This path resolves their source-control row and
|
|
91
|
-
* reapplies a stored project settings row when one exists. If no project row
|
|
92
|
-
* exists, it preserves the coordinator's provider-aware fallback.
|
|
93
|
-
* Sessions without a GitHub source-control row (e.g. chat-only channel sessions)
|
|
94
|
-
* hydrate through `hydrateFactorySession` with their own resolved tenant.
|
|
95
|
-
* Best-effort: failures are logged, never thrown.
|
|
25
|
+
* Seed a freshly created controller session's tenant org from its source-control
|
|
26
|
+
* row. Memory settings remain DB-authoritative and are loaded into request
|
|
27
|
+
* context for each invocation instead of being copied into mutable session
|
|
28
|
+
* state.
|
|
96
29
|
*/
|
|
97
|
-
async function hydrateSessionMemorySettings(session, { sourceControl
|
|
30
|
+
async function hydrateSessionMemorySettings(session, { sourceControl }) {
|
|
98
31
|
const state = session.state.get() ?? {};
|
|
99
|
-
|
|
32
|
+
if (Boolean(state.factoryProjectId) && hasResolvedOrg(state.factoryOrgId)) return;
|
|
100
33
|
try {
|
|
101
|
-
|
|
102
|
-
await seedSessionOrg(session, record?.orgId);
|
|
103
|
-
if (!record) return;
|
|
104
|
-
const factoryProjectId = isFactoryRun ? String(state.factoryProjectId) : void 0;
|
|
105
|
-
const settings = await memorySettings.get({
|
|
106
|
-
orgId: record.orgId,
|
|
107
|
-
userId: factoryProjectId ? factoryMemorySettingsUserId(factoryProjectId) : record.userId
|
|
108
|
-
});
|
|
109
|
-
if (factoryProjectId && !settings) return;
|
|
110
|
-
const project = factoryProjectId ? await projects.get({
|
|
111
|
-
orgId: record.orgId,
|
|
112
|
-
id: factoryProjectId
|
|
113
|
-
}) : null;
|
|
114
|
-
const provider = project?.defaultModelId?.split("/")[0];
|
|
115
|
-
const fallbackOmModelId = provider ? resolveProviderOMDefault(provider, project.defaultModelId ?? void 0).modelId : void 0;
|
|
116
|
-
await applyStoredMemorySettings(session, factoryProjectId && settings && !fallbackOmModelId ? {
|
|
117
|
-
...settings,
|
|
118
|
-
observerModelId: settings?.observerModelId ?? session.om.observer.modelId() ?? null,
|
|
119
|
-
reflectorModelId: settings?.reflectorModelId ?? session.om.reflector.modelId() ?? null
|
|
120
|
-
} : settings, fallbackOmModelId);
|
|
34
|
+
await seedSessionOrg(session, (await sourceControl.sessions.getBySessionId(session.identity.getResourceId()))?.orgId);
|
|
121
35
|
} catch (error) {
|
|
122
|
-
console.warn("[Factory
|
|
36
|
+
console.warn("[Factory session hydration] Unable to resolve the session organization.", error);
|
|
123
37
|
if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, void 0);
|
|
124
38
|
}
|
|
125
39
|
}
|
|
126
40
|
//#endregion
|
|
127
|
-
export { DEFAULT_OBSERVATION_THRESHOLD, DEFAULT_REFLECTION_THRESHOLD,
|
|
41
|
+
export { DEFAULT_OBSERVATION_THRESHOLD, DEFAULT_REFLECTION_THRESHOLD, hydrateSessionMemorySettings, layerPersonalMemorySettings };
|
|
128
42
|
|
|
129
43
|
//# sourceMappingURL=memory-settings-hydration.js.map
|
|
@@ -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';\nimport { resolveProviderOMDefault } from '@mastra/code-sdk/onboarding/packs';\n\nimport {\n factoryMemorySettingsUserId,\n type MemorySettingsRecord,\n type MemorySettingsStorage,\n} 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 { 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 PersonalMemorySettingsArgs {\n /** Without the domain there is no personal preference to read. */\n memorySettings: Pick<MemorySettingsStorage, 'get'> | undefined;\n orgId: string;\n /** The human whose settings apply — for a channel session, the linked sender. */\n userId: string;\n}\n\n/**\n * Layer a user's own memory-settings row over whatever the session already runs\n * with, so a personal preference beats the one the project path resolved.\n *\n * The row wins only for the knobs the user has actually saved: an unsaved knob\n * keeps the value already on the session (the project's row, or the\n * provider-aware fallback resolved from the factory default model). That\n * distinction matters because `applyStoredMemorySettings` treats a null knob as\n * \"reset to the built-in default\", and switching observation onto an\n * uncredentialed provider would fail every observe cycle.\n *\n * Best-effort: a missing row, an uninitialized domain, or a read failure all\n * mean \"no personal preference\" and leave the session exactly as it was.\n */\nexport async function applyPersonalMemorySettings(\n session: OMConfigurableSession,\n { memorySettings, orgId, userId }: PersonalMemorySettingsArgs,\n): Promise<void> {\n if (!memorySettings) return;\n try {\n const record = await memorySettings.get({ orgId, userId });\n if (!record) return;\n const state = session.state.get() ?? {};\n await applyStoredMemorySettings(session, {\n ...record,\n observerModelId: record.observerModelId ?? session.om.observer.modelId() ?? null,\n reflectorModelId: record.reflectorModelId ?? session.om.reflector.modelId() ?? null,\n observationThreshold: record.observationThreshold ?? storedNumber(state.observationThreshold),\n reflectionThreshold: record.reflectionThreshold ?? storedNumber(state.reflectionThreshold),\n observeAttachments: record.observeAttachments ?? storedObserveAttachments(state.observeAttachments),\n });\n } catch (error) {\n console.warn(\"[Factory memory-settings hydration] Unable to apply the user's memory settings.\", error);\n }\n}\n\nfunction storedNumber(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null;\n}\n\nfunction storedObserveAttachments(value: unknown): 'auto' | boolean | null {\n return value === 'auto' || typeof value === 'boolean' ? value : null;\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 projects: Pick<FactoryProjectsStorage, 'get'>;\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 curation scopes\n * every node and record on `factoryOrgId`; before the SDK refusal guard,\n * missing it made curation substitute the session owner id. For web chat sessions\n * that is the agent controller's own id rather than a tenant, so curated\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 * Tagged sessions may be coordinator-owned runs or web sessions that only carry\n * browser-seeded Factory state. This path resolves their source-control row and\n * reapplies a stored project settings row when one exists. If no project row\n * exists, it preserves the coordinator's provider-aware fallback.\n * Sessions without a GitHub source-control row (e.g. chat-only channel sessions)\n * hydrate through `hydrateFactorySession` with their own resolved tenant.\n * Best-effort: failures are logged, never thrown.\n */\nexport async function hydrateSessionMemorySettings(\n session: MemorySettingsHydrationSession,\n { sourceControl, projects, memorySettings }: MemorySettingsHydrationDependencies,\n): Promise<void> {\n const state = session.state.get() ?? {};\n const isFactoryRun = Boolean(state.factoryProjectId);\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 curation 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 const factoryProjectId = isFactoryRun ? String(state.factoryProjectId) : undefined;\n const settings = await memorySettings.get({\n orgId: record.orgId,\n userId: factoryProjectId ? factoryMemorySettingsUserId(factoryProjectId) : record.userId,\n });\n // Coordinator hydration applies a provider-aware fallback when no project row\n // exists. Do not replace that fallback with the generic OM default here.\n if (factoryProjectId && !settings) return;\n const project = factoryProjectId ? await projects.get({ orgId: record.orgId, id: factoryProjectId }) : null;\n const provider = project?.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider\n ? resolveProviderOMDefault(provider, project.defaultModelId ?? undefined).modelId\n : undefined;\n await applyStoredMemorySettings(\n session,\n factoryProjectId && settings && !fallbackOmModelId\n ? {\n ...settings,\n observerModelId: settings?.observerModelId ?? session.om.observer.modelId() ?? null,\n reflectorModelId: settings?.reflectorModelId ?? session.om.reflector.modelId() ?? null,\n }\n : settings,\n fallbackOmModelId,\n );\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":"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"}
|
|
1
|
+
{"version":3,"file":"memory-settings-hydration.js","names":[],"sources":["../../src/session/memory-settings-hydration.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\n\nimport type { MemorySettingsRecord } 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/**\n * Layer a channel sender's own row over the project's shared row. The sender\n * wins only for knobs they saved; an unsaved (`null`) knob keeps the project's\n * value rather than resetting to Auto or the built-in default, because the\n * project's choice may be the only credentialed one.\n */\nexport function layerPersonalMemorySettings(\n project: MemorySettingsRecord | null,\n personal: MemorySettingsRecord | null,\n): MemorySettingsRecord | null {\n if (!personal) return project;\n if (!project) return personal;\n return {\n ...project,\n observerModelId: personal.observerModelId ?? project.observerModelId,\n reflectorModelId: personal.reflectorModelId ?? project.reflectorModelId,\n observationThreshold: personal.observationThreshold ?? project.observationThreshold,\n reflectionThreshold: personal.reflectionThreshold ?? project.reflectionThreshold,\n observeAttachments: personal.observeAttachments ?? project.observeAttachments,\n };\n}\n\nexport interface MemorySettingsHydrationSession {\n readonly identity: { getResourceId(): string };\n state: {\n get: () => MastraCodeState | undefined;\n set: (updates: Partial<MastraCodeState>) => Promise<void> | void;\n };\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}\n\n/**\n * Seed a freshly created controller session's tenant org from its source-control\n * row. Memory settings remain DB-authoritative and are loaded into request\n * context for each invocation instead of being copied into mutable session\n * state.\n */\nexport async function hydrateSessionMemorySettings(\n session: MemorySettingsHydrationSession,\n { sourceControl }: MemorySettingsHydrationDependencies,\n): Promise<void> {\n const state: Partial<MastraCodeState> = session.state.get() ?? {};\n const isFactoryRun = Boolean(state.factoryProjectId);\n if (isFactoryRun && hasResolvedOrg(state.factoryOrgId)) return;\n\n try {\n const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());\n await seedSessionOrg(session, record?.orgId);\n } catch (error) {\n console.warn('[Factory session hydration] Unable to resolve the session organization.', error);\n if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, undefined);\n }\n}\n"],"mappings":";;;AAOA,MAAa,gCAAgC;AAC7C,MAAa,+BAA+B;;;;;;;AAQ5C,SAAgB,4BACd,SACA,UAC6B;CAC7B,IAAI,CAAC,UAAU,OAAO;CACtB,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO;EACL,GAAG;EACH,iBAAiB,SAAS,mBAAmB,QAAQ;EACrD,kBAAkB,SAAS,oBAAoB,QAAQ;EACvD,sBAAsB,SAAS,wBAAwB,QAAQ;EAC/D,qBAAqB,SAAS,uBAAuB,QAAQ;EAC7D,oBAAoB,SAAS,sBAAsB,QAAQ;CAC7D;AACF;;;;;;;AAuBA,eAAsB,6BACpB,SACA,EAAE,iBACa;CACf,MAAM,QAAkC,QAAQ,MAAM,IAAI,KAAK,CAAC;CAEhE,IADqB,QAAQ,MAAM,gBACpB,KAAK,eAAe,MAAM,YAAY,GAAG;CAExD,IAAI;EAEF,MAAM,eAAe,UAAS,MADT,cAAc,SAAS,eAAe,QAAQ,SAAS,cAAc,CAAC,EAAA,EACrD,KAAK;CAC7C,SAAS,OAAO;EACd,QAAQ,KAAK,2EAA2E,KAAK;EAC7F,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,EAAE,cAAc,MAAM,eAAe,SAAS,KAAA,CAAS;CACjF;AACF"}
|
|
@@ -18,23 +18,14 @@ export interface MemorySettingsRecord {
|
|
|
18
18
|
createdAt: Date;
|
|
19
19
|
updatedAt: Date;
|
|
20
20
|
}
|
|
21
|
-
/** Partial update — only the provided knobs are written. */
|
|
21
|
+
/** Partial update — only the provided knobs are written. `null` resets a model role to auto. */
|
|
22
22
|
export interface MemorySettingsPatch {
|
|
23
|
-
observerModelId?: string;
|
|
24
|
-
reflectorModelId?: string;
|
|
23
|
+
observerModelId?: string | null;
|
|
24
|
+
reflectorModelId?: string | null;
|
|
25
25
|
observationThreshold?: number;
|
|
26
26
|
reflectionThreshold?: number;
|
|
27
27
|
observeAttachments?: 'auto' | boolean;
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Knobs written only when the stored value is still unset (`NULL`). The check
|
|
31
|
-
* happens inside the backend's atomic-update primitive, so a concurrent
|
|
32
|
-
* explicit write to the same knob is never clobbered by a fill.
|
|
33
|
-
*/
|
|
34
|
-
export interface MemorySettingsFillIfUnset {
|
|
35
|
-
observerModelId?: string;
|
|
36
|
-
reflectorModelId?: string;
|
|
37
|
-
}
|
|
38
29
|
/**
|
|
39
30
|
* Sentinel `user_id` for a factory project's shared memory settings. Factory
|
|
40
31
|
* runs are org-shared, so their OM configuration lives on the project — this
|
|
@@ -55,16 +46,13 @@ export declare class MemorySettingsStorage extends FactoryStorageDomain {
|
|
|
55
46
|
}): Promise<MemorySettingsRecord | null>;
|
|
56
47
|
/**
|
|
57
48
|
* Upsert the user's row, writing only the knobs present in `patch`.
|
|
58
|
-
*
|
|
59
|
-
* `NULL`, decided inside the atomic update so concurrent explicit writes
|
|
60
|
-
* win over fills. Concurrent first writes are resolved via the shared
|
|
49
|
+
* Concurrent first writes are resolved via the shared
|
|
61
50
|
* insert-then-catch-unique-violation pattern (see `queue_health`).
|
|
62
51
|
*/
|
|
63
|
-
patch({ orgId, userId, patch,
|
|
52
|
+
patch({ orgId, userId, patch, }: {
|
|
64
53
|
orgId: string;
|
|
65
54
|
userId: string;
|
|
66
55
|
patch: MemorySettingsPatch;
|
|
67
|
-
fillIfUnset?: MemorySettingsFillIfUnset;
|
|
68
56
|
}): Promise<MemorySettingsRecord>;
|
|
69
57
|
}
|
|
70
58
|
//# sourceMappingURL=base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/memory-settings/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAqB,MAAM,sBAAsB,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,kBAAkB,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IAC5C,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/memory-settings/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAqB,MAAM,sBAAsB,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,kBAAkB,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IAC5C,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,gGAAgG;AAChG,MAAM,WAAW,mBAAmB;IAClC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,eAAO,MAAM,sBAAsB,EAAE,gBAgBpC,CAAC;AAuCF,qBAAa,qBAAsB,SAAQ,oBAAoB;;;IAKvD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAKrG;;;;OAIG;IACG,KAAK,CAAC,EACV,KAAK,EACL,MAAM,EACN,KAAK,GACN,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,mBAAmB,CAAC;KAC5B,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAiClC"}
|
|
@@ -88,33 +88,26 @@ var MemorySettingsStorage = class extends FactoryStorageDomain {
|
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
90
|
* Upsert the user's row, writing only the knobs present in `patch`.
|
|
91
|
-
*
|
|
92
|
-
* `NULL`, decided inside the atomic update so concurrent explicit writes
|
|
93
|
-
* win over fills. Concurrent first writes are resolved via the shared
|
|
91
|
+
* Concurrent first writes are resolved via the shared
|
|
94
92
|
* insert-then-catch-unique-violation pattern (see `queue_health`).
|
|
95
93
|
*/
|
|
96
|
-
async patch({ orgId, userId, patch
|
|
94
|
+
async patch({ orgId, userId, patch }) {
|
|
97
95
|
const now = /* @__PURE__ */ new Date();
|
|
98
96
|
const updateExisting = () => this.#db.updateAtomic("memory_settings", {
|
|
99
97
|
org_id: orgId,
|
|
100
98
|
user_id: userId
|
|
101
|
-
}, (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
};
|
|
106
|
-
if (fillIfUnset?.observerModelId !== void 0 && row.observer_model_id == null) columns.observer_model_id = patch.observerModelId ?? fillIfUnset.observerModelId;
|
|
107
|
-
if (fillIfUnset?.reflectorModelId !== void 0 && row.reflector_model_id == null) columns.reflector_model_id = patch.reflectorModelId ?? fillIfUnset.reflectorModelId;
|
|
108
|
-
return columns;
|
|
109
|
-
});
|
|
99
|
+
}, () => ({
|
|
100
|
+
...patchToColumns(patch),
|
|
101
|
+
updated_at: now
|
|
102
|
+
}));
|
|
110
103
|
const updated = await updateExisting();
|
|
111
104
|
if (updated) return toRecord(updated);
|
|
112
105
|
try {
|
|
113
106
|
return toRecord(await this.#db.insertOne("memory_settings", {
|
|
114
107
|
org_id: orgId,
|
|
115
108
|
user_id: userId,
|
|
116
|
-
observer_model_id:
|
|
117
|
-
reflector_model_id:
|
|
109
|
+
observer_model_id: null,
|
|
110
|
+
reflector_model_id: null,
|
|
118
111
|
observation_threshold: null,
|
|
119
112
|
reflection_threshold: null,
|
|
120
113
|
observe_attachments: null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","names":["#db"],"sources":["../../../../src/storage/domains/memory-settings/base.ts"],"sourcesContent":["import { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\n\n/**\n * A user's persisted memory (observational-memory) configuration. The\n * DB-backed tenant counterpart of the local `settings.json` OM fields\n * (`observerModelOverride`, `reflectorModelOverride`, `omObservationThreshold`,\n * `omReflectionThreshold`, `omObserveAttachments`): one row per `(org, user)`,\n * every knob nullable so only explicitly-changed values are stored.\n */\nexport interface MemorySettingsRecord {\n orgId: string;\n userId: string;\n observerModelId: string | null;\n reflectorModelId: string | null;\n observationThreshold: number | null;\n reflectionThreshold: number | null;\n observeAttachments: 'auto' | boolean | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/** Partial update — only the provided knobs are written. */\nexport interface MemorySettingsPatch {\n observerModelId?: string;\n reflectorModelId?: string;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"base.js","names":["#db"],"sources":["../../../../src/storage/domains/memory-settings/base.ts"],"sourcesContent":["import { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\n\n/**\n * A user's persisted memory (observational-memory) configuration. The\n * DB-backed tenant counterpart of the local `settings.json` OM fields\n * (`observerModelOverride`, `reflectorModelOverride`, `omObservationThreshold`,\n * `omReflectionThreshold`, `omObserveAttachments`): one row per `(org, user)`,\n * every knob nullable so only explicitly-changed values are stored.\n */\nexport interface MemorySettingsRecord {\n orgId: string;\n userId: string;\n observerModelId: string | null;\n reflectorModelId: string | null;\n observationThreshold: number | null;\n reflectionThreshold: number | null;\n observeAttachments: 'auto' | boolean | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/** Partial update — only the provided knobs are written. `null` resets a model role to auto. */\nexport interface MemorySettingsPatch {\n observerModelId?: string | null;\n reflectorModelId?: string | null;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n}\n\n/**\n * Sentinel `user_id` for a factory project's shared memory settings. Factory\n * runs are org-shared, so their OM configuration lives on the project — this\n * key reuses the same `(org, user)` table without a schema change. Real user\n * ids are WorkOS ids (`user_…`) or the `local` sentinel, so the prefix cannot\n * collide.\n */\nexport function factoryMemorySettingsUserId(factoryProjectId: string): string {\n return `factory-project:${factoryProjectId}`;\n}\n\nexport const MEMORY_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'memory_settings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n observer_model_id: { type: 'text', nullable: true },\n reflector_model_id: { type: 'text', nullable: true },\n observation_threshold: { type: 'integer', nullable: true },\n reflection_threshold: { type: 'integer', nullable: true },\n // 'auto' | true | false — json keeps the tri-state without string encoding.\n observe_attachments: { type: 'json', nullable: true },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'memory_settings_org_user_key', columns: ['org_id', 'user_id'] }],\n};\n\ninterface MemorySettingsDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n user_id: string;\n observer_model_id: string | null;\n reflector_model_id: string | null;\n observation_threshold: number | null;\n reflection_threshold: number | null;\n observe_attachments: 'auto' | boolean | null;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction toRecord(row: MemorySettingsDbRow): MemorySettingsRecord {\n return {\n orgId: row.org_id,\n userId: row.user_id,\n observerModelId: row.observer_model_id,\n reflectorModelId: row.reflector_model_id,\n observationThreshold: row.observation_threshold,\n reflectionThreshold: row.reflection_threshold,\n observeAttachments: row.observe_attachments,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nfunction patchToColumns(patch: MemorySettingsPatch): Partial<MemorySettingsDbRow> {\n const columns: Partial<MemorySettingsDbRow> = {};\n if (patch.observerModelId !== undefined) columns.observer_model_id = patch.observerModelId;\n if (patch.reflectorModelId !== undefined) columns.reflector_model_id = patch.reflectorModelId;\n if (patch.observationThreshold !== undefined) columns.observation_threshold = patch.observationThreshold;\n if (patch.reflectionThreshold !== undefined) columns.reflection_threshold = patch.reflectionThreshold;\n if (patch.observeAttachments !== undefined) columns.observe_attachments = patch.observeAttachments;\n return columns;\n}\n\nexport class MemorySettingsStorage extends FactoryStorageDomain {\n constructor() {\n super('memory-settings');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MEMORY_SETTINGS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('memory_settings', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async get({ orgId, userId }: { orgId: string; userId: string }): Promise<MemorySettingsRecord | null> {\n const row = await this.#db.findOne<MemorySettingsDbRow>('memory_settings', { org_id: orgId, user_id: userId });\n return row ? toRecord(row) : null;\n }\n\n /**\n * Upsert the user's row, writing only the knobs present in `patch`.\n * Concurrent first writes are resolved via the shared\n * insert-then-catch-unique-violation pattern (see `queue_health`).\n */\n async patch({\n orgId,\n userId,\n patch,\n }: {\n orgId: string;\n userId: string;\n patch: MemorySettingsPatch;\n }): Promise<MemorySettingsRecord> {\n const now = new Date();\n const updateExisting = () =>\n this.#db.updateAtomic<MemorySettingsDbRow>('memory_settings', { org_id: orgId, user_id: userId }, () => ({\n ...patchToColumns(patch),\n updated_at: now,\n }));\n\n const updated = await updateExisting();\n if (updated) return toRecord(updated);\n\n try {\n const row = await this.#db.insertOne<MemorySettingsDbRow>('memory_settings', {\n org_id: orgId,\n user_id: userId,\n observer_model_id: null,\n reflector_model_id: null,\n observation_threshold: null,\n reflection_threshold: null,\n observe_attachments: null,\n ...patchToColumns(patch),\n created_at: now,\n updated_at: now,\n });\n return toRecord(row);\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n // Lost the first-write race — apply the patch to the winning row.\n const row = await updateExisting();\n if (!row) throw error;\n return toRecord(row);\n }\n }\n}\n"],"mappings":";;;;;;;;;AAsCA,SAAgB,4BAA4B,kBAAkC;CAC5E,OAAO,mBAAmB;AAC5B;AAEA,MAAa,yBAA2C;CACtD,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,QAAQ,EAAE,MAAM,OAAO;EACvB,SAAS,EAAE,MAAM,OAAO;EACxB,mBAAmB;GAAE,MAAM;GAAQ,UAAU;EAAK;EAClD,oBAAoB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACnD,uBAAuB;GAAE,MAAM;GAAW,UAAU;EAAK;EACzD,sBAAsB;GAAE,MAAM;GAAW,UAAU;EAAK;EAExD,qBAAqB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACpD,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,eAAe,CAAC;EAAE,MAAM;EAAgC,SAAS,CAAC,UAAU,SAAS;CAAE,CAAC;AAC1F;AAeA,SAAS,SAAS,KAAgD;CAChE,OAAO;EACL,OAAO,IAAI;EACX,QAAQ,IAAI;EACZ,iBAAiB,IAAI;EACrB,kBAAkB,IAAI;EACtB,sBAAsB,IAAI;EAC1B,qBAAqB,IAAI;EACzB,oBAAoB,IAAI;EACxB,WAAW,IAAI;EACf,WAAW,IAAI;CACjB;AACF;AAEA,SAAS,eAAe,OAA0D;CAChF,MAAM,UAAwC,CAAC;CAC/C,IAAI,MAAM,oBAAoB,KAAA,GAAW,QAAQ,oBAAoB,MAAM;CAC3E,IAAI,MAAM,qBAAqB,KAAA,GAAW,QAAQ,qBAAqB,MAAM;CAC7E,IAAI,MAAM,yBAAyB,KAAA,GAAW,QAAQ,wBAAwB,MAAM;CACpF,IAAI,MAAM,wBAAwB,KAAA,GAAW,QAAQ,uBAAuB,MAAM;CAClF,IAAI,MAAM,uBAAuB,KAAA,GAAW,QAAQ,sBAAsB,MAAM;CAChF,OAAO;AACT;AAEA,IAAa,wBAAb,cAA2C,qBAAqB;CAC9D,cAAc;EACZ,MAAM,iBAAiB;CACzB;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,kBAAkB,CAAC,sBAAsB,CAAC;CACvD;CAEA,MAAM,sBAAqC;EACzC,MAAM,KAAK,IAAI,WAAW,mBAAmB,CAAC,CAAC;CACjD;CAEA,IAAIA,MAAyB;EAC3B,OAAO,KAAK;CACd;CAEA,MAAM,IAAI,EAAE,OAAO,UAAmF;EACpG,MAAM,MAAM,MAAM,KAAKA,IAAI,QAA6B,mBAAmB;GAAE,QAAQ;GAAO,SAAS;EAAO,CAAC;EAC7G,OAAO,MAAM,SAAS,GAAG,IAAI;CAC/B;;;;;;CAOA,MAAM,MAAM,EACV,OACA,QACA,SAKgC;EAChC,MAAM,sBAAM,IAAI,KAAK;EACrB,MAAM,uBACJ,KAAKA,IAAI,aAAkC,mBAAmB;GAAE,QAAQ;GAAO,SAAS;EAAO,UAAU;GACvG,GAAG,eAAe,KAAK;GACvB,YAAY;EACd,EAAE;EAEJ,MAAM,UAAU,MAAM,eAAe;EACrC,IAAI,SAAS,OAAO,SAAS,OAAO;EAEpC,IAAI;GAaF,OAAO,SAAS,MAZE,KAAKA,IAAI,UAA+B,mBAAmB;IAC3E,QAAQ;IACR,SAAS;IACT,mBAAmB;IACnB,oBAAoB;IACpB,uBAAuB;IACvB,sBAAsB;IACtB,qBAAqB;IACrB,GAAG,eAAe,KAAK;IACvB,YAAY;IACZ,YAAY;GACd,CAAC,CACkB;EACrB,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;GAEpD,MAAM,MAAM,MAAM,eAAe;GACjC,IAAI,CAAC,KAAK,MAAM;GAChB,OAAO,SAAS,GAAG;EACrB;CACF;AACF"}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import type { MastraCodeState } from '@mastra/code-sdk/schema';
|
|
8
8
|
import type { AgentController } from '@mastra/core/agent-controller';
|
|
9
9
|
import type { RequestContext } from '@mastra/core/request-context';
|
|
10
|
-
import type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';
|
|
11
10
|
import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
|
|
12
11
|
import type { SupervisorScope } from './read-tools.js';
|
|
13
12
|
type FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;
|
|
@@ -26,13 +25,12 @@ export declare function resolveSupervisorScope(options: {
|
|
|
26
25
|
}): Promise<SupervisorScope | null>;
|
|
27
26
|
/**
|
|
28
27
|
* Session-start hook: stamp the owning project onto a supervisor session and
|
|
29
|
-
* apply the factory's default model
|
|
30
|
-
*
|
|
28
|
+
* apply the factory's default model. Runs on every (re)creation so a restarted
|
|
29
|
+
* server heals the in-memory state. Sessions
|
|
31
30
|
* that are not supervisors are left untouched.
|
|
32
31
|
*/
|
|
33
32
|
export declare function hydrateSupervisorSession(session: FactorySession, deps: {
|
|
34
33
|
projects: Pick<FactoryProjectsStorage, 'getById'>;
|
|
35
|
-
memorySettings?: MemorySettingsStorage;
|
|
36
34
|
}): Promise<void>;
|
|
37
35
|
export {};
|
|
38
36
|
//# sourceMappingURL=session.d.ts.map
|