@mastra/factory 0.24.0-alpha.12 → 0.24.0-alpha.14
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/factory.d.ts.map +1 -1
- package/dist/factory.js +33 -4
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +5 -2
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/routes/fs.d.ts +31 -23
- package/dist/routes/fs.d.ts.map +1 -1
- package/dist/routes/fs.js +214 -61
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/session-access.d.ts +55 -0
- package/dist/routes/session-access.d.ts.map +1 -0
- package/dist/routes/session-access.js +165 -0
- package/dist/routes/session-access.js.map +1 -0
- package/dist/routes/source-control-sessions.d.ts +5 -0
- package/dist/routes/source-control-sessions.d.ts.map +1 -1
- package/dist/routes/source-control-sessions.js +86 -9
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/routes/surface.d.ts +5 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +2 -2
- package/dist/routes/surface.js.map +1 -1
- package/dist/session/resource-migration.d.ts +18 -0
- package/dist/session/resource-migration.d.ts.map +1 -1
- package/dist/session/resource-migration.js +33 -1
- package/dist/session/resource-migration.js.map +1 -1
- package/dist/session/session-threads.d.ts +9 -0
- package/dist/session/session-threads.d.ts.map +1 -1
- package/dist/session/session-threads.js +24 -4
- package/dist/session/session-threads.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +15 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +29 -0
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +5 -1
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +7 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +1 -1
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/workspace.d.ts +5 -2
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +11 -1
- package/dist/workspace.js.map +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface.js","names":[],"sources":["../../src/routes/surface.ts"],"sourcesContent":["import type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\nimport type { FactorySandbox } from '@mastra/core/workspace';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationHooks } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport { MaterializeError } from '../integrations/github/sandbox.js';\nimport { FactoryDispatchError } from '../rules/dispatch-errors.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport {\n createSourceControlSessionLookup,\n ensureFactorySourceSession,\n FactorySourceSessionResolutionError,\n resolveFactorySourceControl,\n} from '../session/factory-session.js';\nimport type { EnsuredFactorySourceSession } from '../session/factory-session.js';\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport { resolveWorkItemRepository } from '../session/work-item-repository.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter, AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type { CommentsDomain } from '../storage/domains/comments/domain.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { IntegrationStorage } from '../storage/domains/integrations/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport {\n SessionOwnershipMismatchError,\n SourceControlConnectionNotFoundError,\n} from '../storage/domains/source-control/base.js';\nimport type { SourceControlStorage, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { isAgentActor } from '../storage/domains/work-items/base.js';\nimport type { FactoryDispatchFailureCode, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { workItemBranch, workItemBranchSource, workItemThreadTitle } from '../work-item-branch.js';\nimport { buildAutomationRunRoutes } from './automation-runs.js';\nimport { ConfigRoutes } from './config.js';\nimport { invalidateCustomProvidersSnapshots } from './custom-provider-source.js';\nimport { buildFsRoutes } from './fs.js';\nimport { IntakeRoutes } from './intake.js';\nimport { KnowledgeRoutes } from './knowledge.js';\nimport { OAuthRoutes } from './oauth.js';\nimport type { RouteAuth } from './route.js';\nimport { SkillRoutes } from './skills.js';\nimport { buildSourceControlSessionRoutes } from './source-control-sessions.js';\nimport { buildSourceControlSettingsRoutes } from './source-control-settings.js';\nimport { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\nconst MATERIALIZE_FAILURE_CODE = {\n 'git-missing': 'repository_git_missing',\n 'egress-blocked': 'repository_egress_blocked',\n 'clone-failed': 'repository_clone_failed',\n 'pull-failed': 'repository_pull_failed',\n 'push-failed': 'repository_push_failed',\n 'commit-failed': 'repository_commit_failed',\n 'gh-missing': 'repository_cli_missing',\n 'pr-failed': 'repository_pr_failed',\n} satisfies Record<MaterializeError['code'], FactoryDispatchFailureCode>;\nexport interface IntegrationRegistration {\n integration: FactoryIntegration;\n ready: boolean;\n ensureReady: () => Promise<void>;\n}\n\nexport interface FactoryApiRoutesDeps {\n controllerId: string;\n controller: AgentController<MastraCodeState>;\n /** Registry of the sessions this process holds, owned by the host so it can watch them too. */\n liveSessions: LiveSessions;\n /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n /** Optional user directory for resolving persisted owners to display profiles. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n authStorage: AuthStorage;\n audit: AuditEmitter & AuditRecorder;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox surface (enablement, provider label, create callback). */\n sandbox?: FactorySandbox;\n /** Push hand-off for environment builds, present when the sandbox can build templates. */\n onRepositoryPush?: IntegrationHooks['onRepositoryPush'];\n /** Root factory storage backend (distributed locks, app-db diagnostics). */\n factoryStorage?: FactoryStorage;\n integrationStorage: IntegrationStorage;\n sourceControlStorage: SourceControlStorage;\n /** App-table domain handles, registered and owned by `MastraFactory.prepare()`. */\n domains: {\n intake: IntakeStorage;\n modelCredentials: ModelCredentialsStorage;\n memorySettings: MemorySettingsStorage;\n customProviders: CustomProvidersStorage;\n filesystem: FilesystemStorage;\n modelDefaults: ModelDefaultsStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n comments: WorkItemCommentsStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Providers the operator opted in to run on the server process's own credentials. */\n deploymentModelProviders?: ReadonlySet<string>;\n knowledgeKey?: string;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n configVersion: string;\n /** Boards installed for this Factory instance. */\n boardRegistry: BoardRegistry;\n /** Work-item feed service, handed to integrations that ingest platform messages. */\n feed: CommentsDomain;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\n startCoordinator: FactoryStartCoordinator;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n }) => void;\n}\n\nfunction guardIntegrationRoutes({\n integration,\n ready,\n ensureReady,\n routes,\n}: IntegrationRegistration & { routes: ApiRoute[] }): ApiRoute[] {\n if (ready) return routes;\n return routes.map(route => {\n if ('handler' in route) {\n const handler = route.handler;\n return {\n ...route,\n handler: async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context, async () => {});\n },\n };\n }\n\n const createHandler = route.createHandler;\n return {\n ...route,\n createHandler: async (args: Parameters<typeof createHandler>[0]) => {\n const handler = await createHandler(args);\n return async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context);\n };\n },\n };\n });\n}\n\n/**\n * Resolve the source-control session the work item already holds for this role,\n * when it still resolves to the item's org and factory project. Returns\n * `undefined` when there is no ref or the ref no longer resolves, so the caller\n * falls back to minting one.\n */\nasync function reuseBoundSession(\n sourceControl: SourceControlStorageHandle,\n input: FactoryBindingPreparationInput,\n role: string = input.role,\n): Promise<EnsuredFactorySourceSession | undefined> {\n const ref = input.item.sessions[role];\n if (!ref) return undefined;\n // At least as strict as the coordinator's resolveSourceSession: a ref it\n // would reject must fall through to minting, not hard-fail the run.\n const session = await sourceControl.sessions.getBySessionId(ref.sessionId);\n if (\n !session?.projectRepositoryId ||\n session.orgId !== input.record.orgId ||\n session.factoryProjectId !== input.record.factoryProjectId\n ) {\n return undefined;\n }\n return {\n sessionId: session.sessionId,\n userId: session.userId,\n projectRepositoryId: session.projectRepositoryId,\n branch: session.branch,\n baseBranch: session.baseBranch,\n };\n}\n\n/** Roles whose sessions a later role may continue in, newest stage first. */\nconst INHERITABLE_ROLE_FALLBACK = ['plan', 'triage'];\n\n/**\n * A role running for the first time continues in the card's latest earlier\n * session on the same branch, so a build approved by someone other than the\n * plan's owner keeps the plan's context instead of starting empty. Review is\n * independent by design and never inherits.\n */\nasync function inheritEarlierSession(\n sourceControl: SourceControlStorageHandle,\n input: FactoryBindingPreparationInput,\n board: { roleForPhase(phase: string): string | undefined } | undefined,\n branch: string,\n projectRepositoryId: string,\n): Promise<EnsuredFactorySourceSession | undefined> {\n if (input.role === 'review') return undefined;\n // The card's linked repository can change after an earlier role ran; only\n // continue in a session on the repository a fresh session would use.\n const candidates: string[] = [];\n for (const entry of [...(input.item.stageHistory ?? [])].reverse()) {\n const role = board?.roleForPhase(entry.stage);\n if (role && role !== input.role && role !== 'review' && !candidates.includes(role)) candidates.push(role);\n }\n for (const role of INHERITABLE_ROLE_FALLBACK) {\n if (role !== input.role && !candidates.includes(role)) candidates.push(role);\n }\n for (const role of candidates) {\n const session = await reuseBoundSession(sourceControl, input, role);\n if (session && session.branch === branch && session.projectRepositoryId === projectRepositoryId) {\n return session;\n }\n }\n return undefined;\n}\n\n/**\n * Start a factory run for a rule binding: ensure the source-control session the\n * coordinator requires, then hand it to `prepare` along with the factory's\n * default model. Exported for tests — this is the autonomous entry point with no\n * browser and no interactive user, so nothing else would catch a regression in\n * what it forwards.\n */\nexport async function prepareFactoryRuleBinding(\n sourceControlProvider: SourceControlStorageHandle | Pick<GithubIntegration, 'sourceControlStorage'>,\n coordinator: Pick<FactoryStartCoordinator, 'prepare'>,\n projects: FactoryProjectsStorage,\n boards: BoardRegistry,\n input: FactoryBindingPreparationInput,\n intake?: Pick<IntakeStorage, 'getConfig'>,\n): Promise<void> {\n try {\n const sourceControl =\n 'sourceControlStorage' in sourceControlProvider\n ? sourceControlProvider.sourceControlStorage\n : sourceControlProvider;\n const source = workItemBranchSource(input.item.externalSource);\n const branch = workItemBranch({ id: input.item.id, source, metadata: input.item.metadata });\n // Only leaving a resting phase derives a lane from the role: roles don't own lanes,\n // and the Done close-out running in the triage seat must not drag the card back.\n // Both the current phase and the role's lane come from the installed board; an\n // unknown board or phase yields no destination rather than a guessed one.\n const board = boards.get(boardForWorkItem(input.item));\n const currentStage = input.item.stages.length === 1 ? input.item.stages[0] : undefined;\n const currentKind = currentStage === undefined ? undefined : board?.phaseKind(currentStage);\n const destinationStage =\n currentKind === undefined\n ? undefined\n : currentKind === 'resting'\n ? board?.phaseForRole(input.role)\n : currentStage;\n if (!destinationStage) {\n throw new FactoryDispatchError(\n 'unsupported_provider_item',\n `Factory skill invocation has no destination lane (role \"${input.role}\", stages [${input.item.stages.join(', ')}]).`,\n );\n }\n const connections = await sourceControl.connections.list({\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n });\n if (!connections.some(connection => connection.integrationId === sourceControl.integrationId)) {\n throw new FactoryDispatchError('source_control_missing', 'Factory source-control connection not found.');\n }\n const project = await projects.getById({ id: input.record.factoryProjectId });\n if (!project || project.orgId !== input.record.orgId) {\n throw new FactoryDispatchError(\n 'session_unavailable',\n `Factory model policy is unavailable for project ${input.record.factoryProjectId}.`,\n );\n }\n // A retry may already own a role session. Check it before treating a\n // repository-less card as ambiguous, but never override an explicit signal.\n const boundSession = await reuseBoundSession(sourceControl, input);\n const intakeConfig = await intake?.getConfig({ orgId: input.record.orgId, integrationIds: ['linear'] });\n let repository = await resolveWorkItemRepository({\n sourceControl,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n item: input.item,\n linearRepositoryMap: intakeConfig?.linear?.repositoryByLinearProject,\n });\n if (repository.status === 'ambiguous' && boundSession) {\n const link = await sourceControl.projectRepositories.get({\n orgId: input.record.orgId,\n id: boundSession.projectRepositoryId,\n });\n const linked =\n link && (await sourceControl.repositories.get({ orgId: input.record.orgId, id: link.repositoryId }));\n if (linked && repository.candidates.includes(linked.slug)) {\n repository = { status: 'resolved', slug: linked.slug, projectRepositoryId: link.id };\n }\n }\n if (repository.status !== 'resolved') {\n const detail =\n repository.status === 'ambiguous'\n ? `Choose one of the linked repositories: ${repository.candidates.join(', ')}.`\n : repository.hint;\n throw new FactoryDispatchError('source_repository_ambiguous', detail);\n }\n // Re-preparing a binding (server restart, retired controller session) must\n // land in the role's existing session: minting a replacement would repoint\n // the work item, flip the session's owner to the approver, and orphan the\n // previous sandbox.\n const approver = input.record.approvedBy ?? undefined;\n if (boundSession && boundSession.projectRepositoryId !== repository.projectRepositoryId) {\n throw new FactoryDispatchError(\n 'source_repository_ambiguous',\n `The existing session is bound to a different repository than ${repository.slug}. Choose the correct repository and retry.`,\n );\n }\n // The session is filed under the card's repository: the sandbox holds\n // every environment repository, but the coordinator, the PR tools and the\n // subscriptions follow the session's link, and the card names which one.\n const preparedSession =\n boundSession ??\n (await inheritEarlierSession(sourceControl, input, board, branch, repository.projectRepositoryId)) ??\n (await ensureFactorySourceSession({\n sourceControl,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n projectRepositoryId: repository.projectRepositoryId,\n branch,\n // A person who approved the run is its interactive user: attribute it to\n // them, not the repo connector. An agent's pre-approval names no person.\n attributeToUserId: isAgentActor(approver) ? undefined : approver,\n }).catch(error => {\n if (error instanceof FactorySourceSessionResolutionError && error.reason === 'repository') {\n // The card resolved to a linked repository the sandbox will not check out.\n throw new FactoryDispatchError(\n 'source_repository_missing',\n `Repository ${repository.slug} is linked but not in this Factory's environment. Turn it on under Settings → Environment, then retry.`,\n { cause: error },\n );\n }\n if (!(error instanceof SessionOwnershipMismatchError)) throw error;\n throw new FactoryDispatchError(\n 'session_unavailable',\n `Session ${error.sessionId} on branch ${branch} predates factory ownership and cannot be reused. Delete that session, then retry.`,\n { cause: error },\n );\n }));\n\n const modelScope = effectiveModelScope(project.modelScope, 'run');\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n modelScope,\n defaultModelId: modelScope === 'factory' ? (project.defaultModelId ?? undefined) : undefined,\n threadTitle: workItemThreadTitle({ source, title: input.item.title, metadata: input.item.metadata }),\n kickoffKey: input.record.id,\n destinationStage,\n workItem: {\n id: input.item.id,\n role: input.role,\n input: {\n externalSource: input.item.externalSource,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n stages: ['intake'],\n sessions: input.item.sessions,\n metadata: input.item.metadata,\n },\n },\n });\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n if (error instanceof FactorySourceSessionResolutionError) {\n const code = error.reason === 'connection' ? 'source_control_missing' : 'source_repository_missing';\n throw new FactoryDispatchError(code, error.message, { cause: error });\n }\n if (error instanceof SourceControlConnectionNotFoundError) {\n throw new FactoryDispatchError('source_control_missing', error.message, { cause: error });\n }\n if (error instanceof MaterializeError) {\n throw new FactoryDispatchError(MATERIALIZE_FAILURE_CODE[error.code], error.message, { cause: error });\n }\n throw error;\n }\n}\n\n/**\n * Build the {@link IntegrationContext} handed to an integration when the\n * factory collects its capabilities (routes, workers). One shape everywhere:\n * `assembleFactoryApiRoutes` uses it per registration, and `MastraFactory` uses it\n * when collecting integration workers at finalize.\n */\nexport function buildIntegrationContext(\n deps: Pick<\n FactoryApiRoutesDeps,\n | 'controller'\n | 'publicOrigin'\n | 'auth'\n | 'sandbox'\n | 'users'\n | 'factoryStorage'\n | 'integrationStorage'\n | 'sourceControlStorage'\n | 'integrations'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n onRepositoryPush?: IntegrationHooks['onRepositoryPush'];\n configVersion: string;\n boardRegistry: BoardRegistry;\n factoryReady: boolean;\n /** Work-item feed service, so a channel integration can ingest platform messages. */\n feed: CommentsDomain;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings' | 'modelDefaults'\n >;\n /**\n * Stable id of the registered source-control-owning integration (today:\n * `'github'` when registered). Every call site must derive and pass it so\n * `routes()`, `channels()`, and `workers()` all see the same context shape.\n */\n sourceControlOwnerId?: string;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n sandbox: deps.sandbox,\n ...(deps.users ? { users: deps.users } : {}),\n factoryStorage: deps.factoryStorage,\n baseUrl: deps.publicOrigin,\n controller: deps.controller,\n stateSigner: deps.stateSigner,\n storage: {\n generic: deps.integrationStorage.forIntegration(integrationId),\n sourceControl: deps.sourceControlStorage.forIntegration(integrationId),\n ...(deps.sourceControlOwnerId\n ? { sourceControlOwner: deps.sourceControlStorage.forIntegration(deps.sourceControlOwnerId) }\n : {}),\n sourceControls: (deps.integrations ?? [])\n .filter(({ integration }) => integration.versionControl)\n .map(({ integration }) => deps.sourceControlStorage.forIntegration(integration.id)),\n projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n modelDefaults: deps.domains.modelDefaults,\n },\n ...(deps.factoryReady ? { workItems: deps.domains.workItems, feed: deps.feed } : {}),\n ...(deps.factoryReady\n ? {\n runtime: { configVersion: deps.configVersion, workItems: deps.domains.workItems, boards: deps.boardRegistry },\n }\n : {}),\n ...(deps.emitAudit || deps.onRepositoryPush\n ? {\n hooks: {\n ...(deps.emitAudit ? { emitAudit: deps.emitAudit } : {}),\n ...(deps.onRepositoryPush ? { onRepositoryPush: deps.onRepositoryPush } : {}),\n },\n }\n : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status`, `/web/linear/status`, and `/web/jira/status`\n * unconditionally, so when an integration is absent (or not ready) the status\n * contract must still hold. Unknown custom ids get no stub — the SPA doesn't\n * poll them.\n */\nfunction disabledIntegrationStatusRoutes(deps: FactoryApiRoutesDeps, id: string, configured = false): ApiRoute[] {\n if (id === 'github') {\n return [\n registerApiRoute('/web/github/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n installations: [],\n reason: 'missing_config',\n diagnostics: getGithubFeatureDiagnostics({\n github: undefined,\n auth: deps.auth,\n appDbConfigured: deps.factoryStorage !== undefined,\n stateSigner: deps.stateSigner,\n sandbox: deps.sandbox,\n }),\n }),\n }),\n ];\n }\n if (id === 'linear') {\n return [\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: {\n linearAppConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: true,\n },\n }),\n }),\n ];\n }\n if (id === 'jira') {\n return [\n registerApiRoute('/web/jira/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n configured,\n site: null,\n reason: 'missing_config',\n diagnostics: {\n jiraConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: deps.factoryStorage !== undefined,\n },\n }),\n }),\n ];\n }\n return [];\n}\n\n/**\n * Stub for `GET /web/channel-accounts` when NO Slack integration is\n * registered. The SPA's Connections section polls the path unconditionally;\n * without a stub the SPA fallback serves HTML, which the UI can only read as\n * \"old server / unknown\". The machine-readable reason lets it say the truth:\n * the integration isn't registered.\n *\n * Mounted only for ABSENT slack — a registered integration owns the path via\n * its connect routes (or, when the state signer is unstable, gets no routes\n * at all and the UI falls back to the generic copy). Static payload, leaks\n * nothing → no auth needed, same posture as the github/linear stubs.\n */\nfunction absentSlackChannelAccountsRoutes(): ApiRoute[] {\n return [\n registerApiRoute('/web/channel-accounts', {\n method: 'GET',\n requiresAuth: false,\n handler: c => c.json({ accounts: [], canConnect: false, reason: 'not_registered' }),\n }),\n ];\n}\n\n/**\n * Assemble the custom `/web/*` API routes as Mastra `server.apiRoutes`:\n * - fs browser routes (project picker), confined to `fsRoot`\n * - config routes (provider/API-key/default-model/OM management)\n * - every registered integration's `routes()` surface (full set when ready,\n * disabled-status stub otherwise), plus stubs for absent known ids\n */\nexport function assembleFactoryApiRoutes(deps: FactoryApiRoutesDeps): ApiRoute[] {\n const emitAudit: AuditEmitter['emit'] = args => deps.audit.emit(args);\n const registrations = deps.integrations ?? [];\n const githubRegistration = registrations.find(({ integration }) => integration.id === 'github');\n const githubStorage = githubRegistration ? deps.sourceControlStorage.forIntegration('github') : undefined;\n const githubIntegration = githubRegistration?.integration as GithubIntegration | undefined;\n const sourceControlRegistrations = registrations.filter(({ integration }) => integration.versionControl);\n const sourceControlIntegrationIds = [\n ...new Set(['github', ...sourceControlRegistrations.map(({ integration }) => integration.id)]),\n ];\n const sourceControls = sourceControlIntegrationIds.map(id => deps.sourceControlStorage.forIntegration(id));\n const sourceControlSessions = createSourceControlSessionLookup(sourceControls);\n\n const integrationRoutes = registrations.flatMap(registration => {\n const { integration } = registration;\n if (!deps.stateSigner) return disabledIntegrationStatusRoutes(deps, integration.id, true);\n const context = buildIntegrationContext(\n {\n ...deps,\n stateSigner: deps.stateSigner,\n emitAudit,\n ...(githubRegistration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n return guardIntegrationRoutes({ ...registration, routes: integration.routes(context) });\n });\n // Session persistence belongs to the linked source-control provider, not to\n // GitHub. Replace legacy GitHub-owned handlers with one resolver spanning\n // the legacy GitHub partition and every registered source-control partition.\n const sharedSessionRouteKeys = new Set([\n 'GET /web/github/projects/:id/sessions',\n 'POST /web/github/projects/:id/sessions',\n 'GET /web/user-sessions/:sessionId',\n 'DELETE /web/user-sessions/:sessionId',\n 'POST /web/user-sessions/:sessionId/title',\n ]);\n const providerRoutes = integrationRoutes.filter(\n route => !sharedSessionRouteKeys.has(route.method + ' ' + route.path),\n );\n const sourceControlSessionRoutes =\n sourceControls.length === 0\n ? []\n : buildSourceControlSessionRoutes({\n auth: deps.auth,\n sourceControls,\n projects: deps.domains.projects,\n ...(deps.users ? { users: deps.users } : {}),\n controller: deps.controller,\n memorySettings: deps.domains.memorySettings,\n sessionRetirement: deps.sessionRetirement,\n // A local sandbox's destroy keeps the session directory, so recreate\n // couldn't deliver the fresh checkout it promises there.\n sandboxRecreateSupported: deps.sandbox !== undefined && deps.sandbox.provider !== 'local',\n filesystem: deps.domains.filesystem,\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n ...(githubStorage ? { githubRepositories: githubStorage.repositories } : {}),\n });\n // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear', 'jira']\n .filter(id => !registrations.some(({ integration }) => integration.id === id))\n .flatMap(id => disabledIntegrationStatusRoutes(deps, id));\n // Absent slack gets the channel-accounts not-registered stub (registered\n // slack owns the path via its own connect routes).\n const slackAbsentStubs = registrations.some(({ integration }) => integration.id === 'slack')\n ? []\n : absentSlackChannelAccountsRoutes();\n\n const transitionService = deps.factoryReady\n ? (deps.factoryTransitionService ??\n new FactoryTransitionService({\n configVersion: deps.configVersion,\n boards: deps.boardRegistry,\n storage: deps.domains.workItems,\n audit: deps.audit,\n }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n request =>\n resolveFactorySourceControl({\n sourceControls,\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n sessionId: request.sessionId,\n }),\n deps.domains.intake,\n { projects: deps.domains.projects, modelDefaults: deps.domains.modelDefaults },\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n startCoordinator,\n ...(sourceControlRegistrations.length > 0\n ? {\n prepareBinding: async (input: FactoryBindingPreparationInput) => {\n const sourceControl = await resolveFactorySourceControl({\n sourceControls,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n });\n if (!sourceControl) {\n throw new FactoryDispatchError(\n 'source_control_missing',\n 'Factory project has no linked source-control repository.',\n );\n }\n return prepareFactoryRuleBinding(\n sourceControl,\n startCoordinator,\n deps.domains.projects,\n deps.boardRegistry,\n input,\n deps.domains.intake,\n );\n },\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n sessions: sourceControlSessions,\n filesystem: deps.domains.filesystem,\n },\n }),\n ...new ConfigRoutes({\n auth: deps.auth,\n controller: deps.controller,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n modelDefaults: deps.domains.modelDefaults,\n sourceControlSessions,\n sourceControls,\n workItems: deps.domains.workItems,\n memorySettings: deps.domains.memorySettings,\n factoryProjects: deps.domains.projects,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\n deploymentProviders: deps.deploymentModelProviders,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n onCustomProvidersChanged: invalidateCustomProvidersSnapshots,\n }).routes(),\n ...new OAuthRoutes({\n auth: deps.auth,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n }).routes(),\n ...new SkillRoutes({\n auth: deps.auth,\n controllerId: deps.controllerId,\n controller: deps.controller,\n sourceControlStorage: githubStorage,\n ensureSourceControlReady: githubRegistration?.ensureReady,\n }).routes(),\n ...sourceControlSessionRoutes,\n ...(sourceControls.length === 0 ? [] : buildSourceControlSettingsRoutes({ auth: deps.auth, sourceControls })),\n ...providerRoutes,\n ...absentStubs,\n ...slackAbsentStubs,\n ...(deps.intakeReady\n ? new IntakeRoutes({\n auth: deps.auth,\n audit: deps.audit,\n intake: deps.domains.intake,\n projects: deps.domains.projects,\n boardRegistry: deps.boardRegistry,\n // Relocation reads and writes work items, which init fail-soft separately from intake.\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n integrations: (deps.integrations ?? []).flatMap(({ integration }) =>\n integration.intake ? [{ id: integration.id, intake: integration.intake }] : [],\n ),\n }).routes()\n : []),\n ...(deps.factoryReady && deps.knowledgeEnabled\n ? new KnowledgeRoutes({\n auth: deps.auth,\n projects: deps.domains.projects,\n knowledge: async key => deps.controller.getMastra()?.getKnowledge(key),\n defaultKnowledgeKey: deps.knowledgeKey,\n threadTitle: async threadId => {\n const memory = await deps.controller.getMastra()?.getStorage()?.getStore('memory');\n return (await memory?.getThreadById({ threadId }))?.title;\n },\n }).routes()\n : []),\n ...(deps.factoryReady\n ? new WorkItemRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n boardRegistry: deps.boardRegistry,\n comments: deps.domains.comments,\n queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: deps.liveSessions,\n }).routes()\n : []),\n ...(deps.factoryReady\n ? buildAutomationRunRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n configVersion: deps.configVersion,\n })\n : []),\n ];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"surface.js","names":[],"sources":["../../src/routes/surface.ts"],"sourcesContent":["import type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\nimport type { FactorySandbox } from '@mastra/core/workspace';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationHooks } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport { MaterializeError } from '../integrations/github/sandbox.js';\nimport { FactoryDispatchError } from '../rules/dispatch-errors.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport {\n createSourceControlSessionLookup,\n ensureFactorySourceSession,\n FactorySourceSessionResolutionError,\n resolveFactorySourceControl,\n} from '../session/factory-session.js';\nimport type { EnsuredFactorySourceSession } from '../session/factory-session.js';\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport { resolveWorkItemRepository } from '../session/work-item-repository.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter, AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type { CommentsDomain } from '../storage/domains/comments/domain.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { IntegrationStorage } from '../storage/domains/integrations/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport {\n SessionOwnershipMismatchError,\n SourceControlConnectionNotFoundError,\n} from '../storage/domains/source-control/base.js';\nimport type { SourceControlStorage, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { isAgentActor } from '../storage/domains/work-items/base.js';\nimport type { FactoryDispatchFailureCode, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { workItemBranch, workItemBranchSource, workItemThreadTitle } from '../work-item-branch.js';\nimport { buildAutomationRunRoutes } from './automation-runs.js';\nimport { ConfigRoutes } from './config.js';\nimport { invalidateCustomProvidersSnapshots } from './custom-provider-source.js';\nimport { buildFsRoutes } from './fs.js';\nimport { IntakeRoutes } from './intake.js';\nimport { KnowledgeRoutes } from './knowledge.js';\nimport { OAuthRoutes } from './oauth.js';\nimport type { RouteAuth } from './route.js';\nimport { SkillRoutes } from './skills.js';\nimport { buildSourceControlSessionRoutes } from './source-control-sessions.js';\nimport { buildSourceControlSettingsRoutes } from './source-control-settings.js';\nimport { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\nconst MATERIALIZE_FAILURE_CODE = {\n 'git-missing': 'repository_git_missing',\n 'egress-blocked': 'repository_egress_blocked',\n 'clone-failed': 'repository_clone_failed',\n 'pull-failed': 'repository_pull_failed',\n 'push-failed': 'repository_push_failed',\n 'commit-failed': 'repository_commit_failed',\n 'gh-missing': 'repository_cli_missing',\n 'pr-failed': 'repository_pr_failed',\n} satisfies Record<MaterializeError['code'], FactoryDispatchFailureCode>;\nexport interface IntegrationRegistration {\n integration: FactoryIntegration;\n ready: boolean;\n ensureReady: () => Promise<void>;\n}\n\nexport interface FactoryApiRoutesDeps {\n controllerId: string;\n controller: AgentController<MastraCodeState>;\n /** Registry of the sessions this process holds, owned by the host so it can watch them too. */\n liveSessions: LiveSessions;\n /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n /** Optional user directory for resolving persisted owners to display profiles. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n authStorage: AuthStorage;\n audit: AuditEmitter & AuditRecorder;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox surface (enablement, provider label, create callback). */\n sandbox?: FactorySandbox;\n /** Push hand-off for environment builds, present when the sandbox can build templates. */\n onRepositoryPush?: IntegrationHooks['onRepositoryPush'];\n /** Root factory storage backend (distributed locks, app-db diagnostics). */\n factoryStorage?: FactoryStorage;\n integrationStorage: IntegrationStorage;\n sourceControlStorage: SourceControlStorage;\n /** App-table domain handles, registered and owned by `MastraFactory.prepare()`. */\n domains: {\n intake: IntakeStorage;\n modelCredentials: ModelCredentialsStorage;\n memorySettings: MemorySettingsStorage;\n customProviders: CustomProvidersStorage;\n filesystem: FilesystemStorage;\n modelDefaults: ModelDefaultsStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n comments: WorkItemCommentsStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Providers the operator opted in to run on the server process's own credentials. */\n deploymentModelProviders?: ReadonlySet<string>;\n knowledgeKey?: string;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n configVersion: string;\n /** Boards installed for this Factory instance. */\n boardRegistry: BoardRegistry;\n /** Work-item feed service, handed to integrations that ingest platform messages. */\n feed: CommentsDomain;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n /** Moves a thread with its observation vectors onto another memory resource. */\n moveThread?: (input: { threadId: string; resourceId: string }) => Promise<unknown>;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\n startCoordinator: FactoryStartCoordinator;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n }) => void;\n}\n\nfunction guardIntegrationRoutes({\n integration,\n ready,\n ensureReady,\n routes,\n}: IntegrationRegistration & { routes: ApiRoute[] }): ApiRoute[] {\n if (ready) return routes;\n return routes.map(route => {\n if ('handler' in route) {\n const handler = route.handler;\n return {\n ...route,\n handler: async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context, async () => {});\n },\n };\n }\n\n const createHandler = route.createHandler;\n return {\n ...route,\n createHandler: async (args: Parameters<typeof createHandler>[0]) => {\n const handler = await createHandler(args);\n return async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context);\n };\n },\n };\n });\n}\n\n/**\n * Resolve the source-control session the work item already holds for this role,\n * when it still resolves to the item's org and factory project. Returns\n * `undefined` when there is no ref or the ref no longer resolves, so the caller\n * falls back to minting one.\n */\nasync function reuseBoundSession(\n sourceControl: SourceControlStorageHandle,\n input: FactoryBindingPreparationInput,\n role: string = input.role,\n): Promise<EnsuredFactorySourceSession | undefined> {\n const ref = input.item.sessions[role];\n if (!ref) return undefined;\n // At least as strict as the coordinator's resolveSourceSession: a ref it\n // would reject must fall through to minting, not hard-fail the run.\n const session = await sourceControl.sessions.getBySessionId(ref.sessionId);\n if (\n !session?.projectRepositoryId ||\n session.orgId !== input.record.orgId ||\n session.factoryProjectId !== input.record.factoryProjectId\n ) {\n return undefined;\n }\n return {\n sessionId: session.sessionId,\n userId: session.userId,\n projectRepositoryId: session.projectRepositoryId,\n branch: session.branch,\n baseBranch: session.baseBranch,\n };\n}\n\n/** Roles whose sessions a later role may continue in, newest stage first. */\nconst INHERITABLE_ROLE_FALLBACK = ['plan', 'triage'];\n\n/**\n * A role running for the first time continues in the card's latest earlier\n * session on the same branch, so a build approved by someone other than the\n * plan's owner keeps the plan's context instead of starting empty. Review is\n * independent by design and never inherits.\n */\nasync function inheritEarlierSession(\n sourceControl: SourceControlStorageHandle,\n input: FactoryBindingPreparationInput,\n board: { roleForPhase(phase: string): string | undefined } | undefined,\n branch: string,\n projectRepositoryId: string,\n): Promise<EnsuredFactorySourceSession | undefined> {\n if (input.role === 'review') return undefined;\n // The card's linked repository can change after an earlier role ran; only\n // continue in a session on the repository a fresh session would use.\n const candidates: string[] = [];\n for (const entry of [...(input.item.stageHistory ?? [])].reverse()) {\n const role = board?.roleForPhase(entry.stage);\n if (role && role !== input.role && role !== 'review' && !candidates.includes(role)) candidates.push(role);\n }\n for (const role of INHERITABLE_ROLE_FALLBACK) {\n if (role !== input.role && !candidates.includes(role)) candidates.push(role);\n }\n for (const role of candidates) {\n const session = await reuseBoundSession(sourceControl, input, role);\n if (session && session.branch === branch && session.projectRepositoryId === projectRepositoryId) {\n return session;\n }\n }\n return undefined;\n}\n\n/**\n * Start a factory run for a rule binding: ensure the source-control session the\n * coordinator requires, then hand it to `prepare` along with the factory's\n * default model. Exported for tests — this is the autonomous entry point with no\n * browser and no interactive user, so nothing else would catch a regression in\n * what it forwards.\n */\nexport async function prepareFactoryRuleBinding(\n sourceControlProvider: SourceControlStorageHandle | Pick<GithubIntegration, 'sourceControlStorage'>,\n coordinator: Pick<FactoryStartCoordinator, 'prepare'>,\n projects: FactoryProjectsStorage,\n boards: BoardRegistry,\n input: FactoryBindingPreparationInput,\n intake?: Pick<IntakeStorage, 'getConfig'>,\n): Promise<void> {\n try {\n const sourceControl =\n 'sourceControlStorage' in sourceControlProvider\n ? sourceControlProvider.sourceControlStorage\n : sourceControlProvider;\n const source = workItemBranchSource(input.item.externalSource);\n const branch = workItemBranch({ id: input.item.id, source, metadata: input.item.metadata });\n // Only leaving a resting phase derives a lane from the role: roles don't own lanes,\n // and the Done close-out running in the triage seat must not drag the card back.\n // Both the current phase and the role's lane come from the installed board; an\n // unknown board or phase yields no destination rather than a guessed one.\n const board = boards.get(boardForWorkItem(input.item));\n const currentStage = input.item.stages.length === 1 ? input.item.stages[0] : undefined;\n const currentKind = currentStage === undefined ? undefined : board?.phaseKind(currentStage);\n const destinationStage =\n currentKind === undefined\n ? undefined\n : currentKind === 'resting'\n ? board?.phaseForRole(input.role)\n : currentStage;\n if (!destinationStage) {\n throw new FactoryDispatchError(\n 'unsupported_provider_item',\n `Factory skill invocation has no destination lane (role \"${input.role}\", stages [${input.item.stages.join(', ')}]).`,\n );\n }\n const connections = await sourceControl.connections.list({\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n });\n if (!connections.some(connection => connection.integrationId === sourceControl.integrationId)) {\n throw new FactoryDispatchError('source_control_missing', 'Factory source-control connection not found.');\n }\n const project = await projects.getById({ id: input.record.factoryProjectId });\n if (!project || project.orgId !== input.record.orgId) {\n throw new FactoryDispatchError(\n 'session_unavailable',\n `Factory model policy is unavailable for project ${input.record.factoryProjectId}.`,\n );\n }\n // A retry may already own a role session. Check it before treating a\n // repository-less card as ambiguous, but never override an explicit signal.\n const boundSession = await reuseBoundSession(sourceControl, input);\n const intakeConfig = await intake?.getConfig({ orgId: input.record.orgId, integrationIds: ['linear'] });\n let repository = await resolveWorkItemRepository({\n sourceControl,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n item: input.item,\n linearRepositoryMap: intakeConfig?.linear?.repositoryByLinearProject,\n });\n if (repository.status === 'ambiguous' && boundSession) {\n const link = await sourceControl.projectRepositories.get({\n orgId: input.record.orgId,\n id: boundSession.projectRepositoryId,\n });\n const linked =\n link && (await sourceControl.repositories.get({ orgId: input.record.orgId, id: link.repositoryId }));\n if (linked && repository.candidates.includes(linked.slug)) {\n repository = { status: 'resolved', slug: linked.slug, projectRepositoryId: link.id };\n }\n }\n if (repository.status !== 'resolved') {\n const detail =\n repository.status === 'ambiguous'\n ? `Choose one of the linked repositories: ${repository.candidates.join(', ')}.`\n : repository.hint;\n throw new FactoryDispatchError('source_repository_ambiguous', detail);\n }\n // Re-preparing a binding (server restart, retired controller session) must\n // land in the role's existing session: minting a replacement would repoint\n // the work item, flip the session's owner to the approver, and orphan the\n // previous sandbox.\n const approver = input.record.approvedBy ?? undefined;\n if (boundSession && boundSession.projectRepositoryId !== repository.projectRepositoryId) {\n throw new FactoryDispatchError(\n 'source_repository_ambiguous',\n `The existing session is bound to a different repository than ${repository.slug}. Choose the correct repository and retry.`,\n );\n }\n // The session is filed under the card's repository: the sandbox holds\n // every environment repository, but the coordinator, the PR tools and the\n // subscriptions follow the session's link, and the card names which one.\n const preparedSession =\n boundSession ??\n (await inheritEarlierSession(sourceControl, input, board, branch, repository.projectRepositoryId)) ??\n (await ensureFactorySourceSession({\n sourceControl,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n projectRepositoryId: repository.projectRepositoryId,\n branch,\n // A person who approved the run is its interactive user: attribute it to\n // them, not the repo connector. An agent's pre-approval names no person.\n attributeToUserId: isAgentActor(approver) ? undefined : approver,\n }).catch(error => {\n if (error instanceof FactorySourceSessionResolutionError && error.reason === 'repository') {\n // The card resolved to a linked repository the sandbox will not check out.\n throw new FactoryDispatchError(\n 'source_repository_missing',\n `Repository ${repository.slug} is linked but not in this Factory's environment. Turn it on under Settings → Environment, then retry.`,\n { cause: error },\n );\n }\n if (!(error instanceof SessionOwnershipMismatchError)) throw error;\n throw new FactoryDispatchError(\n 'session_unavailable',\n `Session ${error.sessionId} on branch ${branch} predates factory ownership and cannot be reused. Delete that session, then retry.`,\n { cause: error },\n );\n }));\n\n const modelScope = effectiveModelScope(project.modelScope, 'run');\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n modelScope,\n defaultModelId: modelScope === 'factory' ? (project.defaultModelId ?? undefined) : undefined,\n threadTitle: workItemThreadTitle({ source, title: input.item.title, metadata: input.item.metadata }),\n kickoffKey: input.record.id,\n destinationStage,\n workItem: {\n id: input.item.id,\n role: input.role,\n input: {\n externalSource: input.item.externalSource,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n stages: ['intake'],\n sessions: input.item.sessions,\n metadata: input.item.metadata,\n },\n },\n });\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n if (error instanceof FactorySourceSessionResolutionError) {\n const code = error.reason === 'connection' ? 'source_control_missing' : 'source_repository_missing';\n throw new FactoryDispatchError(code, error.message, { cause: error });\n }\n if (error instanceof SourceControlConnectionNotFoundError) {\n throw new FactoryDispatchError('source_control_missing', error.message, { cause: error });\n }\n if (error instanceof MaterializeError) {\n throw new FactoryDispatchError(MATERIALIZE_FAILURE_CODE[error.code], error.message, { cause: error });\n }\n throw error;\n }\n}\n\n/**\n * Build the {@link IntegrationContext} handed to an integration when the\n * factory collects its capabilities (routes, workers). One shape everywhere:\n * `assembleFactoryApiRoutes` uses it per registration, and `MastraFactory` uses it\n * when collecting integration workers at finalize.\n */\nexport function buildIntegrationContext(\n deps: Pick<\n FactoryApiRoutesDeps,\n | 'controller'\n | 'publicOrigin'\n | 'auth'\n | 'sandbox'\n | 'users'\n | 'factoryStorage'\n | 'integrationStorage'\n | 'sourceControlStorage'\n | 'integrations'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n onRepositoryPush?: IntegrationHooks['onRepositoryPush'];\n configVersion: string;\n boardRegistry: BoardRegistry;\n factoryReady: boolean;\n /** Work-item feed service, so a channel integration can ingest platform messages. */\n feed: CommentsDomain;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings' | 'modelDefaults'\n >;\n /**\n * Stable id of the registered source-control-owning integration (today:\n * `'github'` when registered). Every call site must derive and pass it so\n * `routes()`, `channels()`, and `workers()` all see the same context shape.\n */\n sourceControlOwnerId?: string;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n sandbox: deps.sandbox,\n ...(deps.users ? { users: deps.users } : {}),\n factoryStorage: deps.factoryStorage,\n baseUrl: deps.publicOrigin,\n controller: deps.controller,\n stateSigner: deps.stateSigner,\n storage: {\n generic: deps.integrationStorage.forIntegration(integrationId),\n sourceControl: deps.sourceControlStorage.forIntegration(integrationId),\n ...(deps.sourceControlOwnerId\n ? { sourceControlOwner: deps.sourceControlStorage.forIntegration(deps.sourceControlOwnerId) }\n : {}),\n sourceControls: (deps.integrations ?? [])\n .filter(({ integration }) => integration.versionControl)\n .map(({ integration }) => deps.sourceControlStorage.forIntegration(integration.id)),\n projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n modelDefaults: deps.domains.modelDefaults,\n },\n ...(deps.factoryReady ? { workItems: deps.domains.workItems, feed: deps.feed } : {}),\n ...(deps.factoryReady\n ? {\n runtime: { configVersion: deps.configVersion, workItems: deps.domains.workItems, boards: deps.boardRegistry },\n }\n : {}),\n ...(deps.emitAudit || deps.onRepositoryPush\n ? {\n hooks: {\n ...(deps.emitAudit ? { emitAudit: deps.emitAudit } : {}),\n ...(deps.onRepositoryPush ? { onRepositoryPush: deps.onRepositoryPush } : {}),\n },\n }\n : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status`, `/web/linear/status`, and `/web/jira/status`\n * unconditionally, so when an integration is absent (or not ready) the status\n * contract must still hold. Unknown custom ids get no stub — the SPA doesn't\n * poll them.\n */\nfunction disabledIntegrationStatusRoutes(deps: FactoryApiRoutesDeps, id: string, configured = false): ApiRoute[] {\n if (id === 'github') {\n return [\n registerApiRoute('/web/github/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n installations: [],\n reason: 'missing_config',\n diagnostics: getGithubFeatureDiagnostics({\n github: undefined,\n auth: deps.auth,\n appDbConfigured: deps.factoryStorage !== undefined,\n stateSigner: deps.stateSigner,\n sandbox: deps.sandbox,\n }),\n }),\n }),\n ];\n }\n if (id === 'linear') {\n return [\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: {\n linearAppConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: true,\n },\n }),\n }),\n ];\n }\n if (id === 'jira') {\n return [\n registerApiRoute('/web/jira/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n configured,\n site: null,\n reason: 'missing_config',\n diagnostics: {\n jiraConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: deps.factoryStorage !== undefined,\n },\n }),\n }),\n ];\n }\n return [];\n}\n\n/**\n * Stub for `GET /web/channel-accounts` when NO Slack integration is\n * registered. The SPA's Connections section polls the path unconditionally;\n * without a stub the SPA fallback serves HTML, which the UI can only read as\n * \"old server / unknown\". The machine-readable reason lets it say the truth:\n * the integration isn't registered.\n *\n * Mounted only for ABSENT slack — a registered integration owns the path via\n * its connect routes (or, when the state signer is unstable, gets no routes\n * at all and the UI falls back to the generic copy). Static payload, leaks\n * nothing → no auth needed, same posture as the github/linear stubs.\n */\nfunction absentSlackChannelAccountsRoutes(): ApiRoute[] {\n return [\n registerApiRoute('/web/channel-accounts', {\n method: 'GET',\n requiresAuth: false,\n handler: c => c.json({ accounts: [], canConnect: false, reason: 'not_registered' }),\n }),\n ];\n}\n\n/**\n * Assemble the custom `/web/*` API routes as Mastra `server.apiRoutes`:\n * - fs browser routes (project picker), confined to `fsRoot`\n * - config routes (provider/API-key/default-model/OM management)\n * - every registered integration's `routes()` surface (full set when ready,\n * disabled-status stub otherwise), plus stubs for absent known ids\n */\nexport function assembleFactoryApiRoutes(deps: FactoryApiRoutesDeps): ApiRoute[] {\n const emitAudit: AuditEmitter['emit'] = args => deps.audit.emit(args);\n const registrations = deps.integrations ?? [];\n const githubRegistration = registrations.find(({ integration }) => integration.id === 'github');\n const githubStorage = githubRegistration ? deps.sourceControlStorage.forIntegration('github') : undefined;\n const githubIntegration = githubRegistration?.integration as GithubIntegration | undefined;\n const sourceControlRegistrations = registrations.filter(({ integration }) => integration.versionControl);\n const sourceControlIntegrationIds = [\n ...new Set(['github', ...sourceControlRegistrations.map(({ integration }) => integration.id)]),\n ];\n const sourceControls = sourceControlIntegrationIds.map(id => deps.sourceControlStorage.forIntegration(id));\n const sourceControlSessions = createSourceControlSessionLookup(sourceControls);\n\n const integrationRoutes = registrations.flatMap(registration => {\n const { integration } = registration;\n if (!deps.stateSigner) return disabledIntegrationStatusRoutes(deps, integration.id, true);\n const context = buildIntegrationContext(\n {\n ...deps,\n stateSigner: deps.stateSigner,\n emitAudit,\n ...(githubRegistration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n return guardIntegrationRoutes({ ...registration, routes: integration.routes(context) });\n });\n // Session persistence belongs to the linked source-control provider, not to\n // GitHub. Replace legacy GitHub-owned handlers with one resolver spanning\n // the legacy GitHub partition and every registered source-control partition.\n const sharedSessionRouteKeys = new Set([\n 'GET /web/github/projects/:id/sessions',\n 'POST /web/github/projects/:id/sessions',\n 'GET /web/user-sessions/:sessionId',\n 'DELETE /web/user-sessions/:sessionId',\n 'POST /web/user-sessions/:sessionId/title',\n ]);\n const providerRoutes = integrationRoutes.filter(\n route => !sharedSessionRouteKeys.has(route.method + ' ' + route.path),\n );\n const sourceControlSessionRoutes =\n sourceControls.length === 0\n ? []\n : buildSourceControlSessionRoutes({\n auth: deps.auth,\n sourceControls,\n projects: deps.domains.projects,\n ...(deps.users ? { users: deps.users } : {}),\n controller: deps.controller,\n memorySettings: deps.domains.memorySettings,\n sessionRetirement: deps.sessionRetirement,\n // A local sandbox's destroy keeps the session directory, so recreate\n // couldn't deliver the fresh checkout it promises there.\n sandboxRecreateSupported: deps.sandbox !== undefined && deps.sandbox.provider !== 'local',\n filesystem: deps.domains.filesystem,\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n ...(deps.moveThread ? { moveThread: deps.moveThread } : {}),\n ...(githubStorage ? { githubRepositories: githubStorage.repositories } : {}),\n });\n // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear', 'jira']\n .filter(id => !registrations.some(({ integration }) => integration.id === id))\n .flatMap(id => disabledIntegrationStatusRoutes(deps, id));\n // Absent slack gets the channel-accounts not-registered stub (registered\n // slack owns the path via its own connect routes).\n const slackAbsentStubs = registrations.some(({ integration }) => integration.id === 'slack')\n ? []\n : absentSlackChannelAccountsRoutes();\n\n const transitionService = deps.factoryReady\n ? (deps.factoryTransitionService ??\n new FactoryTransitionService({\n configVersion: deps.configVersion,\n boards: deps.boardRegistry,\n storage: deps.domains.workItems,\n audit: deps.audit,\n }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n request =>\n resolveFactorySourceControl({\n sourceControls,\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n sessionId: request.sessionId,\n }),\n deps.domains.intake,\n { projects: deps.domains.projects, modelDefaults: deps.domains.modelDefaults },\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n startCoordinator,\n ...(sourceControlRegistrations.length > 0\n ? {\n prepareBinding: async (input: FactoryBindingPreparationInput) => {\n const sourceControl = await resolveFactorySourceControl({\n sourceControls,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n });\n if (!sourceControl) {\n throw new FactoryDispatchError(\n 'source_control_missing',\n 'Factory project has no linked source-control repository.',\n );\n }\n return prepareFactoryRuleBinding(\n sourceControl,\n startCoordinator,\n deps.domains.projects,\n deps.boardRegistry,\n input,\n deps.domains.intake,\n );\n },\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n sessions: sourceControlSessions,\n },\n }),\n ...new ConfigRoutes({\n auth: deps.auth,\n controller: deps.controller,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n modelDefaults: deps.domains.modelDefaults,\n sourceControlSessions,\n sourceControls,\n workItems: deps.domains.workItems,\n memorySettings: deps.domains.memorySettings,\n factoryProjects: deps.domains.projects,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\n deploymentProviders: deps.deploymentModelProviders,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n onCustomProvidersChanged: invalidateCustomProvidersSnapshots,\n }).routes(),\n ...new OAuthRoutes({\n auth: deps.auth,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n }).routes(),\n ...new SkillRoutes({\n auth: deps.auth,\n controllerId: deps.controllerId,\n controller: deps.controller,\n sourceControlStorage: githubStorage,\n ensureSourceControlReady: githubRegistration?.ensureReady,\n }).routes(),\n ...sourceControlSessionRoutes,\n ...(sourceControls.length === 0 ? [] : buildSourceControlSettingsRoutes({ auth: deps.auth, sourceControls })),\n ...providerRoutes,\n ...absentStubs,\n ...slackAbsentStubs,\n ...(deps.intakeReady\n ? new IntakeRoutes({\n auth: deps.auth,\n audit: deps.audit,\n intake: deps.domains.intake,\n projects: deps.domains.projects,\n boardRegistry: deps.boardRegistry,\n // Relocation reads and writes work items, which init fail-soft separately from intake.\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n integrations: (deps.integrations ?? []).flatMap(({ integration }) =>\n integration.intake ? [{ id: integration.id, intake: integration.intake }] : [],\n ),\n }).routes()\n : []),\n ...(deps.factoryReady && deps.knowledgeEnabled\n ? new KnowledgeRoutes({\n auth: deps.auth,\n projects: deps.domains.projects,\n knowledge: async key => deps.controller.getMastra()?.getKnowledge(key),\n defaultKnowledgeKey: deps.knowledgeKey,\n threadTitle: async threadId => {\n const memory = await deps.controller.getMastra()?.getStorage()?.getStore('memory');\n return (await memory?.getThreadById({ threadId }))?.title;\n },\n }).routes()\n : []),\n ...(deps.factoryReady\n ? new WorkItemRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n boardRegistry: deps.boardRegistry,\n comments: deps.domains.comments,\n queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: deps.liveSessions,\n }).routes()\n : []),\n ...(deps.factoryReady\n ? buildAutomationRunRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n configVersion: deps.configVersion,\n })\n : []),\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,MAAM,2BAA2B;CAC/B,eAAe;CACf,kBAAkB;CAClB,gBAAgB;CAChB,eAAe;CACf,eAAe;CACf,iBAAiB;CACjB,cAAc;CACd,aAAa;AACf;AAmEA,SAAS,uBAAuB,EAC9B,aACA,OACA,aACA,UAC+D;CAC/D,IAAI,OAAO,OAAO;CAClB,OAAO,OAAO,KAAI,UAAS;EACzB,IAAI,aAAa,OAAO;GACtB,MAAM,UAAU,MAAM;GACtB,OAAO;IACL,GAAG;IACH,SAAS,OAAO,YAA2C;KACzD,IAAI;MACF,MAAM,YAAY;KACpB,QAAQ;MACN,OAAO,QAAQ,KACb;OAAE,OAAO;OAA2B,SAAS,GAAG,YAAY,GAAG;MAA8B,GAC7F,GACF;KACF;KACA,OAAO,QAAQ,SAAS,YAAY,CAAC,CAAC;IACxC;GACF;EACF;EAEA,MAAM,gBAAgB,MAAM;EAC5B,OAAO;GACL,GAAG;GACH,eAAe,OAAO,SAA8C;IAClE,MAAM,UAAU,MAAM,cAAc,IAAI;IACxC,OAAO,OAAO,YAA2C;KACvD,IAAI;MACF,MAAM,YAAY;KACpB,QAAQ;MACN,OAAO,QAAQ,KACb;OAAE,OAAO;OAA2B,SAAS,GAAG,YAAY,GAAG;MAA8B,GAC7F,GACF;KACF;KACA,OAAO,QAAQ,OAAO;IACxB;GACF;EACF;CACF,CAAC;AACH;;;;;;;AAQA,eAAe,kBACb,eACA,OACA,OAAe,MAAM,MAC6B;CAClD,MAAM,MAAM,MAAM,KAAK,SAAS;CAChC,IAAI,CAAC,KAAK,OAAO,KAAA;CAGjB,MAAM,UAAU,MAAM,cAAc,SAAS,eAAe,IAAI,SAAS;CACzE,IACE,CAAC,SAAS,uBACV,QAAQ,UAAU,MAAM,OAAO,SAC/B,QAAQ,qBAAqB,MAAM,OAAO,kBAE1C;CAEF,OAAO;EACL,WAAW,QAAQ;EACnB,QAAQ,QAAQ;EAChB,qBAAqB,QAAQ;EAC7B,QAAQ,QAAQ;EAChB,YAAY,QAAQ;CACtB;AACF;;AAGA,MAAM,4BAA4B,CAAC,QAAQ,QAAQ;;;;;;;AAQnD,eAAe,sBACb,eACA,OACA,OACA,QACA,qBACkD;CAClD,IAAI,MAAM,SAAS,UAAU,OAAO,KAAA;CAGpC,MAAM,aAAuB,CAAC;CAC9B,KAAK,MAAM,SAAS,CAAC,GAAI,MAAM,KAAK,gBAAgB,CAAC,CAAE,CAAC,CAAC,QAAQ,GAAG;EAClE,MAAM,OAAO,OAAO,aAAa,MAAM,KAAK;EAC5C,IAAI,QAAQ,SAAS,MAAM,QAAQ,SAAS,YAAY,CAAC,WAAW,SAAS,IAAI,GAAG,WAAW,KAAK,IAAI;CAC1G;CACA,KAAK,MAAM,QAAQ,2BACjB,IAAI,SAAS,MAAM,QAAQ,CAAC,WAAW,SAAS,IAAI,GAAG,WAAW,KAAK,IAAI;CAE7E,KAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,UAAU,MAAM,kBAAkB,eAAe,OAAO,IAAI;EAClE,IAAI,WAAW,QAAQ,WAAW,UAAU,QAAQ,wBAAwB,qBAC1E,OAAO;CAEX;AAEF;;;;;;;;AASA,eAAsB,0BACpB,uBACA,aACA,UACA,QACA,OACA,QACe;CACf,IAAI;EACF,MAAM,gBACJ,0BAA0B,wBACtB,sBAAsB,uBACtB;EACN,MAAM,SAAS,qBAAqB,MAAM,KAAK,cAAc;EAC7D,MAAM,SAAS,eAAe;GAAE,IAAI,MAAM,KAAK;GAAI;GAAQ,UAAU,MAAM,KAAK;EAAS,CAAC;EAK1F,MAAM,QAAQ,OAAO,IAAI,iBAAiB,MAAM,IAAI,CAAC;EACrD,MAAM,eAAe,MAAM,KAAK,OAAO,WAAW,IAAI,MAAM,KAAK,OAAO,KAAK,KAAA;EAC7E,MAAM,cAAc,iBAAiB,KAAA,IAAY,KAAA,IAAY,OAAO,UAAU,YAAY;EAC1F,MAAM,mBACJ,gBAAgB,KAAA,IACZ,KAAA,IACA,gBAAgB,YACd,OAAO,aAAa,MAAM,IAAI,IAC9B;EACR,IAAI,CAAC,kBACH,MAAM,IAAI,qBACR,6BACA,2DAA2D,MAAM,KAAK,aAAa,MAAM,KAAK,OAAO,KAAK,IAAI,EAAE,IAClH;EAMF,IAAI,EAAC,MAJqB,cAAc,YAAY,KAAK;GACvD,OAAO,MAAM,OAAO;GACpB,kBAAkB,MAAM,OAAO;EACjC,CAAC,EAAA,CACgB,MAAK,eAAc,WAAW,kBAAkB,cAAc,aAAa,GAC1F,MAAM,IAAI,qBAAqB,0BAA0B,8CAA8C;EAEzG,MAAM,UAAU,MAAM,SAAS,QAAQ,EAAE,IAAI,MAAM,OAAO,iBAAiB,CAAC;EAC5E,IAAI,CAAC,WAAW,QAAQ,UAAU,MAAM,OAAO,OAC7C,MAAM,IAAI,qBACR,uBACA,mDAAmD,MAAM,OAAO,iBAAiB,EACnF;EAIF,MAAM,eAAe,MAAM,kBAAkB,eAAe,KAAK;EACjE,MAAM,eAAe,MAAM,QAAQ,UAAU;GAAE,OAAO,MAAM,OAAO;GAAO,gBAAgB,CAAC,QAAQ;EAAE,CAAC;EACtG,IAAI,aAAa,MAAM,0BAA0B;GAC/C;GACA,OAAO,MAAM,OAAO;GACpB,kBAAkB,MAAM,OAAO;GAC/B,MAAM,MAAM;GACZ,qBAAqB,cAAc,QAAQ;EAC7C,CAAC;EACD,IAAI,WAAW,WAAW,eAAe,cAAc;GACrD,MAAM,OAAO,MAAM,cAAc,oBAAoB,IAAI;IACvD,OAAO,MAAM,OAAO;IACpB,IAAI,aAAa;GACnB,CAAC;GACD,MAAM,SACJ,QAAS,MAAM,cAAc,aAAa,IAAI;IAAE,OAAO,MAAM,OAAO;IAAO,IAAI,KAAK;GAAa,CAAC;GACpG,IAAI,UAAU,WAAW,WAAW,SAAS,OAAO,IAAI,GACtD,aAAa;IAAE,QAAQ;IAAY,MAAM,OAAO;IAAM,qBAAqB,KAAK;GAAG;EAEvF;EACA,IAAI,WAAW,WAAW,YAAY;GACpC,MAAM,SACJ,WAAW,WAAW,cAClB,0CAA0C,WAAW,WAAW,KAAK,IAAI,EAAE,KAC3E,WAAW;GACjB,MAAM,IAAI,qBAAqB,+BAA+B,MAAM;EACtE;EAKA,MAAM,WAAW,MAAM,OAAO,cAAc,KAAA;EAC5C,IAAI,gBAAgB,aAAa,wBAAwB,WAAW,qBAClE,MAAM,IAAI,qBACR,+BACA,gEAAgE,WAAW,KAAK,2CAClF;EAKF,MAAM,kBACJ,gBACC,MAAM,sBAAsB,eAAe,OAAO,OAAO,QAAQ,WAAW,mBAAmB,KAC/F,MAAM,2BAA2B;GAChC;GACA,OAAO,MAAM,OAAO;GACpB,kBAAkB,MAAM,OAAO;GAC/B,qBAAqB,WAAW;GAChC;GAGA,mBAAmB,aAAa,QAAQ,IAAI,KAAA,IAAY;EAC1D,CAAC,CAAC,CAAC,OAAM,UAAS;GAChB,IAAI,iBAAiB,uCAAuC,MAAM,WAAW,cAE3E,MAAM,IAAI,qBACR,6BACA,cAAc,WAAW,KAAK,yGAC9B,EAAE,OAAO,MAAM,CACjB;GAEF,IAAI,EAAE,iBAAiB,gCAAgC,MAAM;GAC7D,MAAM,IAAI,qBACR,uBACA,WAAW,MAAM,UAAU,aAAa,OAAO,qFAC/C,EAAE,OAAO,MAAM,CACjB;EACF,CAAC;EAEH,MAAM,aAAa,oBAAoB,QAAQ,YAAY,KAAK;EAChE,MAAM,YAAY,QAAQ;GACxB,OAAO,MAAM,OAAO;GACpB,QAAQ,gBAAgB;GACxB,kBAAkB,MAAM,OAAO;GAC/B,WAAW,gBAAgB;GAC3B;GACA,gBAAgB,eAAe,YAAa,QAAQ,kBAAkB,KAAA,IAAa,KAAA;GACnF,aAAa,oBAAoB;IAAE;IAAQ,OAAO,MAAM,KAAK;IAAO,UAAU,MAAM,KAAK;GAAS,CAAC;GACnG,YAAY,MAAM,OAAO;GACzB;GACA,UAAU;IACR,IAAI,MAAM,KAAK;IACf,MAAM,MAAM;IACZ,OAAO;KACL,gBAAgB,MAAM,KAAK;KAC3B,kBAAkB,MAAM,KAAK;KAC7B,OAAO,MAAM,KAAK;KAClB,QAAQ,CAAC,QAAQ;KACjB,UAAU,MAAM,KAAK;KACrB,UAAU,MAAM,KAAK;IACvB;GACF;EACF,CAAC;CACH,SAAS,OAAO;EACd,IAAI,iBAAiB,sBAAsB,MAAM;EACjD,IAAI,iBAAiB,qCAAqC;GACxD,MAAM,OAAO,MAAM,WAAW,eAAe,2BAA2B;GACxE,MAAM,IAAI,qBAAqB,MAAM,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;EACtE;EACA,IAAI,iBAAiB,sCACnB,MAAM,IAAI,qBAAqB,0BAA0B,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;EAE1F,IAAI,iBAAiB,kBACnB,MAAM,IAAI,qBAAqB,yBAAyB,MAAM,OAAO,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;EAEtG,MAAM;CACR;AACF;;;;;;;AAQA,SAAgB,wBACd,MA+BA,eACoB;CACpB,OAAO;EACL,MAAM,KAAK;EACX,SAAS,KAAK;EACd,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;EAC1C,gBAAgB,KAAK;EACrB,SAAS,KAAK;EACd,YAAY,KAAK;EACjB,aAAa,KAAK;EAClB,SAAS;GACP,SAAS,KAAK,mBAAmB,eAAe,aAAa;GAC7D,eAAe,KAAK,qBAAqB,eAAe,aAAa;GACrE,GAAI,KAAK,uBACL,EAAE,oBAAoB,KAAK,qBAAqB,eAAe,KAAK,oBAAoB,EAAE,IAC1F,CAAC;GACL,iBAAiB,KAAK,gBAAgB,CAAC,EAAA,CACpC,QAAQ,EAAE,kBAAkB,YAAY,cAAc,CAAC,CACvD,KAAK,EAAE,kBAAkB,KAAK,qBAAqB,eAAe,YAAY,EAAE,CAAC;GACpF,UAAU,KAAK,QAAQ;GACvB,QAAQ,KAAK,QAAQ;GACrB,iBAAiB,KAAK,QAAQ;GAC9B,gBAAgB,KAAK,QAAQ;GAC7B,eAAe,KAAK,QAAQ;EAC9B;EACA,GAAI,KAAK,eAAe;GAAE,WAAW,KAAK,QAAQ;GAAW,MAAM,KAAK;EAAK,IAAI,CAAC;EAClF,GAAI,KAAK,eACL,EACE,SAAS;GAAE,eAAe,KAAK;GAAe,WAAW,KAAK,QAAQ;GAAW,QAAQ,KAAK;EAAc,EAC9G,IACA,CAAC;EACL,GAAI,KAAK,aAAa,KAAK,mBACvB,EACE,OAAO;GACL,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;GACtD,GAAI,KAAK,mBAAmB,EAAE,kBAAkB,KAAK,iBAAiB,IAAI,CAAC;EAC7E,EACF,IACA,CAAC;CACP;AACF;;;;;;;;AASA,SAAS,gCAAgC,MAA4B,IAAY,aAAa,OAAmB;CAC/G,IAAI,OAAO,UACT,OAAO,CACL,iBAAiB,sBAAsB;EACrC,QAAQ;EACR,cAAc;EACd,UAAS,MACP,EAAE,KAAK;GACL,SAAS;GACT,WAAW;GACX,eAAe,CAAC;GAChB,QAAQ;GACR,aAAa,4BAA4B;IACvC,QAAQ,KAAA;IACR,MAAM,KAAK;IACX,iBAAiB,KAAK,mBAAmB,KAAA;IACzC,aAAa,KAAK;IAClB,SAAS,KAAK;GAChB,CAAC;EACH,CAAC;CACL,CAAC,CACH;CAEF,IAAI,OAAO,UACT,OAAO,CACL,iBAAiB,sBAAsB;EACrC,QAAQ;EACR,cAAc;EACd,UAAS,MACP,EAAE,KAAK;GACL,SAAS;GACT,WAAW;GACX,WAAW;GACX,QAAQ;GACR,aAAa;IACX,qBAAqB;IACrB,oBAAoB,KAAK,KAAK,QAAQ;IACtC,iBAAiB;GACnB;EACF,CAAC;CACL,CAAC,CACH;CAEF,IAAI,OAAO,QACT,OAAO,CACL,iBAAiB,oBAAoB;EACnC,QAAQ;EACR,cAAc;EACd,UAAS,MACP,EAAE,KAAK;GACL,SAAS;GACT;GACA,MAAM;GACN,QAAQ;GACR,aAAa;IACX,gBAAgB;IAChB,oBAAoB,KAAK,KAAK,QAAQ;IACtC,iBAAiB,KAAK,mBAAmB,KAAA;GAC3C;EACF,CAAC;CACL,CAAC,CACH;CAEF,OAAO,CAAC;AACV;;;;;;;;;;;;;AAcA,SAAS,mCAA+C;CACtD,OAAO,CACL,iBAAiB,yBAAyB;EACxC,QAAQ;EACR,cAAc;EACd,UAAS,MAAK,EAAE,KAAK;GAAE,UAAU,CAAC;GAAG,YAAY;GAAO,QAAQ;EAAiB,CAAC;CACpF,CAAC,CACH;AACF;;;;;;;;AASA,SAAgB,yBAAyB,MAAwC;CAC/E,MAAM,aAAkC,SAAQ,KAAK,MAAM,KAAK,IAAI;CACpE,MAAM,gBAAgB,KAAK,gBAAgB,CAAC;CAC5C,MAAM,qBAAqB,cAAc,MAAM,EAAE,kBAAkB,YAAY,OAAO,QAAQ;CAC9F,MAAM,gBAAgB,qBAAqB,KAAK,qBAAqB,eAAe,QAAQ,IAAI,KAAA;CACtE,oBAAoB;CAC9C,MAAM,6BAA6B,cAAc,QAAQ,EAAE,kBAAkB,YAAY,cAAc;CAIvG,MAAM,iBAAiB,CAFrB,mBAAG,IAAI,IAAI,CAAC,UAAU,GAAG,2BAA2B,KAAK,EAAE,kBAAkB,YAAY,EAAE,CAAC,CAAC,CAE9C,CAAC,CAAC,KAAI,OAAM,KAAK,qBAAqB,eAAe,EAAE,CAAC;CACzG,MAAM,wBAAwB,iCAAiC,cAAc;CAE7E,MAAM,oBAAoB,cAAc,SAAQ,iBAAgB;EAC9D,MAAM,EAAE,gBAAgB;EACxB,IAAI,CAAC,KAAK,aAAa,OAAO,gCAAgC,MAAM,YAAY,IAAI,IAAI;EACxF,MAAM,UAAU,wBACd;GACE,GAAG;GACH,aAAa,KAAK;GAClB;GACA,GAAI,qBAAqB,EAAE,sBAAsB,SAAS,IAAI,CAAC;EACjE,GACA,YAAY,EACd;EACA,OAAO,uBAAuB;GAAE,GAAG;GAAc,QAAQ,YAAY,OAAO,OAAO;EAAE,CAAC;CACxF,CAAC;CAID,MAAM,yCAAyB,IAAI,IAAI;EACrC;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,MAAM,iBAAiB,kBAAkB,QACvC,UAAS,CAAC,uBAAuB,IAAI,MAAM,SAAS,MAAM,MAAM,IAAI,CACtE;CACA,MAAM,6BACJ,eAAe,WAAW,IACtB,CAAC,IACD,gCAAgC;EAC9B,MAAM,KAAK;EACX;EACA,UAAU,KAAK,QAAQ;EACvB,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;EAC1C,YAAY,KAAK;EACjB,gBAAgB,KAAK,QAAQ;EAC7B,mBAAmB,KAAK;EAGxB,0BAA0B,KAAK,YAAY,KAAA,KAAa,KAAK,QAAQ,aAAa;EAClF,YAAY,KAAK,QAAQ;EACzB,GAAI,KAAK,eAAe,EAAE,WAAW,KAAK,QAAQ,UAAU,IAAI,CAAC;EACjE,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;EACzD,GAAI,gBAAgB,EAAE,oBAAoB,cAAc,aAAa,IAAI,CAAC;CAC5E,CAAC;CAEP,MAAM,cAAc;EAAC;EAAU;EAAU;CAAM,CAAC,CAC7C,QAAO,OAAM,CAAC,cAAc,MAAM,EAAE,kBAAkB,YAAY,OAAO,EAAE,CAAC,CAAC,CAC7E,SAAQ,OAAM,gCAAgC,MAAM,EAAE,CAAC;CAG1D,MAAM,mBAAmB,cAAc,MAAM,EAAE,kBAAkB,YAAY,OAAO,OAAO,IACvF,CAAC,IACD,iCAAiC;CAErC,MAAM,oBAAoB,KAAK,eAC1B,KAAK,4BACN,IAAI,yBAAyB;EAC3B,eAAe,KAAK;EACpB,QAAQ,KAAK;EACb,SAAS,KAAK,QAAQ;EACtB,OAAO,KAAK;CACd,CAAC,IACD,KAAA;CACJ,MAAM,mBAAmB,oBACrB,IAAI,wBACF,KAAK,YACL,KAAK,QAAQ,WACb,oBACA,YACE,4BAA4B;EAC1B;EACA,OAAO,QAAQ;EACf,kBAAkB,QAAQ;EAC1B,WAAW,QAAQ;CACrB,CAAC,GACH,KAAK,QAAQ,QACb;EAAE,UAAU,KAAK,QAAQ;EAAU,eAAe,KAAK,QAAQ;CAAc,CAC/E,IACA,KAAA;CACJ,IAAI,qBAAqB,kBACvB,KAAK,mBAAmB;EACtB;EACA;EACA,GAAI,2BAA2B,SAAS,IACpC,EACE,gBAAgB,OAAO,UAA0C;GAC/D,MAAM,gBAAgB,MAAM,4BAA4B;IACtD;IACA,OAAO,MAAM,OAAO;IACpB,kBAAkB,MAAM,OAAO;GACjC,CAAC;GACD,IAAI,CAAC,eACH,MAAM,IAAI,qBACR,0BACA,0DACF;GAEF,OAAO,0BACL,eACA,kBACA,KAAK,QAAQ,UACb,KAAK,eACL,OACA,KAAK,QAAQ,MACf;EACF,EACF,IACA,CAAC;CACP,CAAC;CAGH,OAAO;EACL,GAAG,cAAc;GACf,MAAM,KAAK;GACX,WAAW;IACT,MAAM,KAAK;IACX,UAAU;GACZ;EACF,CAAC;EACD,GAAG,IAAI,aAAa;GAClB,MAAM,KAAK;GACX,YAAY,KAAK;GACjB,aAAa,KAAK;GAClB,kBAAkB,KAAK,QAAQ;GAC/B,eAAe,KAAK,QAAQ;GAC5B;GACA;GACA,WAAW,KAAK,QAAQ;GACxB,gBAAgB,KAAK,QAAQ;GAC7B,iBAAiB,KAAK,QAAQ;GAC9B,iBAAiB,KAAK,QAAQ;GAC9B,UAAU,EAAE,WAAW,KAAK,iBAAiB;GAC7C,qBAAqB,KAAK;GAC1B,sBAAsB;GACtB,0BAA0B;EAC5B,CAAC,CAAC,CAAC,OAAO;EACV,GAAG,IAAI,YAAY;GACjB,MAAM,KAAK;GACX,aAAa,KAAK;GAClB,kBAAkB,KAAK,QAAQ;GAC/B,sBAAsB;EACxB,CAAC,CAAC,CAAC,OAAO;EACV,GAAG,IAAI,YAAY;GACjB,MAAM,KAAK;GACX,cAAc,KAAK;GACnB,YAAY,KAAK;GACjB,sBAAsB;GACtB,0BAA0B,oBAAoB;EAChD,CAAC,CAAC,CAAC,OAAO;EACV,GAAG;EACH,GAAI,eAAe,WAAW,IAAI,CAAC,IAAI,iCAAiC;GAAE,MAAM,KAAK;GAAM;EAAe,CAAC;EAC3G,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAI,KAAK,cACL,IAAI,aAAa;GACf,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,QAAQ,KAAK,QAAQ;GACrB,UAAU,KAAK,QAAQ;GACvB,eAAe,KAAK;GAEpB,GAAI,KAAK,eAAe,EAAE,WAAW,KAAK,QAAQ,UAAU,IAAI,CAAC;GACjE,eAAe,KAAK,gBAAgB,CAAC,EAAA,CAAG,SAAS,EAAE,kBACjD,YAAY,SAAS,CAAC;IAAE,IAAI,YAAY;IAAI,QAAQ,YAAY;GAAO,CAAC,IAAI,CAAC,CAC/E;EACF,CAAC,CAAC,CAAC,OAAO,IACV,CAAC;EACL,GAAI,KAAK,gBAAgB,KAAK,mBAC1B,IAAI,gBAAgB;GAClB,MAAM,KAAK;GACX,UAAU,KAAK,QAAQ;GACvB,WAAW,OAAM,QAAO,KAAK,WAAW,UAAU,CAAC,EAAE,aAAa,GAAG;GACrE,qBAAqB,KAAK;GAC1B,aAAa,OAAM,aAAY;IAE7B,QAAQ,OAAM,MADO,KAAK,WAAW,UAAU,CAAC,EAAE,WAAW,CAAC,EAAE,SAAS,QAAQ,EAAA,EAC3D,cAAc,EAAE,SAAS,CAAC,EAAA,EAAI;GACtD;EACF,CAAC,CAAC,CAAC,OAAO,IACV,CAAC;EACL,GAAI,KAAK,eACL,IAAI,eAAe;GACjB,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,UAAU,KAAK,QAAQ;GACvB,WAAW,KAAK,QAAQ;GACxB,eAAe,KAAK;GACpB,UAAU,KAAK,QAAQ;GACvB,aAAa,KAAK,QAAQ;GAC1B;GACA;GACA,cAAc,KAAK;EACrB,CAAC,CAAC,CAAC,OAAO,IACV,CAAC;EACL,GAAI,KAAK,eACL,yBAAyB;GACvB,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,UAAU,KAAK,QAAQ;GACvB,WAAW,KAAK,QAAQ;GACxB,eAAe,KAAK;EACtB,CAAC,IACD,CAAC;CACP;AACF"}
|
|
@@ -84,4 +84,22 @@ export declare function sessionMemoryResource(session: SourceControlSession, bou
|
|
|
84
84
|
* sessions merge into one resource, and their records cannot be merged.
|
|
85
85
|
*/
|
|
86
86
|
export declare function migrateSessionResources(deps: SessionResourceMigrationDeps): Promise<SessionResourceMigrationReport>;
|
|
87
|
+
export interface SessionVisibilityBackfillDeps {
|
|
88
|
+
sessions: {
|
|
89
|
+
listSessionsAwaitingVisibilityCheck(): Promise<SourceControlSession[]>;
|
|
90
|
+
markVisibilityChecked(sessionId: string, options: {
|
|
91
|
+
makePrivate: boolean;
|
|
92
|
+
}): Promise<void>;
|
|
93
|
+
};
|
|
94
|
+
workItems: Pick<SessionResourceMigrationDeps['workItems'], 'listRunBoundItems'>;
|
|
95
|
+
memory: Pick<MemoryStorage, 'getThreadById'>;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Mark shared user sessions private when their conversation lives on their owner's resource.
|
|
99
|
+
* The resource already keeps teammates out of such a session; this makes lists agree, so they
|
|
100
|
+
* stop seeing a session they can't open. Sessions shared onto their Factory, runs, Slack threads
|
|
101
|
+
* and side sessions are left alone. Each session is checked once: new sessions start checked,
|
|
102
|
+
* and a session that throws stays unchecked so the next start retries it.
|
|
103
|
+
*/
|
|
104
|
+
export declare function privatizeOwnerResourceSessions(deps: SessionVisibilityBackfillDeps): Promise<number>;
|
|
87
105
|
//# sourceMappingURL=resource-migration.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-migration.d.ts","sourceRoot":"","sources":["../../src/session/resource-migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"resource-migration.d.ts","sourceRoot":"","sources":["../../src/session/resource-migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAezE,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE;QACR,qCAAqC,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;QACzE,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACzD,CAAC;IACF,SAAS,EAAE;QACT,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,gBAAgB,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3G,yBAAyB,CAAC,KAAK,EAAE;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;KAC9F,CAAC;IACF,gGAAgG;IAChG,YAAY,CAAC,EAAE;QACb,2BAA2B,CAAC,KAAK,EAAE;YAAE,cAAc,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;KACvG,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,aAAa,GAAG,wBAAwB,GAAG,6BAA6B,CAAC,CAAC;IACtH;;;OAGG;IACH,OAAO,EAAE;QAAE,sBAAsB,CAAC,KAAK,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;KAAE,CAAC;CAClH;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gEAAgE;IAChE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;OAGG;IACH,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,wBAAwB,EAAE,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,UAAU,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,gBAAgB,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,EAAE,GACtE;IAAE,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAYvE;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,4BAA4B,GACjC,OAAO,CAAC,8BAA8B,CAAC,CAsHzC;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE;QACR,mCAAmC,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;QACvE,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE;YAAE,WAAW,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC5F,CAAC;IACF,SAAS,EAAE,IAAI,CAAC,4BAA4B,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC,CAAC;IAChF,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;CAC9C;AAED;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,6BAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBzG"}
|
|
@@ -9,6 +9,8 @@ const SLACK_SESSION_BRANCH_PREFIX = "slack/";
|
|
|
9
9
|
* session id, so the next start finds it by this flag and its session stamp and finishes the move.
|
|
10
10
|
*/
|
|
11
11
|
const MOVE_PENDING_KEY = "factoryResourceMovePending";
|
|
12
|
+
/** How often a long resource migration reports how far it got. */
|
|
13
|
+
const PROGRESS_LOG_INTERVAL_MS = 1e4;
|
|
12
14
|
/**
|
|
13
15
|
* The memory resource a session belongs to: its Factory for runs, side
|
|
14
16
|
* sessions and Slack channel threads, its owner for sessions a person opened,
|
|
@@ -66,6 +68,8 @@ async function migrateSessionResources(deps) {
|
|
|
66
68
|
resourceObservationsLeft: []
|
|
67
69
|
};
|
|
68
70
|
const sessions = await deps.sessions.listSessionsAwaitingResourceMigration();
|
|
71
|
+
if (sessions.length > 0) console.info(`[factory] session resource migration: moving ${sessions.length} sessions in the background`);
|
|
72
|
+
let lastProgressAt = Date.now();
|
|
69
73
|
for (const session of sessions) {
|
|
70
74
|
report.sessions += 1;
|
|
71
75
|
const { sessionId } = session;
|
|
@@ -139,11 +143,39 @@ async function migrateSessionResources(deps) {
|
|
|
139
143
|
} catch (error) {
|
|
140
144
|
report.failed.push(sessionId);
|
|
141
145
|
console.warn(`[factory] session resource migration: session ${sessionId} not moved: ${error instanceof Error ? error.message : error}`);
|
|
146
|
+
} finally {
|
|
147
|
+
if (report.sessions < sessions.length && Date.now() - lastProgressAt >= PROGRESS_LOG_INTERVAL_MS) {
|
|
148
|
+
lastProgressAt = Date.now();
|
|
149
|
+
console.info(`[factory] session resource migration: ${report.sessions}/${sessions.length} sessions checked, ${report.threads} threads moved`);
|
|
150
|
+
}
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
153
|
if (report.threads + report.runBindings + report.subscriptions + report.skipped.length + report.failed.length + report.subscriptionsPending.length > 0) console.info(`[factory] session resource migration: ${report.threads} threads, ${report.runBindings} run bindings, ${report.subscriptions} subscriptions moved; ${report.skipped.length} Factory sessions without a Factory skipped; ${report.failed.length} sessions failed; ${report.subscriptionsPending.length} sessions wait for integrations to move their subscriptions`);
|
|
145
154
|
return report;
|
|
146
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Mark shared user sessions private when their conversation lives on their owner's resource.
|
|
158
|
+
* The resource already keeps teammates out of such a session; this makes lists agree, so they
|
|
159
|
+
* stop seeing a session they can't open. Sessions shared onto their Factory, runs, Slack threads
|
|
160
|
+
* and side sessions are left alone. Each session is checked once: new sessions start checked,
|
|
161
|
+
* and a session that throws stays unchecked so the next start retries it.
|
|
162
|
+
*/
|
|
163
|
+
async function privatizeOwnerResourceSessions(deps) {
|
|
164
|
+
let privatized = 0;
|
|
165
|
+
for (const session of await deps.sessions.listSessionsAwaitingVisibilityCheck()) try {
|
|
166
|
+
let makePrivate = false;
|
|
167
|
+
if (session.visibility !== "private") {
|
|
168
|
+
const { owner } = sessionMemoryResource(session, await deps.workItems.listRunBoundItems(session.sessionId));
|
|
169
|
+
makePrivate = (owner === "user" ? await deps.memory.getThreadById({ threadId: session.sessionId }) : null)?.resourceId === session.userId;
|
|
170
|
+
}
|
|
171
|
+
await deps.sessions.markVisibilityChecked(session.sessionId, { makePrivate });
|
|
172
|
+
if (makePrivate) privatized += 1;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.warn(`[factory] session visibility check: session ${session.sessionId} not checked: ${error instanceof Error ? error.message : error}`);
|
|
175
|
+
}
|
|
176
|
+
if (privatized > 0) console.info(`[factory] session visibility check: ${privatized} sessions on their owner's memory made private`);
|
|
177
|
+
return privatized;
|
|
178
|
+
}
|
|
147
179
|
async function moveThread(deps, thread, resourceId) {
|
|
148
180
|
await deps.threads.updateThreadResourceId({
|
|
149
181
|
threadId: thread.id,
|
|
@@ -158,6 +190,6 @@ async function moveThread(deps, thread, resourceId) {
|
|
|
158
190
|
});
|
|
159
191
|
}
|
|
160
192
|
//#endregion
|
|
161
|
-
export { migrateSessionResources, sessionMemoryResource };
|
|
193
|
+
export { migrateSessionResources, privatizeOwnerResourceSessions, sessionMemoryResource };
|
|
162
194
|
|
|
163
195
|
//# sourceMappingURL=resource-migration.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-migration.js","names":[],"sources":["../../src/session/resource-migration.ts"],"sourcesContent":["import type { StorageThreadType } from '@mastra/core/memory';\nimport type { MemoryStorage } from '@mastra/core/storage';\n\nimport { externallyAuthoredWorkItem } from '../rules/types.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { WorkItemRow } from '../storage/domains/work-items/base.js';\nimport { sessionStampCounts } from './session-threads.js';\n\n/** Slack-started sessions live on a `slack/<thread>` branch (see `threadBranch` in the Slack integration). */\nconst SLACK_SESSION_BRANCH_PREFIX = 'slack/';\n\n/**\n * Set on a thread while it moves and turned off once the move, vectors included, is done. A\n * crash in between leaves the thread moved in storage but no longer listed under its\n * session id, so the next start finds it by this flag and its session stamp and finishes the move.\n */\nconst MOVE_PENDING_KEY = 'factoryResourceMovePending';\n\nexport interface SessionResourceMigrationDeps {\n sessions: {\n listSessionsAwaitingResourceMigration(): Promise<SourceControlSession[]>;\n markResourcesMigrated(sessionId: string): Promise<void>;\n };\n workItems: {\n listRunBoundItems(sessionId: string): Promise<(Pick<WorkItemRow, 'externalSource' | 'metadata'> | null)[]>;\n moveRunBindingsToResource(input: { sessionId: string; resourceId: string }): Promise<number>;\n };\n /** Absent when the integrations domain is not ready; subscriptions then stay where they are. */\n integrations?: {\n moveSubscriptionsToResource(input: { fromResourceId: string; toResourceId: string }): Promise<number>;\n };\n memory: Pick<MemoryStorage, 'listThreads' | 'patchThread' | 'getObservationalMemory' | 'supportsObservationalMemory'>;\n /**\n * Moves a thread with everything keyed by its resource, such as observation vectors. Pass the\n * sessions' `Memory`; the bare memory store only moves the thread and its messages.\n */\n threads: { updateThreadResourceId(input: { threadId: string; resourceId: string }): Promise<StorageThreadType> };\n}\n\nexport interface SessionResourceMigrationReport {\n sessions: number;\n factorySessions: number;\n userSessions: number;\n /** Runs on untrusted content; they keep their own resource. */\n isolatedSessions: number;\n /** Factory-owned sessions with no Factory to move them onto; left untouched. */\n skipped: string[];\n /** Sessions whose move threw; the next startup retries them. */\n failed: string[];\n /**\n * Moved sessions whose subscriptions couldn't move because the integrations\n * domain wasn't ready; left unmarked so a later startup moves them.\n */\n subscriptionsPending: string[];\n threads: number;\n runBindings: number;\n subscriptions: number;\n /** Moved sessions whose resource-scoped observational memory stays under their old resource. */\n resourceObservationsLeft: string[];\n}\n\n/**\n * The memory resource a session belongs to: its Factory for runs, side\n * sessions and Slack channel threads, its owner for sessions a person opened,\n * private side sessions and Slack DMs. A run on untrusted content (a pull request, or a card\n * written outside the write-access circle) keeps its own resource, like a new\n * run would; so does a run whose work item no longer loads, since nothing then\n * shows its content was trusted. `null` when a Factory-owned session has no\n * Factory recorded — guessing would merge it into the wrong resource.\n */\nexport function sessionMemoryResource(\n session: SourceControlSession,\n boundItems: (Pick<WorkItemRow, 'externalSource' | 'metadata'> | null)[],\n): { owner: 'factory' | 'user' | 'isolated'; resourceId: string | null } {\n if (boundItems.some(item => item === null || untrustedWorkItem(item))) {\n return { owner: 'isolated', resourceId: session.sessionId };\n }\n // A Slack DM or private side session is its owner's, so it belongs with their own sessions.\n const sharedWithOrg =\n session.visibility !== 'private' &&\n (session.originSessionId !== null || session.branch.startsWith(SLACK_SESSION_BRANCH_PREFIX));\n const factoryOwned = boundItems.length > 0 || sharedWithOrg;\n return factoryOwned\n ? { owner: 'factory', resourceId: session.factoryProjectId }\n : { owner: 'user', resourceId: session.userId };\n}\n\nfunction untrustedWorkItem(item: Pick<WorkItemRow, 'externalSource' | 'metadata'>): boolean {\n return item.externalSource?.type === 'pull-request' || externallyAuthoredWorkItem(item);\n}\n\n/**\n * Move sessions stored under the legacy `resourceId = sessionId` onto their\n * Factory or user resource. Threads keep their id (`threadId === sessionId`)\n * and get `factorySessionId` so per-session listings still find them.\n *\n * Threads move through the sessions' `Memory`, so their observation vectors\n * follow them and recall search finds their past observations under the new\n * resource. Every write only touches values still equal to the session id, so\n * a rerun is a no-op and a crash leaves a state the next run resumes from. A\n * session is marked once moved, so later starts only read sessions that are not\n * (new sessions are created marked).\n * Nothing is deleted. Thread-scoped observational memory is keyed by thread id\n * and keeps working. Resource-scoped observational memory written under\n * `resource:<sessionId>` stays under that key and is no longer read: several\n * sessions merge into one resource, and their records cannot be merged.\n */\nexport async function migrateSessionResources(\n deps: SessionResourceMigrationDeps,\n): Promise<SessionResourceMigrationReport> {\n const report: SessionResourceMigrationReport = {\n sessions: 0,\n factorySessions: 0,\n userSessions: 0,\n isolatedSessions: 0,\n skipped: [],\n failed: [],\n subscriptionsPending: [],\n threads: 0,\n runBindings: 0,\n subscriptions: 0,\n resourceObservationsLeft: [],\n };\n const sessions = await deps.sessions.listSessionsAwaitingResourceMigration();\n\n for (const session of sessions) {\n report.sessions += 1;\n const { sessionId } = session;\n try {\n const { owner, resourceId } = sessionMemoryResource(session, await deps.workItems.listRunBoundItems(sessionId));\n if (!resourceId) {\n report.skipped.push(sessionId);\n continue;\n }\n if (owner === 'factory') report.factorySessions += 1;\n else if (owner === 'isolated') report.isolatedSessions += 1;\n else report.userSessions += 1;\n if (resourceId === sessionId) {\n await deps.sessions.markResourcesMigrated(sessionId);\n continue;\n }\n\n // Finish moves a crash interrupted after storage moved the thread.\n const { threads: interrupted } = await deps.memory.listThreads({\n filter: { resourceId, metadata: { factorySessionId: sessionId, [MOVE_PENDING_KEY]: true } },\n perPage: false,\n });\n for (const thread of interrupted) {\n if (thread.resourceId === sessionId) continue; // listed under the session id below\n await moveThread(deps, thread, thread.resourceId);\n report.threads += 1;\n }\n const { threads } = await deps.memory.listThreads({ filter: { resourceId: sessionId }, perPage: false });\n // A private session's stamps don't count under its Factory, so only its own\n // thread moves there; its other threads stay listed under its id.\n const stampsCount = sessionStampCounts(session, resourceId);\n for (const thread of threads) {\n if (!stampsCount && thread.id !== sessionId) continue;\n // Tag before moving: once moved, the thread is no longer found under the session id.\n const metadata = { ...thread.metadata, factorySessionId: sessionId, [MOVE_PENDING_KEY]: true };\n await deps.memory.patchThread({ id: thread.id, metadata });\n await moveThread(deps, { ...thread, metadata }, resourceId);\n report.threads += 1;\n }\n report.runBindings += await deps.workItems.moveRunBindingsToResource({ sessionId, resourceId });\n if (deps.integrations) {\n report.subscriptions += await deps.integrations.moveSubscriptionsToResource({\n fromResourceId: sessionId,\n toResourceId: resourceId,\n });\n }\n // Reported once, on the run that moves the session's threads.\n if (\n threads.length > 0 &&\n deps.memory.supportsObservationalMemory &&\n (await deps.memory.getObservationalMemory(null, sessionId))\n ) {\n report.resourceObservationsLeft.push(sessionId);\n console.warn(\n `[factory] session resource migration: session ${sessionId} has resource-scoped observational memory; ` +\n `it stays under resource ${sessionId} and is not carried into ${resourceId}`,\n );\n }\n if (!deps.integrations) {\n report.subscriptionsPending.push(sessionId);\n continue;\n }\n await deps.sessions.markResourcesMigrated(sessionId);\n } catch (error) {\n report.failed.push(sessionId);\n console.warn(\n `[factory] session resource migration: session ${sessionId} not moved: ${error instanceof Error ? error.message : error}`,\n );\n }\n }\n\n if (\n report.threads +\n report.runBindings +\n report.subscriptions +\n report.skipped.length +\n report.failed.length +\n report.subscriptionsPending.length >\n 0\n ) {\n console.info(\n `[factory] session resource migration: ${report.threads} threads, ${report.runBindings} run bindings, ` +\n `${report.subscriptions} subscriptions moved; ${report.skipped.length} Factory sessions without a Factory skipped; ` +\n `${report.failed.length} sessions failed; ${report.subscriptionsPending.length} sessions wait for integrations ` +\n `to move their subscriptions`,\n );\n }\n return report;\n}\n\nasync function moveThread(\n deps: SessionResourceMigrationDeps,\n thread: StorageThreadType,\n resourceId: string,\n): Promise<void> {\n await deps.threads.updateThreadResourceId({ threadId: thread.id, resourceId });\n // Stores merge thread metadata, so the flag is switched off rather than removed.\n await deps.memory.patchThread({ id: thread.id, metadata: { ...thread.metadata, [MOVE_PENDING_KEY]: false } });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resource-migration.js","names":[],"sources":["../../src/session/resource-migration.ts"],"sourcesContent":["import type { StorageThreadType } from '@mastra/core/memory';\nimport type { MemoryStorage } from '@mastra/core/storage';\n\nimport { externallyAuthoredWorkItem } from '../rules/types.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { WorkItemRow } from '../storage/domains/work-items/base.js';\nimport { sessionStampCounts } from './session-threads.js';\n\n/** Slack-started sessions live on a `slack/<thread>` branch (see `threadBranch` in the Slack integration). */\nconst SLACK_SESSION_BRANCH_PREFIX = 'slack/';\n\n/**\n * Set on a thread while it moves and turned off once the move, vectors included, is done. A\n * crash in between leaves the thread moved in storage but no longer listed under its\n * session id, so the next start finds it by this flag and its session stamp and finishes the move.\n */\nconst MOVE_PENDING_KEY = 'factoryResourceMovePending';\n/** How often a long resource migration reports how far it got. */\nconst PROGRESS_LOG_INTERVAL_MS = 10_000;\n\nexport interface SessionResourceMigrationDeps {\n sessions: {\n listSessionsAwaitingResourceMigration(): Promise<SourceControlSession[]>;\n markResourcesMigrated(sessionId: string): Promise<void>;\n };\n workItems: {\n listRunBoundItems(sessionId: string): Promise<(Pick<WorkItemRow, 'externalSource' | 'metadata'> | null)[]>;\n moveRunBindingsToResource(input: { sessionId: string; resourceId: string }): Promise<number>;\n };\n /** Absent when the integrations domain is not ready; subscriptions then stay where they are. */\n integrations?: {\n moveSubscriptionsToResource(input: { fromResourceId: string; toResourceId: string }): Promise<number>;\n };\n memory: Pick<MemoryStorage, 'listThreads' | 'patchThread' | 'getObservationalMemory' | 'supportsObservationalMemory'>;\n /**\n * Moves a thread with everything keyed by its resource, such as observation vectors. Pass the\n * sessions' `Memory`; the bare memory store only moves the thread and its messages.\n */\n threads: { updateThreadResourceId(input: { threadId: string; resourceId: string }): Promise<StorageThreadType> };\n}\n\nexport interface SessionResourceMigrationReport {\n sessions: number;\n factorySessions: number;\n userSessions: number;\n /** Runs on untrusted content; they keep their own resource. */\n isolatedSessions: number;\n /** Factory-owned sessions with no Factory to move them onto; left untouched. */\n skipped: string[];\n /** Sessions whose move threw; the next startup retries them. */\n failed: string[];\n /**\n * Moved sessions whose subscriptions couldn't move because the integrations\n * domain wasn't ready; left unmarked so a later startup moves them.\n */\n subscriptionsPending: string[];\n threads: number;\n runBindings: number;\n subscriptions: number;\n /** Moved sessions whose resource-scoped observational memory stays under their old resource. */\n resourceObservationsLeft: string[];\n}\n\n/**\n * The memory resource a session belongs to: its Factory for runs, side\n * sessions and Slack channel threads, its owner for sessions a person opened,\n * private side sessions and Slack DMs. A run on untrusted content (a pull request, or a card\n * written outside the write-access circle) keeps its own resource, like a new\n * run would; so does a run whose work item no longer loads, since nothing then\n * shows its content was trusted. `null` when a Factory-owned session has no\n * Factory recorded — guessing would merge it into the wrong resource.\n */\nexport function sessionMemoryResource(\n session: SourceControlSession,\n boundItems: (Pick<WorkItemRow, 'externalSource' | 'metadata'> | null)[],\n): { owner: 'factory' | 'user' | 'isolated'; resourceId: string | null } {\n if (boundItems.some(item => item === null || untrustedWorkItem(item))) {\n return { owner: 'isolated', resourceId: session.sessionId };\n }\n // A Slack DM or private side session is its owner's, so it belongs with their own sessions.\n const sharedWithOrg =\n session.visibility !== 'private' &&\n (session.originSessionId !== null || session.branch.startsWith(SLACK_SESSION_BRANCH_PREFIX));\n const factoryOwned = boundItems.length > 0 || sharedWithOrg;\n return factoryOwned\n ? { owner: 'factory', resourceId: session.factoryProjectId }\n : { owner: 'user', resourceId: session.userId };\n}\n\nfunction untrustedWorkItem(item: Pick<WorkItemRow, 'externalSource' | 'metadata'>): boolean {\n return item.externalSource?.type === 'pull-request' || externallyAuthoredWorkItem(item);\n}\n\n/**\n * Move sessions stored under the legacy `resourceId = sessionId` onto their\n * Factory or user resource. Threads keep their id (`threadId === sessionId`)\n * and get `factorySessionId` so per-session listings still find them.\n *\n * Threads move through the sessions' `Memory`, so their observation vectors\n * follow them and recall search finds their past observations under the new\n * resource. Every write only touches values still equal to the session id, so\n * a rerun is a no-op and a crash leaves a state the next run resumes from. A\n * session is marked once moved, so later starts only read sessions that are not\n * (new sessions are created marked).\n * Nothing is deleted. Thread-scoped observational memory is keyed by thread id\n * and keeps working. Resource-scoped observational memory written under\n * `resource:<sessionId>` stays under that key and is no longer read: several\n * sessions merge into one resource, and their records cannot be merged.\n */\nexport async function migrateSessionResources(\n deps: SessionResourceMigrationDeps,\n): Promise<SessionResourceMigrationReport> {\n const report: SessionResourceMigrationReport = {\n sessions: 0,\n factorySessions: 0,\n userSessions: 0,\n isolatedSessions: 0,\n skipped: [],\n failed: [],\n subscriptionsPending: [],\n threads: 0,\n runBindings: 0,\n subscriptions: 0,\n resourceObservationsLeft: [],\n };\n const sessions = await deps.sessions.listSessionsAwaitingResourceMigration();\n // This pass can take minutes against a remote database, so say what it is doing and\n // how far it got.\n if (sessions.length > 0) {\n console.info(`[factory] session resource migration: moving ${sessions.length} sessions in the background`);\n }\n let lastProgressAt = Date.now();\n\n for (const session of sessions) {\n report.sessions += 1;\n const { sessionId } = session;\n try {\n const { owner, resourceId } = sessionMemoryResource(session, await deps.workItems.listRunBoundItems(sessionId));\n if (!resourceId) {\n report.skipped.push(sessionId);\n continue;\n }\n if (owner === 'factory') report.factorySessions += 1;\n else if (owner === 'isolated') report.isolatedSessions += 1;\n else report.userSessions += 1;\n if (resourceId === sessionId) {\n await deps.sessions.markResourcesMigrated(sessionId);\n continue;\n }\n\n // Finish moves a crash interrupted after storage moved the thread.\n const { threads: interrupted } = await deps.memory.listThreads({\n filter: { resourceId, metadata: { factorySessionId: sessionId, [MOVE_PENDING_KEY]: true } },\n perPage: false,\n });\n for (const thread of interrupted) {\n if (thread.resourceId === sessionId) continue; // listed under the session id below\n await moveThread(deps, thread, thread.resourceId);\n report.threads += 1;\n }\n const { threads } = await deps.memory.listThreads({ filter: { resourceId: sessionId }, perPage: false });\n // A private session's stamps don't count under its Factory, so only its own\n // thread moves there; its other threads stay listed under its id.\n const stampsCount = sessionStampCounts(session, resourceId);\n for (const thread of threads) {\n if (!stampsCount && thread.id !== sessionId) continue;\n // Tag before moving: once moved, the thread is no longer found under the session id.\n const metadata = { ...thread.metadata, factorySessionId: sessionId, [MOVE_PENDING_KEY]: true };\n await deps.memory.patchThread({ id: thread.id, metadata });\n await moveThread(deps, { ...thread, metadata }, resourceId);\n report.threads += 1;\n }\n report.runBindings += await deps.workItems.moveRunBindingsToResource({ sessionId, resourceId });\n if (deps.integrations) {\n report.subscriptions += await deps.integrations.moveSubscriptionsToResource({\n fromResourceId: sessionId,\n toResourceId: resourceId,\n });\n }\n // Reported once, on the run that moves the session's threads.\n if (\n threads.length > 0 &&\n deps.memory.supportsObservationalMemory &&\n (await deps.memory.getObservationalMemory(null, sessionId))\n ) {\n report.resourceObservationsLeft.push(sessionId);\n console.warn(\n `[factory] session resource migration: session ${sessionId} has resource-scoped observational memory; ` +\n `it stays under resource ${sessionId} and is not carried into ${resourceId}`,\n );\n }\n if (!deps.integrations) {\n report.subscriptionsPending.push(sessionId);\n continue;\n }\n await deps.sessions.markResourcesMigrated(sessionId);\n } catch (error) {\n report.failed.push(sessionId);\n console.warn(\n `[factory] session resource migration: session ${sessionId} not moved: ${error instanceof Error ? error.message : error}`,\n );\n } finally {\n if (report.sessions < sessions.length && Date.now() - lastProgressAt >= PROGRESS_LOG_INTERVAL_MS) {\n lastProgressAt = Date.now();\n console.info(\n `[factory] session resource migration: ${report.sessions}/${sessions.length} sessions checked, ` +\n `${report.threads} threads moved`,\n );\n }\n }\n }\n\n if (\n report.threads +\n report.runBindings +\n report.subscriptions +\n report.skipped.length +\n report.failed.length +\n report.subscriptionsPending.length >\n 0\n ) {\n console.info(\n `[factory] session resource migration: ${report.threads} threads, ${report.runBindings} run bindings, ` +\n `${report.subscriptions} subscriptions moved; ${report.skipped.length} Factory sessions without a Factory skipped; ` +\n `${report.failed.length} sessions failed; ${report.subscriptionsPending.length} sessions wait for integrations ` +\n `to move their subscriptions`,\n );\n }\n return report;\n}\n\nexport interface SessionVisibilityBackfillDeps {\n sessions: {\n listSessionsAwaitingVisibilityCheck(): Promise<SourceControlSession[]>;\n markVisibilityChecked(sessionId: string, options: { makePrivate: boolean }): Promise<void>;\n };\n workItems: Pick<SessionResourceMigrationDeps['workItems'], 'listRunBoundItems'>;\n memory: Pick<MemoryStorage, 'getThreadById'>;\n}\n\n/**\n * Mark shared user sessions private when their conversation lives on their owner's resource.\n * The resource already keeps teammates out of such a session; this makes lists agree, so they\n * stop seeing a session they can't open. Sessions shared onto their Factory, runs, Slack threads\n * and side sessions are left alone. Each session is checked once: new sessions start checked,\n * and a session that throws stays unchecked so the next start retries it.\n */\nexport async function privatizeOwnerResourceSessions(deps: SessionVisibilityBackfillDeps): Promise<number> {\n let privatized = 0;\n for (const session of await deps.sessions.listSessionsAwaitingVisibilityCheck()) {\n try {\n let makePrivate = false;\n if (session.visibility !== 'private') {\n const { owner } = sessionMemoryResource(session, await deps.workItems.listRunBoundItems(session.sessionId));\n const thread = owner === 'user' ? await deps.memory.getThreadById({ threadId: session.sessionId }) : null;\n makePrivate = thread?.resourceId === session.userId;\n }\n await deps.sessions.markVisibilityChecked(session.sessionId, { makePrivate });\n if (makePrivate) privatized += 1;\n } catch (error) {\n console.warn(\n `[factory] session visibility check: session ${session.sessionId} not checked: ${error instanceof Error ? error.message : error}`,\n );\n }\n }\n if (privatized > 0) {\n console.info(`[factory] session visibility check: ${privatized} sessions on their owner's memory made private`);\n }\n return privatized;\n}\n\nasync function moveThread(\n deps: SessionResourceMigrationDeps,\n thread: StorageThreadType,\n resourceId: string,\n): Promise<void> {\n await deps.threads.updateThreadResourceId({ threadId: thread.id, resourceId });\n // Stores merge thread metadata, so the flag is switched off rather than removed.\n await deps.memory.patchThread({ id: thread.id, metadata: { ...thread.metadata, [MOVE_PENDING_KEY]: false } });\n}\n"],"mappings":"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"}
|
|
@@ -28,6 +28,15 @@ export declare function isRunBound(session: FactorySessionOwnership, runs: Sessi
|
|
|
28
28
|
export declare function memoryResourceOf(session: FactorySessionOwnership, own: {
|
|
29
29
|
resourceId?: string | null;
|
|
30
30
|
} | null | undefined, runBound: boolean): string;
|
|
31
|
+
/** The resource a person's session belongs on for a visibility: its Factory's once shared, its owner's while private. */
|
|
32
|
+
export declare function userSessionResourceFor(session: Pick<FactorySessionOwnership, 'userId' | 'factoryProjectId'>, visibility: 'org' | 'private'): string;
|
|
33
|
+
/**
|
|
34
|
+
* The threads to move when a person's session changes visibility and so moves
|
|
35
|
+
* onto `resourceId`: its own thread and the threads stamped with its id that
|
|
36
|
+
* sit on its owner's or Factory's resource. Threads under the session's own id
|
|
37
|
+
* (from before resources were shared) stay where they are.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sessionThreadsToMove(controller: Pick<AgentController<any>, 'queryThreads' | 'queryThreadById'>, session: FactorySessionOwnership, resourceId: string): Promise<string[]>;
|
|
31
40
|
/**
|
|
32
41
|
* Whether a `factorySessionId` stamp naming this session counts on a thread
|
|
33
42
|
* under `resourceId`. Any org member can write a stamped thread under the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-threads.d.ts","sourceRoot":"","sources":["../../src/session/session-threads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE5F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;AAEnF,KAAK,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAElE;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,YAAY,GAAG,SAAS,EACpC,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,kBAAkB,GAAG,SAAS,GACnC,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,kBAAkB,GAAG,SAAS,oBAOtG;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,uBAAuB,EAChC,GAAG,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACtD,QAAQ,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"session-threads.d.ts","sourceRoot":"","sources":["../../src/session/session-threads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE5F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,CAAC,CAAC;AAEnF,KAAK,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAElE;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,YAAY,GAAG,SAAS,EACpC,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,kBAAkB,GAAG,SAAS,GACnC,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,EAAE,kBAAkB,GAAG,SAAS,oBAOtG;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,uBAAuB,EAChC,GAAG,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACtD,QAAQ,EAAE,OAAO,UA4BlB;AAED,yHAAyH;AACzH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,GAAG,kBAAkB,CAAC,EACrE,UAAU,EAAE,KAAK,GAAG,SAAS,GAC5B,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,cAAc,GAAG,iBAAiB,CAAC,EAC1E,OAAO,EAAE,uBAAuB,EAChC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CAUnB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEnH;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,cAAc,GAAG,iBAAiB,CAAC,EAC1E,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,kBAAkB,GAAG,SAAS,GACnC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAgBlC"}
|
|
@@ -24,13 +24,33 @@ async function isRunBound(session, runs) {
|
|
|
24
24
|
function memoryResourceOf(session, own, runBound) {
|
|
25
25
|
const factoryProjectId = session.factoryProjectId ?? void 0;
|
|
26
26
|
const shared = (session.branch?.startsWith("slack/") ?? false) || session.originSessionId ? session.visibility === "private" ? session.userId : factoryProjectId : void 0;
|
|
27
|
-
|
|
27
|
+
if (shared) {
|
|
28
|
+
if (own?.resourceId && [session.sessionId, shared].includes(own.resourceId)) return own.resourceId;
|
|
29
|
+
return shared;
|
|
30
|
+
}
|
|
31
|
+
const allowed = [
|
|
28
32
|
session.sessionId,
|
|
29
33
|
session.userId,
|
|
30
|
-
...runBound && factoryProjectId ? [factoryProjectId] : []
|
|
34
|
+
...(runBound || session.visibility === "org") && factoryProjectId ? [factoryProjectId] : []
|
|
31
35
|
];
|
|
32
36
|
if (own?.resourceId && allowed.includes(own.resourceId)) return own.resourceId;
|
|
33
|
-
return
|
|
37
|
+
return userSessionResourceFor(session, session.visibility === "org" ? "org" : "private");
|
|
38
|
+
}
|
|
39
|
+
/** The resource a person's session belongs on for a visibility: its Factory's once shared, its owner's while private. */
|
|
40
|
+
function userSessionResourceFor(session, visibility) {
|
|
41
|
+
return visibility === "org" && session.factoryProjectId ? session.factoryProjectId : session.userId;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The threads to move when a person's session changes visibility and so moves
|
|
45
|
+
* onto `resourceId`: its own thread and the threads stamped with its id that
|
|
46
|
+
* sit on its owner's or Factory's resource. Threads under the session's own id
|
|
47
|
+
* (from before resources were shared) stay where they are.
|
|
48
|
+
*/
|
|
49
|
+
async function sessionThreadsToMove(controller, session, resourceId) {
|
|
50
|
+
const movable = /* @__PURE__ */ new Set([session.userId, ...session.factoryProjectId ? [session.factoryProjectId] : []]);
|
|
51
|
+
const [own, stamped] = await Promise.all([controller.queryThreadById({ threadId: session.sessionId }), controller.queryThreads({ metadata: { factorySessionId: session.sessionId } })]);
|
|
52
|
+
const threads = [...own ? [own] : [], ...stamped].filter((thread) => thread.resourceId !== resourceId && movable.has(thread.resourceId));
|
|
53
|
+
return [...new Set(threads.map((thread) => thread.id))];
|
|
34
54
|
}
|
|
35
55
|
/**
|
|
36
56
|
* Whether a `factorySessionId` stamp naming this session counts on a thread
|
|
@@ -65,6 +85,6 @@ async function listFactorySessionThreads(controller, session, runs) {
|
|
|
65
85
|
return [...threads.values()];
|
|
66
86
|
}
|
|
67
87
|
//#endregion
|
|
68
|
-
export { isRunBound, listFactorySessionThreads, memoryResourceOf, sessionMemoryResourceId, sessionStampCounts };
|
|
88
|
+
export { isRunBound, listFactorySessionThreads, memoryResourceOf, sessionMemoryResourceId, sessionStampCounts, sessionThreadsToMove, userSessionResourceFor };
|
|
69
89
|
|
|
70
90
|
//# sourceMappingURL=session-threads.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-threads.js","names":[],"sources":["../../src/session/session-threads.ts"],"sourcesContent":["import type { AgentController, AgentControllerThread } from '@mastra/core/agent-controller';\n\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\n/** The Factory session row fields that say which memory resource it uses. */\nexport interface FactorySessionOwnership {\n sessionId: string;\n userId: string;\n orgId?: string;\n factoryProjectId?: string | null;\n branch?: string;\n visibility?: string;\n originSessionId?: string | null;\n}\n\n/** Run bindings: the server-side record that a session runs work for its Factory. */\nexport type SessionRunBindings = Pick<WorkItemsStorage, 'hasRunBindingForSession'>;\n\ntype ThreadLookup = Pick<AgentController<any>, 'queryThreadById'>;\n\n/**\n * The one memory resource a Factory session runs under. A Slack session uses\n * its Factory's, or its sender's for a DM; a side session uses its Factory's,\n * or its owner's when private; any other session uses its owner's. A session's own thread can say otherwise\n * only within what the server recorded for it: its own id (created before\n * resources were shared, or a run kept on its own resource), and the Factory's\n * only for a session with a run binding. A client can create a thread under the\n * session's id, so the thread alone never makes a session the Factory's.\n */\nexport async function sessionMemoryResourceId(\n controller: ThreadLookup | undefined,\n session: FactorySessionOwnership,\n runs: SessionRunBindings | undefined,\n): Promise<string> {\n const [own, runBound] = await Promise.all([\n controller?.queryThreadById({ threadId: session.sessionId }),\n isRunBound(session, runs),\n ]);\n return memoryResourceOf(session, own, runBound);\n}\n\nexport async function isRunBound(session: FactorySessionOwnership, runs: SessionRunBindings | undefined) {\n if (!runs || !session.orgId || !session.factoryProjectId) return false;\n return runs.hasRunBindingForSession({\n orgId: session.orgId,\n factoryProjectId: session.factoryProjectId,\n sessionId: session.sessionId,\n });\n}\n\n/** {@link sessionMemoryResourceId} for a caller that already loaded the session's own thread. */\nexport function memoryResourceOf(\n session: FactorySessionOwnership,\n own: { resourceId?: string | null } | null | undefined,\n runBound: boolean,\n) {\n const factoryProjectId = session.factoryProjectId ?? undefined;\n const slack = session.branch?.startsWith('slack/') ?? false;\n // A private Slack DM or side session stays with its owner.\n const shared =\n slack || session.originSessionId\n ? session.visibility === 'private'\n ? session.userId\n : factoryProjectId\n : undefined;\n
|
|
1
|
+
{"version":3,"file":"session-threads.js","names":[],"sources":["../../src/session/session-threads.ts"],"sourcesContent":["import type { AgentController, AgentControllerThread } from '@mastra/core/agent-controller';\n\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\n\n/** The Factory session row fields that say which memory resource it uses. */\nexport interface FactorySessionOwnership {\n sessionId: string;\n userId: string;\n orgId?: string;\n factoryProjectId?: string | null;\n branch?: string;\n visibility?: string;\n originSessionId?: string | null;\n}\n\n/** Run bindings: the server-side record that a session runs work for its Factory. */\nexport type SessionRunBindings = Pick<WorkItemsStorage, 'hasRunBindingForSession'>;\n\ntype ThreadLookup = Pick<AgentController<any>, 'queryThreadById'>;\n\n/**\n * The one memory resource a Factory session runs under. A Slack session uses\n * its Factory's, or its sender's for a DM; a side session uses its Factory's,\n * or its owner's when private; any other session uses its owner's. A session's own thread can say otherwise\n * only within what the server recorded for it: its own id (created before\n * resources were shared, or a run kept on its own resource), and the Factory's\n * only for a session with a run binding. A client can create a thread under the\n * session's id, so the thread alone never makes a session the Factory's.\n */\nexport async function sessionMemoryResourceId(\n controller: ThreadLookup | undefined,\n session: FactorySessionOwnership,\n runs: SessionRunBindings | undefined,\n): Promise<string> {\n const [own, runBound] = await Promise.all([\n controller?.queryThreadById({ threadId: session.sessionId }),\n isRunBound(session, runs),\n ]);\n return memoryResourceOf(session, own, runBound);\n}\n\nexport async function isRunBound(session: FactorySessionOwnership, runs: SessionRunBindings | undefined) {\n if (!runs || !session.orgId || !session.factoryProjectId) return false;\n return runs.hasRunBindingForSession({\n orgId: session.orgId,\n factoryProjectId: session.factoryProjectId,\n sessionId: session.sessionId,\n });\n}\n\n/** {@link sessionMemoryResourceId} for a caller that already loaded the session's own thread. */\nexport function memoryResourceOf(\n session: FactorySessionOwnership,\n own: { resourceId?: string | null } | null | undefined,\n runBound: boolean,\n) {\n const factoryProjectId = session.factoryProjectId ?? undefined;\n const slack = session.branch?.startsWith('slack/') ?? false;\n // A private Slack DM or side session stays with its owner.\n const shared =\n slack || session.originSessionId\n ? session.visibility === 'private'\n ? session.userId\n : factoryProjectId\n : undefined;\n if (shared) {\n if (own?.resourceId && [session.sessionId, shared].includes(own.resourceId)) return own.resourceId;\n return shared;\n }\n // A session a person started runs on their resource while private and its\n // Factory's once its owner shares it, which moves its threads there. Its own\n // thread says where it is; a client cannot make a private session the\n // Factory's by writing that thread, since the Factory is only allowed for a\n // shared or run-bound session.\n const allowed = [\n session.sessionId,\n session.userId,\n ...((runBound || session.visibility === 'org') && factoryProjectId ? [factoryProjectId] : []),\n ];\n if (own?.resourceId && allowed.includes(own.resourceId)) return own.resourceId;\n // No thread yet: where a session with this visibility starts.\n return userSessionResourceFor(session, session.visibility === 'org' ? 'org' : 'private');\n}\n\n/** The resource a person's session belongs on for a visibility: its Factory's once shared, its owner's while private. */\nexport function userSessionResourceFor(\n session: Pick<FactorySessionOwnership, 'userId' | 'factoryProjectId'>,\n visibility: 'org' | 'private',\n): string {\n return visibility === 'org' && session.factoryProjectId ? session.factoryProjectId : session.userId;\n}\n\n/**\n * The threads to move when a person's session changes visibility and so moves\n * onto `resourceId`: its own thread and the threads stamped with its id that\n * sit on its owner's or Factory's resource. Threads under the session's own id\n * (from before resources were shared) stay where they are.\n */\nexport async function sessionThreadsToMove(\n controller: Pick<AgentController<any>, 'queryThreads' | 'queryThreadById'>,\n session: FactorySessionOwnership,\n resourceId: string,\n): Promise<string[]> {\n const movable = new Set([session.userId, ...(session.factoryProjectId ? [session.factoryProjectId] : [])]);\n const [own, stamped] = await Promise.all([\n controller.queryThreadById({ threadId: session.sessionId }),\n controller.queryThreads({ metadata: { factorySessionId: session.sessionId } }),\n ]);\n const threads = [...(own ? [own] : []), ...stamped].filter(\n thread => thread.resourceId !== resourceId && movable.has(thread.resourceId),\n );\n return [...new Set(threads.map(thread => thread.id))];\n}\n\n/**\n * Whether a `factorySessionId` stamp naming this session counts on a thread\n * under `resourceId`. Any org member can write a stamped thread under the\n * Factory's resource, so a private session's stamp never counts there: only\n * its own thread (`thread.id === sessionId`) does.\n */\nexport function sessionStampCounts(session: FactorySessionOwnership, resourceId: string | null | undefined): boolean {\n return !(session.visibility === 'private' && !!session.factoryProjectId && resourceId === session.factoryProjectId);\n}\n\n/**\n * The threads of one Factory session. Sessions share their Factory's or user's\n * resource, so listing by resource would return every session's threads. A\n * session's own thread carries its id, threads it opens are stamped with\n * `factorySessionId`, and a session created before resources were shared keeps\n * its threads under its own id as the resource. Thread ids and stamps are\n * client-writable, so the own and stamped threads only count under the\n * session's memory resource.\n */\nexport async function listFactorySessionThreads(\n controller: Pick<AgentController<any>, 'queryThreads' | 'queryThreadById'>,\n session: FactorySessionOwnership,\n runs: SessionRunBindings | undefined,\n): Promise<AgentControllerThread[]> {\n const { sessionId } = session;\n const [own, runBound, stamped, legacy] = await Promise.all([\n controller.queryThreadById({ threadId: sessionId }),\n isRunBound(session, runs),\n controller.queryThreads({ metadata: { factorySessionId: sessionId } }),\n controller.queryThreads({ resourceId: sessionId }),\n ]);\n const resourceId = memoryResourceOf(session, own, runBound);\n const threads = new Map<string, AgentControllerThread>();\n const owned = [\n ...(own?.resourceId === resourceId ? [own] : []),\n ...(sessionStampCounts(session, resourceId) ? stamped.filter(thread => thread.resourceId === resourceId) : []),\n ];\n for (const thread of [...owned, ...legacy]) threads.set(thread.id, thread);\n return [...threads.values()];\n}\n"],"mappings":"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"}
|
|
@@ -374,6 +374,11 @@ export interface SourceControlStorageHandle {
|
|
|
374
374
|
sessionId: string;
|
|
375
375
|
muted: boolean;
|
|
376
376
|
}): Promise<void>;
|
|
377
|
+
/** Change who can see the session. Returns the updated row, or null when it does not exist. */
|
|
378
|
+
setVisibility(args: {
|
|
379
|
+
sessionId: string;
|
|
380
|
+
visibility: SourceControlSessionVisibility;
|
|
381
|
+
}): Promise<SourceControlSession | null>;
|
|
377
382
|
/** Keyed by the link (today's callers) or by the factory. */
|
|
378
383
|
getForBranch(args: SessionBranchKey): Promise<SourceControlSession | null>;
|
|
379
384
|
create(input: CreateSourceControlSessionInput): Promise<SourceControlSession>;
|
|
@@ -427,6 +432,16 @@ export declare class SourceControlStorage extends FactoryStorageDomain {
|
|
|
427
432
|
constructor();
|
|
428
433
|
/** Sessions, across providers and organizations, the memory-resource migration has not finished. */
|
|
429
434
|
listSessionsAwaitingResourceMigration(): Promise<SourceControlSession[]>;
|
|
435
|
+
/**
|
|
436
|
+
* Sessions, across providers and organizations, whose visibility startup has not yet checked against
|
|
437
|
+
* their memory resource. Only sessions already on their resource are returned; the rest wait for the
|
|
438
|
+
* resource migration.
|
|
439
|
+
*/
|
|
440
|
+
listSessionsAwaitingVisibilityCheck(): Promise<SourceControlSession[]>;
|
|
441
|
+
/** Record that a session's visibility was checked, making it private when `makePrivate`. */
|
|
442
|
+
markVisibilityChecked(sessionId: string, { makePrivate }: {
|
|
443
|
+
makePrivate: boolean;
|
|
444
|
+
}): Promise<void>;
|
|
430
445
|
/** Record that a session's threads are on their memory resource, so later starts skip it. */
|
|
431
446
|
markResourcesMigrated(sessionId: string): Promise<void>;
|
|
432
447
|
init(): Promise<void>;
|