@mastra/factory 0.9.0-alpha.2 → 0.9.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +4 -4
- package/dist/factory.js.map +1 -1
- package/dist/integrations/platform/api-client.d.ts.map +1 -1
- package/dist/integrations/platform/api-client.js +2 -2
- package/dist/integrations/platform/api-client.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +2 -1
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +7 -3
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/routes/config.d.ts +12 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +74 -20
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/oauth.d.ts +3 -3
- package/dist/routes/oauth.d.ts.map +1 -1
- package/dist/routes/oauth.js +36 -19
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +1 -0
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.d.ts +1 -1
- package/dist/routes/tenant-credentials.d.ts.map +1 -1
- package/dist/routes/tenant-credentials.js +12 -7
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +9 -3
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/session/factory-session.d.ts +11 -2
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +8 -4
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/memory-settings-hydration.d.ts +1 -1
- package/dist/session/memory-settings-hydration.d.ts.map +1 -1
- package/dist/session/memory-settings-hydration.js +2 -2
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/storage/domains/credentials/base.d.ts +9 -5
- package/dist/storage/domains/credentials/base.d.ts.map +1 -1
- package/dist/storage/domains/credentials/base.js +34 -27
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/memory-settings/base.d.ts +8 -0
- package/dist/storage/domains/memory-settings/base.d.ts.map +1 -1
- package/dist/storage/domains/memory-settings/base.js +11 -1
- package/dist/storage/domains/memory-settings/base.js.map +1 -1
- package/package.json +7 -7
package/dist/routes/oauth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.js","names":[],"sources":["../../src/routes/oauth.ts"],"sourcesContent":["/**\n * Web OAuth sign-in routes for model providers (Settings › Providers).\n *\n * Wraps the SDK's step-based OAuth primitives (start/complete for Anthropic's\n * paste-code PKCE flow, start/poll for the Codex/Copilot/xAI device flows) in\n * HTTP routes. Flow state lives in login sessions — the `model-credentials`\n * domain's `oauth_login_sessions` table in tenant mode (any replica can\n * complete/poll), an in-memory store in local mode — so a flow can span\n * requests. Completed credentials are always **user-scoped** (plan tokens are\n * personal subscriptions) in tenant mode, or written to the file-backed\n * `AuthStorage` in local mode.\n *\n * Tokens never leave the server; responses only carry flow metadata (URLs,\n * user codes, poll delays).\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { nextPollDelayMs } from '@mastra/code-sdk/auth/device-code';\nimport { completeAnthropicLogin, startAnthropicLogin } from '@mastra/code-sdk/auth/providers/anthropic';\nimport {\n copilotNextPollDelayMs,\n pollGitHubCopilotDeviceLogin,\n startGitHubCopilotDeviceLogin,\n} from '@mastra/code-sdk/auth/providers/github-copilot';\nimport type { CopilotDeviceLoginPending } from '@mastra/code-sdk/auth/providers/github-copilot';\nimport { pollCodexDeviceLogin, startCodexDeviceLogin } from '@mastra/code-sdk/auth/providers/openai-codex';\nimport type { CodexDeviceLoginPending } from '@mastra/code-sdk/auth/providers/openai-codex';\nimport { pollXAIDeviceLogin, startXAIDeviceLogin } from '@mastra/code-sdk/auth/providers/xai';\nimport type { XAIDeviceLoginPending } from '@mastra/code-sdk/auth/providers/xai';\nimport type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { OAuthCredentials } from '@mastra/code-sdk/auth/types';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { LoginSessionKind, LoginSessionRow } from '../storage/domains/credentials/base.js';\nimport { getAuthProviderId, resolveCredentialContext } from './provider-credentials.js';\nimport type { CredentialContext } from './provider-credentials.js';\nimport { Route } from './route.js';\nimport type { RouteDependencies } from './route.js';\n\n/** Lifetime of a paste-code session (Anthropic gives no explicit expiry). */\nconst PASTE_CODE_TTL_MS = 10 * 60 * 1000;\n\ninterface OAuthFlowStart {\n url: string;\n userCode?: string;\n instructions: string;\n /** ms epoch after which the flow expires. */\n expiresAt: number;\n /** Delay before the first upstream poll (device-code flows only). */\n nextPollMs?: number;\n /** Serializable flow state persisted in the login session. */\n pending: Record<string, unknown>;\n}\n\ntype OAuthFlowPoll =\n | { status: 'complete'; credentials: OAuthCredentials }\n | { status: 'pending'; nextPollMs: number; pending?: Record<string, unknown> }\n | { status: 'failed'; error: string };\n\ninterface OAuthFlow {\n kind: LoginSessionKind;\n start(): Promise<OAuthFlowStart>;\n /** Paste-code flows: exchange the pasted code for credentials. Throws on bad input. */\n complete?(pending: Record<string, unknown>, code: string): Promise<OAuthCredentials>;\n /** Device-code flows: perform exactly one upstream poll. */\n poll?(pending: Record<string, unknown>): Promise<OAuthFlowPoll>;\n}\n\n/** Web OAuth flows keyed by *catalog* provider id (mirrors {@link WEB_OAUTH_FLOW_KINDS}). */\nconst OAUTH_FLOWS: Record<string, OAuthFlow> = {\n anthropic: {\n kind: 'paste-code',\n start: async () => {\n const { url, verifier } = await startAnthropicLogin();\n return {\n url,\n instructions: 'Open the link, authorize, then paste the code shown on the final page.',\n expiresAt: Date.now() + PASTE_CODE_TTL_MS,\n pending: { verifier },\n };\n },\n complete: (pending, code) => completeAnthropicLogin(code, String(pending.verifier ?? '')),\n },\n openai: {\n kind: 'device-code',\n start: async () => {\n const p = await startCodexDeviceLogin();\n return {\n url: p.url,\n userCode: p.userCode,\n instructions: p.instructions,\n expiresAt: p.deadlineAt,\n nextPollMs: p.intervalMs,\n pending: { ...p },\n };\n },\n poll: async pending => {\n const r = await pollCodexDeviceLogin(pending as unknown as CodexDeviceLoginPending);\n if (r.status === 'complete') return { status: 'complete', credentials: r.credentials };\n if (r.status === 'pending') return { status: 'pending', nextPollMs: r.nextPollMs };\n return { status: 'failed', error: r.error };\n },\n },\n 'github-copilot': {\n kind: 'device-code',\n start: async () => {\n const p = await startGitHubCopilotDeviceLogin();\n return {\n url: p.url,\n userCode: p.userCode,\n instructions: p.instructions,\n expiresAt: p.deadlineAt,\n nextPollMs: copilotNextPollDelayMs(p),\n pending: { ...p },\n };\n },\n poll: async pending => {\n const p = pending as unknown as CopilotDeviceLoginPending;\n // Web flows are always started against github.com (no Enterprise input).\n // Never let deserialized session state redirect server-side polling to\n // an arbitrary hostname.\n if (p.domain !== 'github.com' || p.enterpriseDomain !== undefined) {\n return { status: 'failed', error: 'Unsupported GitHub host' };\n }\n const r = await pollGitHubCopilotDeviceLogin(p);\n if (r.status === 'complete') return { status: 'complete', credentials: r.credentials };\n if (r.status === 'pending') return { status: 'pending', nextPollMs: r.nextPollMs, pending: { ...r.pending } };\n return { status: 'failed', error: r.error };\n },\n },\n xai: {\n kind: 'device-code',\n start: async () => {\n const p = await startXAIDeviceLogin();\n return {\n url: p.url,\n userCode: p.userCode,\n instructions: p.instructions,\n expiresAt: p.state.deadlineAt,\n nextPollMs: nextPollDelayMs(p.state),\n pending: { ...p },\n };\n },\n poll: async pending => {\n const r = await pollXAIDeviceLogin(pending as unknown as XAIDeviceLoginPending);\n if (r.status === 'complete') return { status: 'complete', credentials: r.credentials };\n if (r.status === 'pending') return { status: 'pending', nextPollMs: r.nextPollMs, pending: { ...r.pending } };\n return { status: 'failed', error: r.error };\n },\n },\n};\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\n/**\n * Local-mode login sessions. Flows in local mode are single-process, so a\n * process-local libsql `:memory:` database (which already handles TTL\n * cleanup) is sufficient.\n */\nlet localSessionsPromise: Promise<ModelCredentialsStorage> | undefined;\nfunction localSessions(): Promise<ModelCredentialsStorage> {\n localSessionsPromise ??= (async () => {\n const { LibSQLFactoryStorage } = await import('@mastra/libsql');\n const storage = new LibSQLFactoryStorage({ id: 'local-oauth-sessions', url: ':memory:' });\n const store = storage.registerDomain(new ModelCredentialsStorage());\n await storage.init();\n return store;\n })();\n return localSessionsPromise;\n}\nconst LOCAL_TENANT = { orgId: 'local', userId: 'local' } as const;\n\nasync function sessionStore(ctx: CredentialContext): Promise<ModelCredentialsStorage> {\n return ctx.mode === 'tenant' ? ctx.storage : localSessions();\n}\n\nfunction sessionTenant(ctx: CredentialContext): { orgId: string; userId: string } {\n return ctx.mode === 'tenant' ? { orgId: ctx.orgId, userId: ctx.userId } : LOCAL_TENANT;\n}\n\n/** Load a session and verify it belongs to the caller + provider (else undefined). */\nasync function loadOwnedSession({\n ctx,\n provider,\n sessionId,\n}: {\n ctx: CredentialContext;\n provider: string;\n sessionId: string;\n}): Promise<LoginSessionRow | undefined> {\n const session = await (await sessionStore(ctx)).getLoginSession(sessionId);\n if (!session) return undefined;\n const tenant = sessionTenant(ctx);\n if (session.orgId !== tenant.orgId || session.userId !== tenant.userId) return undefined;\n if (session.provider !== provider) return undefined;\n return session;\n}\n\n/** Persist completed OAuth credentials — always user-scoped in tenant mode. */\nasync function persistOAuthCredential({\n ctx,\n provider,\n credentials,\n authStorage,\n onCredentialsChanged,\n}: {\n ctx: CredentialContext;\n provider: string;\n credentials: OAuthCredentials;\n authStorage: AuthStorage | undefined;\n onCredentialsChanged: (tenant: { orgId: string; userId?: string }) => void;\n}): Promise<void> {\n const authProviderId = getAuthProviderId(provider);\n if (ctx.mode === 'tenant') {\n await ctx.storage.setCredential({ orgId: ctx.orgId, userId: ctx.userId }, authProviderId, {\n type: 'oauth',\n ...credentials,\n });\n onCredentialsChanged({ orgId: ctx.orgId, userId: ctx.userId });\n return;\n }\n if (!authStorage) throw new Error('Credential storage is not available');\n authStorage.set(authProviderId, { type: 'oauth', ...credentials });\n}\n\nasync function readJsonBody(c: Context): Promise<Record<string, unknown>> {\n try {\n const body = (await c.req.json()) as unknown;\n return body && typeof body === 'object' ? (body as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nexport interface OAuthRoutesDeps extends RouteDependencies {\n /** File-backed credential store; used in local (no-auth) mode. */\n authStorage?: AuthStorage;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n modelCredentials?: ModelCredentialsStorage;\n /** Notifies the host after tenant credentials change so caches can be dropped. */\n onCredentialsChanged?: (tenant: { orgId: string; userId?: string }) => void;\n}\n\n/**\n * Provider OAuth sign-in routes as Mastra `apiRoutes`:\n * - `POST /web/config/providers/:provider/oauth/start` — begin a sign-in flow\n * - `POST /web/config/providers/:provider/oauth/complete` — paste-code exchange\n * - `POST /web/config/providers/:provider/oauth/poll` — one device-code poll\n * - `DELETE /web/config/providers/:provider/oauth/session/:sessionId` — cancel a flow\n * - `DELETE /web/config/providers/:provider/oauth` — sign out (caller only)\n */\nexport class OAuthRoutes extends Route<OAuthRoutesDeps> {\n routes(): ApiRoute[] {\n const { auth, authStorage, modelCredentials } = this.deps;\n const onCredentialsChanged = this.deps.onCredentialsChanged ?? (() => {});\n\n return [\n registerApiRoute('/web/config/providers/:provider/oauth/start', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const flow = OAUTH_FLOWS[provider];\n if (!flow) {\n return c.json({ error: 'oauth_not_supported', message: `Provider does not support web sign-in` }, 404);\n }\n // Body may carry `{ mode }` for future multi-mode providers; each web\n // flow currently has exactly one mode, so an unknown mode is rejected.\n const body = await readJsonBody(loose(c));\n if (typeof body.mode === 'string' && body.mode !== flow.kind) {\n return c.json({ error: 'invalid_mode', message: `Unsupported mode for ${provider}: ${body.mode}` }, 400);\n }\n\n let started: OAuthFlowStart;\n try {\n started = await flow.start();\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 502);\n }\n\n const sessionId = randomUUID();\n const tenant = sessionTenant(ctx);\n await (\n await sessionStore(ctx)\n ).createLoginSession({\n sessionId,\n orgId: tenant.orgId,\n userId: tenant.userId,\n provider,\n kind: flow.kind,\n pending: started.pending,\n expiresAt: new Date(started.expiresAt),\n nextPollAt: started.nextPollMs != null ? new Date(Date.now() + started.nextPollMs) : null,\n });\n\n return c.json({\n sessionId,\n kind: flow.kind,\n url: started.url,\n ...(started.userCode ? { userCode: started.userCode } : {}),\n instructions: started.instructions,\n expiresAt: started.expiresAt,\n ...(started.nextPollMs != null ? { nextPollMs: started.nextPollMs } : {}),\n });\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/oauth/complete', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const flow = OAUTH_FLOWS[provider];\n if (!flow?.complete) return c.json({ error: 'oauth_not_supported' }, 404);\n\n const body = await readJsonBody(loose(c));\n const sessionId = typeof body.sessionId === 'string' ? body.sessionId : '';\n const code = typeof body.code === 'string' ? body.code.trim() : '';\n if (!sessionId || !code) return c.json({ error: 'Missing required fields: sessionId, code' }, 400);\n\n const session = await loadOwnedSession({ ctx, provider, sessionId });\n if (!session) return c.json({ error: 'session_not_found' }, 404);\n if (session.kind !== 'paste-code') return c.json({ error: 'wrong_session_kind' }, 400);\n\n const claimed = await (\n await sessionStore(ctx)\n ).claimLoginSession(sessionId, {\n orgId: session.orgId,\n userId: session.userId,\n provider,\n kind: 'paste-code',\n });\n if (!claimed) return c.json({ error: 'oauth_in_progress' }, 409);\n\n let credentials: OAuthCredentials;\n try {\n credentials = await flow.complete(claimed.pending, code);\n } catch (error) {\n await (await sessionStore(ctx)).touchLoginSession(sessionId, { nextPollAt: null });\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);\n }\n\n await persistOAuthCredential({ ctx, provider, credentials, authStorage, onCredentialsChanged });\n await (await sessionStore(ctx)).deleteLoginSession(sessionId);\n return c.json({ status: 'complete', ok: true });\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/oauth/poll', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const flow = OAUTH_FLOWS[provider];\n if (!flow?.poll) return c.json({ error: 'oauth_not_supported' }, 404);\n\n const body = await readJsonBody(loose(c));\n const sessionId = typeof body.sessionId === 'string' ? body.sessionId : '';\n if (!sessionId) return c.json({ error: 'Missing required field: sessionId' }, 400);\n\n const session = await loadOwnedSession({ ctx, provider, sessionId });\n if (!session) return c.json({ error: 'session_not_found' }, 404);\n if (session.kind !== 'device-code') return c.json({ error: 'wrong_session_kind' }, 400);\n\n // Server-side rate limit: at most one upstream poll per interval,\n // regardless of how eagerly the client calls this route.\n const now = Date.now();\n const nextPollAt = session.nextPollAt?.getTime();\n if (nextPollAt != null && now < nextPollAt) {\n return c.json({ status: 'pending', nextPollMs: nextPollAt - now });\n }\n\n const claimed = await (\n await sessionStore(ctx)\n ).claimLoginSession(sessionId, {\n orgId: session.orgId,\n userId: session.userId,\n provider,\n kind: 'device-code',\n });\n if (!claimed) return c.json({ status: 'pending', nextPollMs: 250 });\n\n let result: OAuthFlowPoll;\n try {\n result = await flow.poll(claimed.pending);\n } catch (error) {\n await (await sessionStore(ctx)).touchLoginSession(sessionId, { nextPollAt: null });\n throw error;\n }\n\n if (result.status === 'complete') {\n await persistOAuthCredential({\n ctx,\n provider,\n credentials: result.credentials,\n authStorage,\n onCredentialsChanged,\n });\n await (await sessionStore(ctx)).deleteLoginSession(sessionId);\n return c.json({ status: 'complete', ok: true });\n }\n\n if (result.status === 'failed') {\n await (await sessionStore(ctx)).deleteLoginSession(sessionId);\n return c.json({ status: 'failed', error: result.error });\n }\n\n await (\n await sessionStore(ctx)\n ).touchLoginSession(sessionId, {\n ...(result.pending ? { pending: result.pending } : {}),\n nextPollAt: new Date(now + result.nextPollMs),\n });\n return c.json({ status: 'pending', nextPollMs: result.nextPollMs });\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/oauth/session/:sessionId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const sessionId = c.req.param('sessionId');\n const session = await loadOwnedSession({ ctx, provider, sessionId });\n if (session) await (await sessionStore(ctx)).deleteLoginSession(sessionId);\n return c.json({ ok: true });\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/oauth', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const authProviderId = getAuthProviderId(provider);\n try {\n if (ctx.mode === 'tenant') {\n // Caller's credential only — never touches org rows or other users.\n await ctx.storage.removeCredential({ orgId: ctx.orgId, userId: ctx.userId }, authProviderId);\n onCredentialsChanged({ orgId: ctx.orgId, userId: ctx.userId });\n } else {\n if (!authStorage) return c.json({ error: 'Credential storage is not available' }, 503);\n authStorage.remove(authProviderId);\n }\n return c.json({ ok: true });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oauth.js","names":[],"sources":["../../src/routes/oauth.ts"],"sourcesContent":["/**\n * Web OAuth sign-in routes for model providers (Settings › Providers).\n *\n * Wraps the SDK's step-based OAuth primitives (start/complete for Anthropic's\n * paste-code PKCE flow, start/poll for the Codex/Copilot/xAI device flows) in\n * HTTP routes. Flow state lives in login sessions — the `model-credentials`\n * domain's `oauth_login_sessions` table in tenant mode (any replica can\n * complete/poll), an in-memory store in local mode — so a flow can span\n * requests. Completed credentials are **user-scoped by default**; org admins\n * can start a flow with `{ scope: 'org' }` to share the credential with the\n * whole org. Local mode writes to the file-backed `AuthStorage`.\n *\n * Tokens never leave the server; responses only carry flow metadata (URLs,\n * user codes, poll delays).\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { nextPollDelayMs } from '@mastra/code-sdk/auth/device-code';\nimport { completeAnthropicLogin, startAnthropicLogin } from '@mastra/code-sdk/auth/providers/anthropic';\nimport {\n copilotNextPollDelayMs,\n pollGitHubCopilotDeviceLogin,\n startGitHubCopilotDeviceLogin,\n} from '@mastra/code-sdk/auth/providers/github-copilot';\nimport type { CopilotDeviceLoginPending } from '@mastra/code-sdk/auth/providers/github-copilot';\nimport { pollCodexDeviceLogin, startCodexDeviceLogin } from '@mastra/code-sdk/auth/providers/openai-codex';\nimport type { CodexDeviceLoginPending } from '@mastra/code-sdk/auth/providers/openai-codex';\nimport { pollXAIDeviceLogin, startXAIDeviceLogin } from '@mastra/code-sdk/auth/providers/xai';\nimport type { XAIDeviceLoginPending } from '@mastra/code-sdk/auth/providers/xai';\nimport type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { OAuthCredentials } from '@mastra/code-sdk/auth/types';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { LoginCredentialScope, LoginSessionKind, LoginSessionRow } from '../storage/domains/credentials/base.js';\nimport { getAuthProviderId, resolveCredentialContext } from './provider-credentials.js';\nimport type { CredentialContext } from './provider-credentials.js';\nimport { Route } from './route.js';\nimport type { RouteDependencies } from './route.js';\n\n/** Lifetime of a paste-code session (Anthropic gives no explicit expiry). */\nconst PASTE_CODE_TTL_MS = 10 * 60 * 1000;\n\ninterface OAuthFlowStart {\n url: string;\n userCode?: string;\n instructions: string;\n /** ms epoch after which the flow expires. */\n expiresAt: number;\n /** Delay before the first upstream poll (device-code flows only). */\n nextPollMs?: number;\n /** Serializable flow state persisted in the login session. */\n pending: Record<string, unknown>;\n}\n\ntype OAuthFlowPoll =\n | { status: 'complete'; credentials: OAuthCredentials }\n | { status: 'pending'; nextPollMs: number; pending?: Record<string, unknown> }\n | { status: 'failed'; error: string };\n\ninterface OAuthFlow {\n kind: LoginSessionKind;\n start(): Promise<OAuthFlowStart>;\n /** Paste-code flows: exchange the pasted code for credentials. Throws on bad input. */\n complete?(pending: Record<string, unknown>, code: string): Promise<OAuthCredentials>;\n /** Device-code flows: perform exactly one upstream poll. */\n poll?(pending: Record<string, unknown>): Promise<OAuthFlowPoll>;\n}\n\n/** Web OAuth flows keyed by *catalog* provider id (mirrors {@link WEB_OAUTH_FLOW_KINDS}). */\nconst OAUTH_FLOWS: Record<string, OAuthFlow> = {\n anthropic: {\n kind: 'paste-code',\n start: async () => {\n const { url, verifier } = await startAnthropicLogin();\n return {\n url,\n instructions: 'Open the link, authorize, then paste the code shown on the final page.',\n expiresAt: Date.now() + PASTE_CODE_TTL_MS,\n pending: { verifier },\n };\n },\n complete: (pending, code) => completeAnthropicLogin(code, String(pending.verifier ?? '')),\n },\n openai: {\n kind: 'device-code',\n start: async () => {\n const p = await startCodexDeviceLogin();\n return {\n url: p.url,\n userCode: p.userCode,\n instructions: p.instructions,\n expiresAt: p.deadlineAt,\n nextPollMs: p.intervalMs,\n pending: { ...p },\n };\n },\n poll: async pending => {\n const r = await pollCodexDeviceLogin(pending as unknown as CodexDeviceLoginPending);\n if (r.status === 'complete') return { status: 'complete', credentials: r.credentials };\n if (r.status === 'pending') return { status: 'pending', nextPollMs: r.nextPollMs };\n return { status: 'failed', error: r.error };\n },\n },\n 'github-copilot': {\n kind: 'device-code',\n start: async () => {\n const p = await startGitHubCopilotDeviceLogin();\n return {\n url: p.url,\n userCode: p.userCode,\n instructions: p.instructions,\n expiresAt: p.deadlineAt,\n nextPollMs: copilotNextPollDelayMs(p),\n pending: { ...p },\n };\n },\n poll: async pending => {\n const p = pending as unknown as CopilotDeviceLoginPending;\n // Web flows are always started against github.com (no Enterprise input).\n // Never let deserialized session state redirect server-side polling to\n // an arbitrary hostname.\n if (p.domain !== 'github.com' || p.enterpriseDomain !== undefined) {\n return { status: 'failed', error: 'Unsupported GitHub host' };\n }\n const r = await pollGitHubCopilotDeviceLogin(p);\n if (r.status === 'complete') return { status: 'complete', credentials: r.credentials };\n if (r.status === 'pending') return { status: 'pending', nextPollMs: r.nextPollMs, pending: { ...r.pending } };\n return { status: 'failed', error: r.error };\n },\n },\n xai: {\n kind: 'device-code',\n start: async () => {\n const p = await startXAIDeviceLogin();\n return {\n url: p.url,\n userCode: p.userCode,\n instructions: p.instructions,\n expiresAt: p.state.deadlineAt,\n nextPollMs: nextPollDelayMs(p.state),\n pending: { ...p },\n };\n },\n poll: async pending => {\n const r = await pollXAIDeviceLogin(pending as unknown as XAIDeviceLoginPending);\n if (r.status === 'complete') return { status: 'complete', credentials: r.credentials };\n if (r.status === 'pending') return { status: 'pending', nextPollMs: r.nextPollMs, pending: { ...r.pending } };\n return { status: 'failed', error: r.error };\n },\n },\n};\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\n/**\n * Local-mode login sessions. Flows in local mode are single-process, so a\n * process-local libsql `:memory:` database (which already handles TTL\n * cleanup) is sufficient.\n */\nlet localSessionsPromise: Promise<ModelCredentialsStorage> | undefined;\nfunction localSessions(): Promise<ModelCredentialsStorage> {\n localSessionsPromise ??= (async () => {\n const { LibSQLFactoryStorage } = await import('@mastra/libsql');\n const storage = new LibSQLFactoryStorage({ id: 'local-oauth-sessions', url: ':memory:' });\n const store = storage.registerDomain(new ModelCredentialsStorage());\n await storage.init();\n return store;\n })();\n return localSessionsPromise;\n}\nconst LOCAL_TENANT = { orgId: 'local', userId: 'local' } as const;\n\nasync function sessionStore(ctx: CredentialContext): Promise<ModelCredentialsStorage> {\n return ctx.mode === 'tenant' ? ctx.storage : localSessions();\n}\n\nfunction sessionTenant(ctx: CredentialContext): { orgId: string; userId: string } {\n return ctx.mode === 'tenant' ? { orgId: ctx.orgId, userId: ctx.userId } : LOCAL_TENANT;\n}\n\n/** Load a session and verify it belongs to the caller + provider (else undefined). */\nasync function loadOwnedSession({\n ctx,\n provider,\n sessionId,\n}: {\n ctx: CredentialContext;\n provider: string;\n sessionId: string;\n}): Promise<LoginSessionRow | undefined> {\n const session = await (await sessionStore(ctx)).getLoginSession(sessionId);\n if (!session) return undefined;\n const tenant = sessionTenant(ctx);\n if (session.orgId !== tenant.orgId || session.userId !== tenant.userId) return undefined;\n if (session.provider !== provider) return undefined;\n return session;\n}\n\n/** Persist completed OAuth credentials — user-scoped unless the flow was started org-wide. */\nasync function persistOAuthCredential({\n ctx,\n provider,\n scope,\n credentials,\n authStorage,\n onCredentialsChanged,\n}: {\n ctx: CredentialContext;\n provider: string;\n scope: LoginCredentialScope | undefined;\n credentials: OAuthCredentials;\n authStorage: AuthStorage | undefined;\n onCredentialsChanged: (tenant: { orgId: string; userId?: string }) => void;\n}): Promise<void> {\n const authProviderId = getAuthProviderId(provider);\n if (ctx.mode === 'tenant') {\n const tenant = { orgId: ctx.orgId, ...(scope === 'org' ? {} : { userId: ctx.userId }) };\n await ctx.storage.setCredential(tenant, authProviderId, {\n type: 'oauth',\n ...credentials,\n });\n onCredentialsChanged(tenant);\n return;\n }\n if (!authStorage) throw new Error('Credential storage is not available');\n authStorage.set(authProviderId, { type: 'oauth', ...credentials });\n}\n\nasync function readJsonBody(c: Context): Promise<Record<string, unknown>> {\n try {\n const body = (await c.req.json()) as unknown;\n return body && typeof body === 'object' ? (body as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nexport interface OAuthRoutesDeps extends RouteDependencies {\n /** File-backed credential store; used in local (no-auth) mode. */\n authStorage?: AuthStorage;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n modelCredentials?: ModelCredentialsStorage;\n /** Notifies the host after tenant credentials change so caches can be dropped. */\n onCredentialsChanged?: (tenant: { orgId: string; userId?: string }) => void;\n}\n\n/**\n * Provider OAuth sign-in routes as Mastra `apiRoutes`:\n * - `POST /web/config/providers/:provider/oauth/start` — begin a sign-in flow\n * - `POST /web/config/providers/:provider/oauth/complete` — paste-code exchange\n * - `POST /web/config/providers/:provider/oauth/poll` — one device-code poll\n * - `DELETE /web/config/providers/:provider/oauth/session/:sessionId` — cancel a flow\n * - `DELETE /web/config/providers/:provider/oauth` — sign out (caller only)\n */\nexport class OAuthRoutes extends Route<OAuthRoutesDeps> {\n routes(): ApiRoute[] {\n const { auth, authStorage, modelCredentials } = this.deps;\n const onCredentialsChanged = this.deps.onCredentialsChanged ?? (() => {});\n\n return [\n registerApiRoute('/web/config/providers/:provider/oauth/start', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const flow = OAUTH_FLOWS[provider];\n if (!flow) {\n return c.json({ error: 'oauth_not_supported', message: `Provider does not support web sign-in` }, 404);\n }\n // Body may carry `{ mode }` for future multi-mode providers; each web\n // flow currently has exactly one mode, so an unknown mode is rejected.\n const body = await readJsonBody(loose(c));\n if (typeof body.mode === 'string' && body.mode !== flow.kind) {\n return c.json({ error: 'invalid_mode', message: `Unsupported mode for ${provider}: ${body.mode}` }, 400);\n }\n\n // Optional `{ scope: 'org' }` shares the completed credential with\n // the whole org (admin-only, tenant mode). Anything else is personal.\n const scope: LoginCredentialScope = body.scope === 'org' ? 'org' : 'user';\n if (scope === 'org' && ctx.mode === 'tenant' && !(await auth.isOrganizationAdmin(loose(c), ctx.orgId))) {\n return c.json({ error: 'forbidden', message: 'Only org admins can sign in for the whole org' }, 403);\n }\n\n let started: OAuthFlowStart;\n try {\n started = await flow.start();\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 502);\n }\n\n const sessionId = randomUUID();\n const tenant = sessionTenant(ctx);\n await (\n await sessionStore(ctx)\n ).createLoginSession({\n sessionId,\n orgId: tenant.orgId,\n userId: tenant.userId,\n provider,\n kind: flow.kind,\n ...(scope === 'org' && ctx.mode === 'tenant' ? { credentialScope: scope } : {}),\n pending: started.pending,\n expiresAt: new Date(started.expiresAt),\n nextPollAt: started.nextPollMs != null ? new Date(Date.now() + started.nextPollMs) : null,\n });\n\n return c.json({\n sessionId,\n kind: flow.kind,\n url: started.url,\n ...(started.userCode ? { userCode: started.userCode } : {}),\n instructions: started.instructions,\n expiresAt: started.expiresAt,\n ...(started.nextPollMs != null ? { nextPollMs: started.nextPollMs } : {}),\n });\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/oauth/complete', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const flow = OAUTH_FLOWS[provider];\n if (!flow?.complete) return c.json({ error: 'oauth_not_supported' }, 404);\n\n const body = await readJsonBody(loose(c));\n const sessionId = typeof body.sessionId === 'string' ? body.sessionId : '';\n const code = typeof body.code === 'string' ? body.code.trim() : '';\n if (!sessionId || !code) return c.json({ error: 'Missing required fields: sessionId, code' }, 400);\n\n const session = await loadOwnedSession({ ctx, provider, sessionId });\n if (!session) return c.json({ error: 'session_not_found' }, 404);\n if (session.kind !== 'paste-code') return c.json({ error: 'wrong_session_kind' }, 400);\n // Org-scoped flows recheck admin access at completion time — the\n // caller may have lost admin since the flow was started.\n if (\n session.credentialScope === 'org' &&\n ctx.mode === 'tenant' &&\n !(await auth.isOrganizationAdmin(loose(c), ctx.orgId))\n ) {\n return c.json({ error: 'forbidden', message: 'Only org admins can sign in for the whole org' }, 403);\n }\n\n const claimed = await (\n await sessionStore(ctx)\n ).claimLoginSession(sessionId, {\n orgId: session.orgId,\n userId: session.userId,\n provider,\n kind: 'paste-code',\n });\n if (!claimed) return c.json({ error: 'oauth_in_progress' }, 409);\n\n let credentials: OAuthCredentials;\n try {\n credentials = await flow.complete(claimed.pending, code);\n } catch (error) {\n await (await sessionStore(ctx)).touchLoginSession(sessionId, { nextPollAt: null });\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);\n }\n\n await persistOAuthCredential({\n ctx,\n provider,\n scope: session.credentialScope,\n credentials,\n authStorage,\n onCredentialsChanged,\n });\n await (await sessionStore(ctx)).deleteLoginSession(sessionId);\n return c.json({ status: 'complete', ok: true });\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/oauth/poll', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const flow = OAUTH_FLOWS[provider];\n if (!flow?.poll) return c.json({ error: 'oauth_not_supported' }, 404);\n\n const body = await readJsonBody(loose(c));\n const sessionId = typeof body.sessionId === 'string' ? body.sessionId : '';\n if (!sessionId) return c.json({ error: 'Missing required field: sessionId' }, 400);\n\n const session = await loadOwnedSession({ ctx, provider, sessionId });\n if (!session) return c.json({ error: 'session_not_found' }, 404);\n if (session.kind !== 'device-code') return c.json({ error: 'wrong_session_kind' }, 400);\n // Org-scoped flows recheck admin access before the credential write —\n // the caller may have lost admin since the flow was started.\n if (\n session.credentialScope === 'org' &&\n ctx.mode === 'tenant' &&\n !(await auth.isOrganizationAdmin(loose(c), ctx.orgId))\n ) {\n return c.json({ error: 'forbidden', message: 'Only org admins can sign in for the whole org' }, 403);\n }\n\n // Server-side rate limit: at most one upstream poll per interval,\n // regardless of how eagerly the client calls this route.\n const now = Date.now();\n const nextPollAt = session.nextPollAt?.getTime();\n if (nextPollAt != null && now < nextPollAt) {\n return c.json({ status: 'pending', nextPollMs: nextPollAt - now });\n }\n\n const claimed = await (\n await sessionStore(ctx)\n ).claimLoginSession(sessionId, {\n orgId: session.orgId,\n userId: session.userId,\n provider,\n kind: 'device-code',\n });\n if (!claimed) return c.json({ status: 'pending', nextPollMs: 250 });\n\n let result: OAuthFlowPoll;\n try {\n result = await flow.poll(claimed.pending);\n } catch (error) {\n await (await sessionStore(ctx)).touchLoginSession(sessionId, { nextPollAt: null });\n throw error;\n }\n\n if (result.status === 'complete') {\n await persistOAuthCredential({\n ctx,\n provider,\n scope: session.credentialScope,\n credentials: result.credentials,\n authStorage,\n onCredentialsChanged,\n });\n await (await sessionStore(ctx)).deleteLoginSession(sessionId);\n return c.json({ status: 'complete', ok: true });\n }\n\n if (result.status === 'failed') {\n await (await sessionStore(ctx)).deleteLoginSession(sessionId);\n return c.json({ status: 'failed', error: result.error });\n }\n\n await (\n await sessionStore(ctx)\n ).touchLoginSession(sessionId, {\n ...(result.pending ? { pending: result.pending } : {}),\n nextPollAt: new Date(now + result.nextPollMs),\n });\n return c.json({ status: 'pending', nextPollMs: result.nextPollMs });\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/oauth/session/:sessionId', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const sessionId = c.req.param('sessionId');\n const session = await loadOwnedSession({ ctx, provider, sessionId });\n if (session) await (await sessionStore(ctx)).deleteLoginSession(sessionId);\n return c.json({ ok: true });\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/oauth', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const authProviderId = getAuthProviderId(provider);\n // `?scope=org` removes the shared org credential (admin-only);\n // default removes the caller's personal credential only.\n const scope = c.req.query('scope') === 'org' ? 'org' : 'user';\n try {\n if (ctx.mode === 'tenant') {\n if (scope === 'org' && !(await auth.isOrganizationAdmin(loose(c), ctx.orgId))) {\n return c.json({ error: 'forbidden', message: 'Only org admins can remove an org-wide sign-in' }, 403);\n }\n const tenant = { orgId: ctx.orgId, ...(scope === 'org' ? {} : { userId: ctx.userId }) };\n await ctx.storage.removeCredential(tenant, authProviderId);\n onCredentialsChanged(tenant);\n } else {\n if (!authStorage) return c.json({ error: 'Credential storage is not available' }, 503);\n authStorage.remove(authProviderId);\n }\n return c.json({ ok: true });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/surface.js
CHANGED
|
@@ -257,6 +257,7 @@ function assembleFactoryApiRoutes(deps) {
|
|
|
257
257
|
modelPacks: deps.domains.modelPacks,
|
|
258
258
|
sourceControlSessions: deps.sourceControlStorage.forIntegration("github").sessions,
|
|
259
259
|
memorySettings: deps.domains.memorySettings,
|
|
260
|
+
factoryProjects: deps.domains.projects,
|
|
260
261
|
customProviders: deps.domains.customProviders,
|
|
261
262
|
features: { knowledge: deps.knowledgeEnabled },
|
|
262
263
|
onCredentialsChanged: invalidateTenantCredentialSnapshots,
|
|
@@ -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 } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\n\nimport type { FactoryIntegration, IntegrationContext } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport type { FactoryRules } from '../rules/types.js';\nimport { isFactoryRuleStage } from '../rules/types.js';\nimport type { BaseCheckpointTriggers } from '../sandbox/base-checkpoint-triggers.js';\nimport type { SandboxFleet } from '../sandbox/fleet.js';\nimport { ensureFactorySourceSession, resolveFactoryDefaultModelId } from '../session/factory-session.js';\nimport { LiveSessions } from '../session/live-sessions.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.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 { ModelPacksStorage } from '../storage/domains/model-packs/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport type { SourceControlStorage } from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.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 { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\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 /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n authStorage: AuthStorage;\n audit: AuditEmitter;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox fleet constructed by the factory (disabled when no machine). */\n fleet: SandboxFleet;\n /** Base-checkpoint trigger surface, when the factory constructed one. */\n baseCheckpoints?: BaseCheckpointTriggers;\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 modelPacks: ModelPacksStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n rules: FactoryRules;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\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\nexport function factoryRuleBranch(item: FactoryBindingPreparationInput['item']): string {\n const metadata = item.metadata ?? {};\n const issueNumber = metadata.githubIssueNumber ?? metadata.number;\n if (\n item.externalSource?.integrationId === 'github' &&\n item.externalSource.type === 'issue' &&\n typeof issueNumber === 'number'\n ) {\n return `factory/issue-${issueNumber}`;\n }\n const pullRequestNumber = metadata.githubPullRequestNumber ?? metadata.number;\n if (\n item.externalSource?.integrationId === 'github' &&\n item.externalSource.type === 'pull-request' &&\n typeof pullRequestNumber === 'number'\n ) {\n return `factory/pr-${pullRequestNumber}`;\n }\n if (item.externalSource?.integrationId === 'linear' && typeof metadata.identifier === 'string') {\n return `factory/linear-${metadata.identifier.toLowerCase()}`;\n }\n throw new Error('Factory skill invocation requires a supported issue or pull request identifier.');\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 github: GithubIntegration,\n coordinator: FactoryStartCoordinator,\n projects: FactoryProjectsStorage,\n input: FactoryBindingPreparationInput,\n): Promise<void> {\n const branch = factoryRuleBranch(input.item);\n const repositorySlug =\n typeof input.item.metadata?.repository === 'string' ? input.item.metadata.repository : undefined;\n const preparedSession = await ensureFactorySourceSession({\n sourceControl: github.sourceControlStorage,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n repositorySlug,\n branch,\n });\n const destinationStage = input.item.stages.length === 1 ? input.item.stages[0] : undefined;\n if (!isFactoryRuleStage(destinationStage))\n throw new Error('Factory skill invocation requires one exclusive board stage.');\n\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),\n threadTitle: `${input.role === 'review' ? 'PR' : 'Issue'}: ${input.item.title}`,\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}\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' | 'publicOrigin' | 'auth' | 'fleet' | 'factoryStorage' | 'integrationStorage' | 'sourceControlStorage'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n rules: FactoryRules;\n factoryReady: boolean;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings'\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 /** Base-checkpoint trigger surface, when the factory constructed one. */\n baseCheckpoints?: BaseCheckpointTriggers;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n fleet: deps.fleet,\n ...(deps.baseCheckpoints ? { baseCheckpoints: deps.baseCheckpoints } : {}),\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 projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n },\n ...(deps.factoryReady ? { rules: { config: deps.rules, workItems: deps.domains.workItems } } : {}),\n ...(deps.emitAudit ? { hooks: { emitAudit: deps.emitAudit } } : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status` and `/web/linear/status` unconditionally, so when an\n * integration is absent (or not ready) the status contract must still hold.\n * Unknown custom ids get no stub — the SPA doesn't 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 fleet: deps.fleet,\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 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/model-pack/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\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 // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear']\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({ rules: deps.rules, storage: deps.domains.workItems }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n githubIntegration?.sourceControlStorage,\n deps.domains.memorySettings,\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n ...(githubIntegration\n ? {\n prepareBinding: (input: FactoryBindingPreparationInput) =>\n prepareFactoryRuleBinding(githubIntegration, startCoordinator, deps.domains.projects, input),\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n fleet: deps.fleet,\n sessions: deps.sourceControlStorage.forIntegration('github').sessions,\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 modelPacks: deps.domains.modelPacks,\n sourceControlSessions: deps.sourceControlStorage.forIntegration('github').sessions,\n memorySettings: deps.domains.memorySettings,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\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 ...integrationRoutes,\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 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 () => deps.factoryStorage?.getMastraStorage().getStore('knowledge'),\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 queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: new LiveSessions(deps.controller),\n }).routes()\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 } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\n\nimport type { FactoryIntegration, IntegrationContext } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport type { FactoryRules } from '../rules/types.js';\nimport { isFactoryRuleStage } from '../rules/types.js';\nimport type { BaseCheckpointTriggers } from '../sandbox/base-checkpoint-triggers.js';\nimport type { SandboxFleet } from '../sandbox/fleet.js';\nimport { ensureFactorySourceSession, resolveFactoryDefaultModelId } from '../session/factory-session.js';\nimport { LiveSessions } from '../session/live-sessions.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.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 { ModelPacksStorage } from '../storage/domains/model-packs/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport type { SourceControlStorage } from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.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 { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\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 /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n authStorage: AuthStorage;\n audit: AuditEmitter;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox fleet constructed by the factory (disabled when no machine). */\n fleet: SandboxFleet;\n /** Base-checkpoint trigger surface, when the factory constructed one. */\n baseCheckpoints?: BaseCheckpointTriggers;\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 modelPacks: ModelPacksStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n rules: FactoryRules;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\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\nexport function factoryRuleBranch(item: FactoryBindingPreparationInput['item']): string {\n const metadata = item.metadata ?? {};\n const issueNumber = metadata.githubIssueNumber ?? metadata.number;\n if (\n item.externalSource?.integrationId === 'github' &&\n item.externalSource.type === 'issue' &&\n typeof issueNumber === 'number'\n ) {\n return `factory/issue-${issueNumber}`;\n }\n const pullRequestNumber = metadata.githubPullRequestNumber ?? metadata.number;\n if (\n item.externalSource?.integrationId === 'github' &&\n item.externalSource.type === 'pull-request' &&\n typeof pullRequestNumber === 'number'\n ) {\n return `factory/pr-${pullRequestNumber}`;\n }\n if (item.externalSource?.integrationId === 'linear' && typeof metadata.identifier === 'string') {\n return `factory/linear-${metadata.identifier.toLowerCase()}`;\n }\n throw new Error('Factory skill invocation requires a supported issue or pull request identifier.');\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 github: GithubIntegration,\n coordinator: FactoryStartCoordinator,\n projects: FactoryProjectsStorage,\n input: FactoryBindingPreparationInput,\n): Promise<void> {\n const branch = factoryRuleBranch(input.item);\n const repositorySlug =\n typeof input.item.metadata?.repository === 'string' ? input.item.metadata.repository : undefined;\n const preparedSession = await ensureFactorySourceSession({\n sourceControl: github.sourceControlStorage,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n repositorySlug,\n branch,\n });\n const destinationStage = input.item.stages.length === 1 ? input.item.stages[0] : undefined;\n if (!isFactoryRuleStage(destinationStage))\n throw new Error('Factory skill invocation requires one exclusive board stage.');\n\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),\n threadTitle: `${input.role === 'review' ? 'PR' : 'Issue'}: ${input.item.title}`,\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}\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' | 'publicOrigin' | 'auth' | 'fleet' | 'factoryStorage' | 'integrationStorage' | 'sourceControlStorage'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n rules: FactoryRules;\n factoryReady: boolean;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings'\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 /** Base-checkpoint trigger surface, when the factory constructed one. */\n baseCheckpoints?: BaseCheckpointTriggers;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n fleet: deps.fleet,\n ...(deps.baseCheckpoints ? { baseCheckpoints: deps.baseCheckpoints } : {}),\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 projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n },\n ...(deps.factoryReady ? { rules: { config: deps.rules, workItems: deps.domains.workItems } } : {}),\n ...(deps.emitAudit ? { hooks: { emitAudit: deps.emitAudit } } : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status` and `/web/linear/status` unconditionally, so when an\n * integration is absent (or not ready) the status contract must still hold.\n * Unknown custom ids get no stub — the SPA doesn't 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 fleet: deps.fleet,\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 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/model-pack/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\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 // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear']\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({ rules: deps.rules, storage: deps.domains.workItems }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n githubIntegration?.sourceControlStorage,\n deps.domains.memorySettings,\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n ...(githubIntegration\n ? {\n prepareBinding: (input: FactoryBindingPreparationInput) =>\n prepareFactoryRuleBinding(githubIntegration, startCoordinator, deps.domains.projects, input),\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n fleet: deps.fleet,\n sessions: deps.sourceControlStorage.forIntegration('github').sessions,\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 modelPacks: deps.domains.modelPacks,\n sourceControlSessions: deps.sourceControlStorage.forIntegration('github').sessions,\n memorySettings: deps.domains.memorySettings,\n factoryProjects: deps.domains.projects,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\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 ...integrationRoutes,\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 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 () => deps.factoryStorage?.getMastraStorage().getStore('knowledge'),\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 queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: new LiveSessions(deps.controller),\n }).routes()\n : []),\n ];\n}\n"],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ import type { RouteAuth } from './route.js';
|
|
|
23
23
|
export declare class TenantCredentialStore implements CredentialStore {
|
|
24
24
|
#private;
|
|
25
25
|
readonly allowEnvironmentFallback = false;
|
|
26
|
-
constructor(orgId: string, userId: string, credentials: ModelCredentialsStorage | undefined);
|
|
26
|
+
constructor(orgId: string, userId: string, credentials: ModelCredentialsStorage | undefined, orgFirst?: boolean);
|
|
27
27
|
/** Hydrate the snapshot when stale; coalesces concurrent callers. */
|
|
28
28
|
ensureFresh(now?: number): Promise<void>;
|
|
29
29
|
/** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-credentials.d.ts","sourceRoot":"","sources":["../../src/routes/tenant-credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,IAAI,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AAG3G,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAG9C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AAEtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAQ5C,qBAAa,qBAAsB,YAAW,eAAe;;IAC3D,QAAQ,CAAC,wBAAwB,SAAS;
|
|
1
|
+
{"version":3,"file":"tenant-credentials.d.ts","sourceRoot":"","sources":["../../src/routes/tenant-credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,IAAI,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AAG3G,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAG9C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AAEtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAQ5C,qBAAa,qBAAsB,YAAW,eAAe;;IAC3D,QAAQ,CAAC,wBAAwB,SAAS;gBAS9B,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,uBAAuB,GAAG,SAAS,EAAE,QAAQ,UAAQ;IAO7G,qEAAqE;IAC/D,WAAW,CAAC,GAAG,SAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BlD,kFAAkF;IAClF,MAAM,IAAI,IAAI;IAMd,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAIjD,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKrD;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAoD/D;AAoBD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,WAAW,EAAE,uBAAuB,GAAG,IAAI,CAE3F;AAED,iEAAiE;AACjE,wBAAgB,qCAAqC,IAAI,IAAI,CAG5D;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CASpG;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,EAC3C,MAAM,EACN,WAAW,GACZ,EAAE;IACD,MAAM,EAAE,mBAAmB,CAAC;IAC5B,WAAW,EAAE,uBAAuB,CAAC;CACtC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhB;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,IAAI,EACJ,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,EAAE,uBAAuB,CAAC;CACtC,GAAG,iBAAiB,CAYpB"}
|
|
@@ -11,14 +11,16 @@ var TenantCredentialStore = class {
|
|
|
11
11
|
allowEnvironmentFallback = false;
|
|
12
12
|
#orgId;
|
|
13
13
|
#userId;
|
|
14
|
+
#orgFirst;
|
|
14
15
|
#credentials;
|
|
15
16
|
#snapshot = /* @__PURE__ */ new Map();
|
|
16
17
|
#fetchedAt = 0;
|
|
17
18
|
#hydrating;
|
|
18
|
-
constructor(orgId, userId, credentials) {
|
|
19
|
+
constructor(orgId, userId, credentials, orgFirst = false) {
|
|
19
20
|
this.#orgId = orgId;
|
|
20
21
|
this.#userId = userId;
|
|
21
22
|
this.#credentials = credentials;
|
|
23
|
+
this.#orgFirst = orgFirst;
|
|
22
24
|
}
|
|
23
25
|
/** Hydrate the snapshot when stale; coalesces concurrent callers. */
|
|
24
26
|
async ensureFresh(now = Date.now()) {
|
|
@@ -33,8 +35,9 @@ var TenantCredentialStore = class {
|
|
|
33
35
|
if (!storage) return;
|
|
34
36
|
const records = await storage.listCredentials(this.#orgId, this.#userId);
|
|
35
37
|
const next = /* @__PURE__ */ new Map();
|
|
36
|
-
|
|
37
|
-
for (const record of records.filter((r) => r.scope ===
|
|
38
|
+
const [under, over] = this.#orgFirst ? ["user", "org"] : ["org", "user"];
|
|
39
|
+
for (const record of records.filter((r) => r.scope === under)) next.set(record.provider, record.credential);
|
|
40
|
+
for (const record of records.filter((r) => r.scope === over)) next.set(record.provider, record.credential);
|
|
38
41
|
this.#snapshot = next;
|
|
39
42
|
this.#fetchedAt = Date.now();
|
|
40
43
|
}
|
|
@@ -63,7 +66,7 @@ var TenantCredentialStore = class {
|
|
|
63
66
|
if (cred?.type === "oauth" && !isOAuthCredentialExpired(cred)) return getOAuthProvider(provider)?.getApiKey(cred);
|
|
64
67
|
return;
|
|
65
68
|
}
|
|
66
|
-
const resolved = await storage.resolveCredential(this.#orgId, this.#userId, provider);
|
|
69
|
+
const resolved = await storage.resolveCredential(this.#orgId, this.#userId, provider, this.#orgFirst ? "org" : "user");
|
|
67
70
|
if (!resolved) {
|
|
68
71
|
this.#snapshot.delete(provider);
|
|
69
72
|
return;
|
|
@@ -93,14 +96,15 @@ var TenantCredentialStore = class {
|
|
|
93
96
|
const tenantStores = /* @__PURE__ */ new Map();
|
|
94
97
|
function storeFor(tenant, credentials) {
|
|
95
98
|
const orgId = tenantOrgId(tenant);
|
|
96
|
-
const
|
|
99
|
+
const orgFirst = tenant.orgFirst === true;
|
|
100
|
+
const key = `${orgId}\u0000${tenant.userId}\u0000${orgFirst ? "org-first" : "user-first"}`;
|
|
97
101
|
let store = tenantStores.get(key);
|
|
98
102
|
if (!store) {
|
|
99
103
|
if (tenantStores.size >= MAX_CACHED_TENANTS) {
|
|
100
104
|
const oldest = tenantStores.keys().next().value;
|
|
101
105
|
if (oldest !== void 0) tenantStores.delete(oldest);
|
|
102
106
|
}
|
|
103
|
-
store = new TenantCredentialStore(orgId, tenant.userId, credentials);
|
|
107
|
+
store = new TenantCredentialStore(orgId, tenant.userId, credentials, orgFirst);
|
|
104
108
|
tenantStores.set(key, store);
|
|
105
109
|
}
|
|
106
110
|
return store;
|
|
@@ -127,7 +131,8 @@ function resetTenantCredentialResolverForTests() {
|
|
|
127
131
|
*/
|
|
128
132
|
function invalidateTenantCredentialSnapshots(tenant) {
|
|
129
133
|
if (tenant.userId) {
|
|
130
|
-
tenantStores.delete(`${tenant.orgId}\u0000${tenant.userId}`);
|
|
134
|
+
tenantStores.delete(`${tenant.orgId}\u0000${tenant.userId}\u0000user-first`);
|
|
135
|
+
tenantStores.delete(`${tenant.orgId}\u0000${tenant.userId}\u0000org-first`);
|
|
131
136
|
return;
|
|
132
137
|
}
|
|
133
138
|
for (const key of tenantStores.keys()) if (key.startsWith(`${tenant.orgId}\u0000`)) tenantStores.delete(key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-credentials.js","names":["#orgId","#userId","#credentials","#fetchedAt","#hydrating","#hydrate","#snapshot"],"sources":["../../src/routes/tenant-credentials.ts"],"sourcesContent":["/**\n * Per-tenant credential store for model resolution (deployed mode).\n *\n * The SDK's `resolveModel` asks the registered `CredentialStoreProvider` for a\n * store synchronously, so this module keeps a small per-tenant **snapshot** of\n * resolved credentials (user rows over org rows) hydrated from the\n * `model-credentials` domain. The snapshot serves the gateway's synchronous\n * path-selection reads (`get` / `getStoredApiKey`); the fetch-time\n * `getApiKey` is authoritative — it re-resolves against the domain and\n * refreshes expired OAuth tokens under the domain's row lock, so a slightly\n * stale snapshot can never send an expired token upstream.\n *\n * Snapshots are primed per request by `createTenantCredentialPrimer` (mounted\n * after the web auth gate) so the first model call of a request already sees\n * the caller's credentials. This store explicitly disables the SDK's\n * environment fallback so server-shell credentials never leak into tenants.\n */\n\nimport type { CredentialTenant as SdkCredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';\nimport { setCredentialStoreProvider } from '@mastra/code-sdk/agents/credential-resolver';\nimport { getOAuthProvider } from '@mastra/code-sdk/auth/storage';\nimport type { AuthCredential, CredentialStore } from '@mastra/code-sdk/auth/types';\nimport type { MiddlewareHandler } from 'hono';\n\nimport { isOAuthCredentialExpired } from '../storage/domains/credentials/base.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport { getTenantCredentialsStorage, tenantOrgId } from './provider-credentials.js';\nimport type { RouteAuth } from './route.js';\n\n/** How long a hydrated snapshot is considered fresh. */\nconst SNAPSHOT_TTL_MS = 15_000;\n\n/** Cap on cached tenant stores; oldest-inserted evicted beyond this. */\nconst MAX_CACHED_TENANTS = 1000;\n\nexport class TenantCredentialStore implements CredentialStore {\n readonly allowEnvironmentFallback = false;\n readonly #orgId: string;\n readonly #userId: string;\n readonly #credentials: ModelCredentialsStorage | undefined;\n #snapshot = new Map<string, AuthCredential>();\n #fetchedAt = 0;\n #hydrating: Promise<void> | undefined;\n\n constructor(orgId: string, userId: string, credentials: ModelCredentialsStorage | undefined) {\n this.#orgId = orgId;\n this.#userId = userId;\n this.#credentials = credentials;\n }\n\n /** Hydrate the snapshot when stale; coalesces concurrent callers. */\n async ensureFresh(now = Date.now()): Promise<void> {\n if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;\n this.#hydrating ??= this.#hydrate().finally(() => {\n this.#hydrating = undefined;\n });\n await this.#hydrating;\n }\n\n async #hydrate(): Promise<void> {\n const storage = await getTenantCredentialsStorage(this.#credentials);\n if (!storage) return; // Keep the last tenant-scoped snapshot.\n const records = await storage.listCredentials(this.#orgId, this.#userId);\n const next = new Map<string, AuthCredential>();\n // Org rows first so user rows overwrite them (user > org precedence).\n for (const record of records.filter(r => r.scope === 'org')) {\n next.set(record.provider, record.credential);\n }\n for (const record of records.filter(r => r.scope === 'user')) {\n next.set(record.provider, record.credential);\n }\n this.#snapshot = next;\n this.#fetchedAt = Date.now();\n }\n\n /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */\n reload(): void {\n if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {\n void this.ensureFresh().catch(() => {});\n }\n }\n\n get(provider: string): AuthCredential | undefined {\n return this.#snapshot.get(provider);\n }\n\n getStoredApiKey(provider: string): string | undefined {\n const cred = this.#snapshot.get(provider);\n return cred?.type === 'api_key' ? cred.key : undefined;\n }\n\n /**\n * Authoritative fetch-time resolution: re-reads the domain (user > org) and\n * refreshes expired OAuth tokens under the domain's row lock. Mirrors\n * `AuthStorage.getApiKey` semantics: `undefined` on missing credential or\n * failed refresh (caller surfaces a re-login error).\n */\n async getApiKey(provider: string): Promise<string | undefined> {\n const storage = await getTenantCredentialsStorage(this.#credentials);\n if (!storage) {\n // Domain unavailable: best effort from the snapshot; expired OAuth\n // tokens cannot be refreshed without the domain's lock.\n const cred = this.#snapshot.get(provider);\n if (cred?.type === 'api_key') return cred.key;\n if (cred?.type === 'oauth' && !isOAuthCredentialExpired(cred)) {\n return getOAuthProvider(provider)?.getApiKey(cred);\n }\n return undefined;\n }\n\n const resolved = await storage.resolveCredential(this.#orgId, this.#userId, provider);\n if (!resolved) {\n this.#snapshot.delete(provider);\n return undefined;\n }\n this.#snapshot.set(provider, resolved.credential);\n\n if (resolved.credential.type === 'api_key') {\n return resolved.credential.key;\n }\n\n const oauthProvider = getOAuthProvider(provider);\n if (!oauthProvider) return undefined;\n\n if (!isOAuthCredentialExpired(resolved.credential)) {\n return oauthProvider.getApiKey(resolved.credential);\n }\n\n // Refresh at the scope the credential actually lives at (OAuth rows are\n // user-scoped by policy, but resolve defensively from the record).\n const rowTenant = resolved.scope === 'user' ? { orgId: this.#orgId, userId: this.#userId } : { orgId: this.#orgId };\n try {\n const refreshed = await storage.refreshOAuth(rowTenant, provider, async current => ({\n type: 'oauth' as const,\n ...(await oauthProvider.refreshToken(current)),\n }));\n if (!refreshed) return undefined;\n this.#snapshot.set(provider, refreshed);\n return oauthProvider.getApiKey(refreshed);\n } catch {\n // Refresh failed — user needs to re-login (same posture as AuthStorage).\n return undefined;\n }\n }\n}\n\nconst tenantStores = new Map<string, TenantCredentialStore>();\n\nfunction storeFor(tenant: SdkCredentialTenant, credentials: ModelCredentialsStorage): TenantCredentialStore {\n const orgId = tenantOrgId(tenant);\n const key = `${orgId}\\u0000${tenant.userId}`;\n let store = tenantStores.get(key);\n if (!store) {\n if (tenantStores.size >= MAX_CACHED_TENANTS) {\n const oldest = tenantStores.keys().next().value;\n if (oldest !== undefined) tenantStores.delete(oldest);\n }\n store = new TenantCredentialStore(orgId, tenant.userId, credentials);\n tenantStores.set(key, store);\n }\n return store;\n}\n\n/**\n * Register the web tenant credential store provider with the SDK. Called by\n * the factory after storage init with the `model-credentials` domain handle;\n * from then on `resolveModel` uses per-tenant credentials and the SDK skips\n * the `loadStoredApiKeysIntoEnv` env side-channel.\n */\nexport function registerTenantCredentialResolver(credentials: ModelCredentialsStorage): void {\n setCredentialStoreProvider(tenant => storeFor(tenant, credentials));\n}\n\n/** Test hook: clear registration and cached tenant snapshots. */\nexport function resetTenantCredentialResolverForTests(): void {\n setCredentialStoreProvider(undefined);\n tenantStores.clear();\n}\n\n/**\n * Drop cached snapshots after a credential write so the change is visible to\n * the next model call immediately instead of after the snapshot TTL. An org\n * write affects every member's resolved view, so all stores under the org are\n * invalidated; a user write only drops that user's store.\n */\nexport function invalidateTenantCredentialSnapshots(tenant: { orgId: string; userId?: string }): void {\n if (tenant.userId) {\n tenantStores.delete(`${tenant.orgId}\\u0000${tenant.userId}`);\n return;\n }\n for (const key of tenantStores.keys()) {\n if (key.startsWith(`${tenant.orgId}\\u0000`)) tenantStores.delete(key);\n }\n}\n\n/**\n * Middleware mounted after the web auth gate: primes the caller's credential\n * snapshot so the request's first model call sees their credentials without an\n * async seam in model resolution. Cheap when fresh (TTL check), best-effort\n * when not — a failed hydrate falls back to env vars, never blocks a request.\n */\nexport async function primeTenantCredentials({\n tenant,\n credentials,\n}: {\n tenant: SdkCredentialTenant;\n credentials: ModelCredentialsStorage;\n}): Promise<void> {\n await storeFor(tenant, credentials).ensureFresh();\n}\n\nexport function createTenantCredentialPrimer({\n auth,\n credentials,\n}: {\n auth: RouteAuth;\n credentials: ModelCredentialsStorage;\n}): MiddlewareHandler {\n return async (c, next) => {\n const tenant = auth.tenant(c);\n if (tenant) {\n try {\n await storeFor(tenant, credentials).ensureFresh();\n } catch {\n // Fail open: model calls fall back to env credentials.\n }\n }\n await next();\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tenant-credentials.js","names":["#orgId","#userId","#orgFirst","#credentials","#fetchedAt","#hydrating","#hydrate","#snapshot"],"sources":["../../src/routes/tenant-credentials.ts"],"sourcesContent":["/**\n * Per-tenant credential store for model resolution (deployed mode).\n *\n * The SDK's `resolveModel` asks the registered `CredentialStoreProvider` for a\n * store synchronously, so this module keeps a small per-tenant **snapshot** of\n * resolved credentials (user rows over org rows) hydrated from the\n * `model-credentials` domain. The snapshot serves the gateway's synchronous\n * path-selection reads (`get` / `getStoredApiKey`); the fetch-time\n * `getApiKey` is authoritative — it re-resolves against the domain and\n * refreshes expired OAuth tokens under the domain's row lock, so a slightly\n * stale snapshot can never send an expired token upstream.\n *\n * Snapshots are primed per request by `createTenantCredentialPrimer` (mounted\n * after the web auth gate) so the first model call of a request already sees\n * the caller's credentials. This store explicitly disables the SDK's\n * environment fallback so server-shell credentials never leak into tenants.\n */\n\nimport type { CredentialTenant as SdkCredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';\nimport { setCredentialStoreProvider } from '@mastra/code-sdk/agents/credential-resolver';\nimport { getOAuthProvider } from '@mastra/code-sdk/auth/storage';\nimport type { AuthCredential, CredentialStore } from '@mastra/code-sdk/auth/types';\nimport type { MiddlewareHandler } from 'hono';\n\nimport { isOAuthCredentialExpired } from '../storage/domains/credentials/base.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport { getTenantCredentialsStorage, tenantOrgId } from './provider-credentials.js';\nimport type { RouteAuth } from './route.js';\n\n/** How long a hydrated snapshot is considered fresh. */\nconst SNAPSHOT_TTL_MS = 15_000;\n\n/** Cap on cached tenant stores; oldest-inserted evicted beyond this. */\nconst MAX_CACHED_TENANTS = 1000;\n\nexport class TenantCredentialStore implements CredentialStore {\n readonly allowEnvironmentFallback = false;\n readonly #orgId: string;\n readonly #userId: string;\n readonly #orgFirst: boolean;\n readonly #credentials: ModelCredentialsStorage | undefined;\n #snapshot = new Map<string, AuthCredential>();\n #fetchedAt = 0;\n #hydrating: Promise<void> | undefined;\n\n constructor(orgId: string, userId: string, credentials: ModelCredentialsStorage | undefined, orgFirst = false) {\n this.#orgId = orgId;\n this.#userId = userId;\n this.#credentials = credentials;\n this.#orgFirst = orgFirst;\n }\n\n /** Hydrate the snapshot when stale; coalesces concurrent callers. */\n async ensureFresh(now = Date.now()): Promise<void> {\n if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;\n this.#hydrating ??= this.#hydrate().finally(() => {\n this.#hydrating = undefined;\n });\n await this.#hydrating;\n }\n\n async #hydrate(): Promise<void> {\n const storage = await getTenantCredentialsStorage(this.#credentials);\n if (!storage) return; // Keep the last tenant-scoped snapshot.\n const records = await storage.listCredentials(this.#orgId, this.#userId);\n const next = new Map<string, AuthCredential>();\n // Lower-precedence rows first so higher-precedence rows overwrite them\n // (user > org normally; org > user for org-first automated runs).\n const [under, over] = this.#orgFirst ? (['user', 'org'] as const) : (['org', 'user'] as const);\n for (const record of records.filter(r => r.scope === under)) {\n next.set(record.provider, record.credential);\n }\n for (const record of records.filter(r => r.scope === over)) {\n next.set(record.provider, record.credential);\n }\n this.#snapshot = next;\n this.#fetchedAt = Date.now();\n }\n\n /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */\n reload(): void {\n if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {\n void this.ensureFresh().catch(() => {});\n }\n }\n\n get(provider: string): AuthCredential | undefined {\n return this.#snapshot.get(provider);\n }\n\n getStoredApiKey(provider: string): string | undefined {\n const cred = this.#snapshot.get(provider);\n return cred?.type === 'api_key' ? cred.key : undefined;\n }\n\n /**\n * Authoritative fetch-time resolution: re-reads the domain (user > org) and\n * refreshes expired OAuth tokens under the domain's row lock. Mirrors\n * `AuthStorage.getApiKey` semantics: `undefined` on missing credential or\n * failed refresh (caller surfaces a re-login error).\n */\n async getApiKey(provider: string): Promise<string | undefined> {\n const storage = await getTenantCredentialsStorage(this.#credentials);\n if (!storage) {\n // Domain unavailable: best effort from the snapshot; expired OAuth\n // tokens cannot be refreshed without the domain's lock.\n const cred = this.#snapshot.get(provider);\n if (cred?.type === 'api_key') return cred.key;\n if (cred?.type === 'oauth' && !isOAuthCredentialExpired(cred)) {\n return getOAuthProvider(provider)?.getApiKey(cred);\n }\n return undefined;\n }\n\n const resolved = await storage.resolveCredential(\n this.#orgId,\n this.#userId,\n provider,\n this.#orgFirst ? 'org' : 'user',\n );\n if (!resolved) {\n this.#snapshot.delete(provider);\n return undefined;\n }\n this.#snapshot.set(provider, resolved.credential);\n\n if (resolved.credential.type === 'api_key') {\n return resolved.credential.key;\n }\n\n const oauthProvider = getOAuthProvider(provider);\n if (!oauthProvider) return undefined;\n\n if (!isOAuthCredentialExpired(resolved.credential)) {\n return oauthProvider.getApiKey(resolved.credential);\n }\n\n // Refresh at the scope the credential actually lives at (OAuth rows are\n // user-scoped by policy, but resolve defensively from the record).\n const rowTenant = resolved.scope === 'user' ? { orgId: this.#orgId, userId: this.#userId } : { orgId: this.#orgId };\n try {\n const refreshed = await storage.refreshOAuth(rowTenant, provider, async current => ({\n type: 'oauth' as const,\n ...(await oauthProvider.refreshToken(current)),\n }));\n if (!refreshed) return undefined;\n this.#snapshot.set(provider, refreshed);\n return oauthProvider.getApiKey(refreshed);\n } catch {\n // Refresh failed — user needs to re-login (same posture as AuthStorage).\n return undefined;\n }\n }\n}\n\nconst tenantStores = new Map<string, TenantCredentialStore>();\n\nfunction storeFor(tenant: SdkCredentialTenant, credentials: ModelCredentialsStorage): TenantCredentialStore {\n const orgId = tenantOrgId(tenant);\n const orgFirst = tenant.orgFirst === true;\n const key = `${orgId}\\u0000${tenant.userId}\\u0000${orgFirst ? 'org-first' : 'user-first'}`;\n let store = tenantStores.get(key);\n if (!store) {\n if (tenantStores.size >= MAX_CACHED_TENANTS) {\n const oldest = tenantStores.keys().next().value;\n if (oldest !== undefined) tenantStores.delete(oldest);\n }\n store = new TenantCredentialStore(orgId, tenant.userId, credentials, orgFirst);\n tenantStores.set(key, store);\n }\n return store;\n}\n\n/**\n * Register the web tenant credential store provider with the SDK. Called by\n * the factory after storage init with the `model-credentials` domain handle;\n * from then on `resolveModel` uses per-tenant credentials and the SDK skips\n * the `loadStoredApiKeysIntoEnv` env side-channel.\n */\nexport function registerTenantCredentialResolver(credentials: ModelCredentialsStorage): void {\n setCredentialStoreProvider(tenant => storeFor(tenant, credentials));\n}\n\n/** Test hook: clear registration and cached tenant snapshots. */\nexport function resetTenantCredentialResolverForTests(): void {\n setCredentialStoreProvider(undefined);\n tenantStores.clear();\n}\n\n/**\n * Drop cached snapshots after a credential write so the change is visible to\n * the next model call immediately instead of after the snapshot TTL. An org\n * write affects every member's resolved view, so all stores under the org are\n * invalidated; a user write only drops that user's store.\n */\nexport function invalidateTenantCredentialSnapshots(tenant: { orgId: string; userId?: string }): void {\n if (tenant.userId) {\n tenantStores.delete(`${tenant.orgId}\\u0000${tenant.userId}\\u0000user-first`);\n tenantStores.delete(`${tenant.orgId}\\u0000${tenant.userId}\\u0000org-first`);\n return;\n }\n for (const key of tenantStores.keys()) {\n if (key.startsWith(`${tenant.orgId}\\u0000`)) tenantStores.delete(key);\n }\n}\n\n/**\n * Middleware mounted after the web auth gate: primes the caller's credential\n * snapshot so the request's first model call sees their credentials without an\n * async seam in model resolution. Cheap when fresh (TTL check), best-effort\n * when not — a failed hydrate falls back to env vars, never blocks a request.\n */\nexport async function primeTenantCredentials({\n tenant,\n credentials,\n}: {\n tenant: SdkCredentialTenant;\n credentials: ModelCredentialsStorage;\n}): Promise<void> {\n await storeFor(tenant, credentials).ensureFresh();\n}\n\nexport function createTenantCredentialPrimer({\n auth,\n credentials,\n}: {\n auth: RouteAuth;\n credentials: ModelCredentialsStorage;\n}): MiddlewareHandler {\n return async (c, next) => {\n const tenant = auth.tenant(c);\n if (tenant) {\n try {\n await storeFor(tenant, credentials).ensureFresh();\n } catch {\n // Fail open: model calls fall back to env credentials.\n }\n }\n await next();\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-coordinator.d.ts","sourceRoot":"","sources":["../../src/rules/start-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACxF,OAAO,KAAK,EAAwB,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAClH,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACnG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1G,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,mBAAmB,CAAC;KAC5B,CAAC;IACF,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;gBAE9D,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC;CAK7E;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,KAAK,iBAAiB,GAAG,eAAe,CAAC,eAAe,CAAC,CAAC;AAqD1D,qBAAa,uBAAuB;;gBAQhC,UAAU,EAAE,iBAAiB,EAC7B,OAAO,EAAE,gBAAgB,EACzB,iBAAiB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,EAChE,aAAa,CAAC,EAAE,0BAA0B,EAC1C,cAAc,CAAC,EAAE,qBAAqB;IASlC,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"start-coordinator.d.ts","sourceRoot":"","sources":["../../src/rules/start-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AACxF,OAAO,KAAK,EAAwB,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAClH,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACnG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1G,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,mBAAmB,CAAC;KAC5B,CAAC;IACF,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;gBAE9D,MAAM,EAAE,OAAO,CAAC,uBAAuB,EAAE;QAAE,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC;CAK7E;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,KAAK,iBAAiB,GAAG,eAAe,CAAC,eAAe,CAAC,CAAC;AAqD1D,qBAAa,uBAAuB;;gBAQhC,UAAU,EAAE,iBAAiB,EAC7B,OAAO,EAAE,gBAAgB,EACzB,iBAAiB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,EAChE,aAAa,CAAC,EAAE,0BAA0B,EAC1C,cAAc,CAAC,EAAE,qBAAqB;IASlC,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,0BAA0B,CAAC;CA4HjF"}
|
|
@@ -70,9 +70,15 @@ var FactoryStartCoordinator = class {
|
|
|
70
70
|
if (!this.#sourceControl) throw new Error("Factory source control storage is unavailable");
|
|
71
71
|
const sourceSession = await resolveSourceSession(this.#sourceControl, request);
|
|
72
72
|
const requestContext = request.requestContext ?? new RequestContext();
|
|
73
|
-
|
|
73
|
+
const existingUser = requestContext.get("user");
|
|
74
|
+
if (existingUser && typeof existingUser === "object") requestContext.set("user", {
|
|
75
|
+
...existingUser,
|
|
76
|
+
orgFirstCredentials: true
|
|
77
|
+
});
|
|
78
|
+
else requestContext.set("user", {
|
|
74
79
|
workosId: request.userId,
|
|
75
|
-
organizationId: request.orgId
|
|
80
|
+
organizationId: request.orgId,
|
|
81
|
+
orgFirstCredentials: true
|
|
76
82
|
});
|
|
77
83
|
const untrustedCheckout = request.workItem.input.externalSource?.type === "pull-request" || request.invocation?.type === "skill" && (request.invocation.skillName === "factory-review" || request.invocation.skillName === "factory-rereview");
|
|
78
84
|
const metadataBaseBranch = request.workItem.input.metadata?.baseBranch;
|
|
@@ -99,7 +105,7 @@ var FactoryStartCoordinator = class {
|
|
|
99
105
|
});
|
|
100
106
|
await hydrateFactorySession(session, {
|
|
101
107
|
orgId: request.orgId,
|
|
102
|
-
|
|
108
|
+
factoryProjectId: request.factoryProjectId,
|
|
103
109
|
defaultModelId: request.defaultModelId,
|
|
104
110
|
memorySettings: this.#memorySettings
|
|
105
111
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-coordinator.js","names":["#controller","#storage","#transitionService","#sourceControl","#memorySettings"],"sources":["../../src/rules/start-coordinator.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\nimport { formatSkillActivation } from '@mastra/core/workspace';\n\nimport { hydrateFactorySession } from '../session/factory-session.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { CreateWorkItemInput, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryRuleStage, FactoryTransitionResult } from './types.js';\n\nexport interface FactoryStartRequest {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sessionId: string;\n threadTitle: string;\n threadTags?: Record<string, string>;\n kickoffKey: string;\n invocation?: { type: 'prompt'; prompt: string } | { type: 'skill'; skillName: string; arguments: string };\n destinationStage: FactoryRuleStage;\n defaultModelId?: string;\n workItem: {\n id?: string;\n role: string;\n input: CreateWorkItemInput;\n };\n requestContext?: RequestContext;\n /** Arm the item's autonomy in the same transaction that prepares the run. */\n armAutonomy?: boolean;\n}\n\nexport class FactoryStartTransitionError extends Error {\n readonly result: Extract<FactoryTransitionResult, { status: 'rejected' }>;\n\n constructor(result: Extract<FactoryTransitionResult, { status: 'rejected' }>) {\n super(result.reason);\n this.name = 'FactoryStartTransitionError';\n this.result = result;\n }\n}\n\nexport interface FactoryStartPreparedResult {\n workItemId: string;\n bindingId: string;\n threadId: string;\n resourceId: string;\n sessionId: string;\n branch: string;\n revision: number;\n kickoffStatus: 'pending' | 'leased' | 'retry' | 'sent' | 'failed';\n replayed: boolean;\n}\n\ntype FactoryController = AgentController<MastraCodeState>;\ntype FactorySession = Awaited<ReturnType<FactoryController['createSession']>>;\n\nfunction escapeSkillBoundary(value: string): string {\n return value.replaceAll('</skill>', '</skill>');\n}\n\nasync function resolveKickoffMessage(\n session: FactorySession,\n invocation: FactoryStartRequest['invocation'],\n): Promise<string | null> {\n if (!invocation) return null;\n if (invocation.type === 'prompt') return invocation.prompt;\n\n const skills = session.getWorkspace()?.skills;\n await skills?.maybeRefresh();\n const skill = await skills?.get(invocation.skillName);\n if (!skill || skill['user-invocable'] === false) {\n throw new Error(`Skill not found: ${invocation.skillName}.`);\n }\n const args = invocation.arguments.trim();\n const content = `${formatSkillActivation(skill)}${args ? `\\n\\nARGUMENTS: ${args}` : ''}`.trim();\n return `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`;\n}\n\nasync function resolveSourceSession(\n storage: SourceControlStorageHandle,\n request: FactoryStartRequest,\n): Promise<SourceControlSession> {\n const session = await storage.sessions.getBySessionId(request.sessionId);\n if (!session || session.orgId !== request.orgId || session.userId !== request.userId) {\n throw new Error('Factory session not found');\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: request.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Factory session repository not found');\n const connection = await storage.connections.get({ orgId: request.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== request.factoryProjectId) {\n throw new Error('Factory session does not belong to this project');\n }\n return session;\n}\n\nasync function configureThread(session: FactorySession, request: FactoryStartRequest): Promise<string> {\n const threadId = session.thread.requireId();\n await session.thread.rename({ title: request.threadTitle });\n const settings = { ...(request.threadTags ?? {}), factorySessionId: request.sessionId };\n await Promise.all(Object.entries(settings).map(([key, value]) => session.thread.setSetting({ key, value })));\n return threadId;\n}\n\nexport class FactoryStartCoordinator {\n readonly #controller: FactoryController;\n readonly #storage: WorkItemsStorage;\n readonly #transitionService?: Pick<FactoryTransitionService, 'transition'>;\n readonly #sourceControl?: SourceControlStorageHandle;\n readonly #memorySettings?: MemorySettingsStorage;\n\n constructor(\n controller: FactoryController,\n storage: WorkItemsStorage,\n transitionService?: Pick<FactoryTransitionService, 'transition'>,\n sourceControl?: SourceControlStorageHandle,\n memorySettings?: MemorySettingsStorage,\n ) {\n this.#controller = controller;\n this.#storage = storage;\n this.#transitionService = transitionService;\n this.#sourceControl = sourceControl;\n this.#memorySettings = memorySettings;\n }\n\n async prepare(request: FactoryStartRequest): Promise<FactoryStartPreparedResult> {\n const storage = this.#storage;\n if (!this.#sourceControl) throw new Error('Factory source control storage is unavailable');\n const sourceSession = await resolveSourceSession(this.#sourceControl, request);\n const requestContext = request.requestContext ?? new RequestContext();\n if (!requestContext.get('user')) {\n requestContext.set('user', { workosId: request.userId, organizationId: request.orgId });\n }\n // Sessions kicked off against third-party content (a PR under review, or\n // any pull-request-sourced work item) get `untrustedCheckout` so the SDK\n // never ingests the checkout's AGENTS.md/CLAUDE.md into the system prompt\n // or reminders — those files are attacker-writable in a PR branch.\n const untrustedCheckout =\n request.workItem.input.externalSource?.type === 'pull-request' ||\n (request.invocation?.type === 'skill' &&\n (request.invocation.skillName === 'factory-review' || request.invocation.skillName === 'factory-rereview'));\n // The trusted ref the SDK may serve project instruction files from on an\n // untrusted checkout (the PR's base branch). Prefer the session record's\n // base branch; fall back to the intake metadata captured from the PR.\n const metadataBaseBranch = request.workItem.input.metadata?.baseBranch;\n const baseRef =\n (sourceSession.baseBranch || undefined) ??\n (typeof metadataBaseBranch === 'string' && metadataBaseBranch ? metadataBaseBranch : undefined);\n const sessionTags = {\n factoryProjectId: request.factoryProjectId,\n projectRepositoryId: sourceSession.projectRepositoryId,\n };\n const session = await this.#controller.createSession({\n id: sourceSession.sessionId,\n ownerId: request.userId,\n resourceId: sourceSession.sessionId,\n threadId: sourceSession.sessionId,\n requestContext,\n tags: sessionTags,\n });\n // Bound-agent authority gates (the transition tool, the factory-phase\n // processor, workspace token selection) resolve the session address from\n // controller state. Seed it server-side — `tags` covers fresh creation,\n // the explicit setState covers get-or-create returning a session another\n // caller created without them — so autonomous runs never depend on a\n // browser connecting to populate the state. `untrustedCheckout` is a\n // boolean so it rides only on state (tags are string-valued).\n await session.state.set({\n ...sessionTags,\n // The authoritative org id for every downstream identity read (the\n // memory seam's organizationId): the session owner is a USER id, not an\n // org, so it must never be improvised from ownerId.\n factoryOrgId: request.orgId,\n ...(untrustedCheckout ? { untrustedCheckout: true, ...(baseRef ? { baseRef } : {}) } : {}),\n });\n await hydrateFactorySession(session, {\n orgId: request.orgId,\n userId: request.userId,\n defaultModelId: request.defaultModelId,\n memorySettings: this.#memorySettings,\n });\n const threadId = await configureThread(session, request);\n const kickoffMessage = await resolveKickoffMessage(session, request.invocation);\n const prepared = await storage.prepareRunStart({\n orgId: request.orgId,\n userId: request.userId,\n factoryProjectId: request.factoryProjectId,\n workItem: { id: request.workItem.id, input: request.workItem.input },\n role: request.workItem.role,\n session: { sessionId: sourceSession.sessionId, branch: sourceSession.branch, threadId },\n resourceId: sourceSession.sessionId,\n kickoffKey: request.kickoffKey,\n kickoffMessage,\n armAutonomy: request.armAutonomy === true,\n });\n await session.thread.setSetting({ key: 'factoryWorkItemId', value: prepared.item.id });\n\n let revision = prepared.item.revision;\n if (prepared.item.stages.length !== 1 || prepared.item.stages[0] !== request.destinationStage) {\n if (!this.#transitionService) throw new Error('Factory transition service is unavailable.');\n const transition = await this.#transitionService.transition({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: prepared.item.id,\n board: prepared.item.externalSource?.type === 'pull-request' ? 'review' : 'work',\n stage: request.destinationStage,\n expectedRevision: prepared.item.revision,\n actor: { type: 'human', id: request.userId },\n ingress: { type: 'human', identity: `start:${request.kickoffKey}:transition` },\n cause: 'run_start',\n });\n if (transition.status === 'rejected') {\n await storage.markPendingStart(prepared.binding.id, 'failed', transition.reason);\n throw new FactoryStartTransitionError(transition);\n }\n revision = transition.revision;\n }\n\n if (kickoffMessage === null) {\n await storage.markPendingStart(prepared.binding.id, 'sent');\n prepared.pendingStart.status = 'sent';\n }\n\n return {\n workItemId: prepared.item.id,\n bindingId: prepared.binding.id,\n threadId,\n resourceId: sourceSession.sessionId,\n sessionId: sourceSession.sessionId,\n branch: sourceSession.branch,\n revision,\n kickoffStatus: prepared.pendingStart.status,\n replayed: prepared.replayed,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"start-coordinator.js","names":["#controller","#storage","#transitionService","#sourceControl","#memorySettings"],"sources":["../../src/rules/start-coordinator.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport { RequestContext } from '@mastra/core/request-context';\nimport { formatSkillActivation } from '@mastra/core/workspace';\n\nimport { hydrateFactorySession } from '../session/factory-session.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { SourceControlSession, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { CreateWorkItemInput, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { FactoryTransitionService } from './transition-service.js';\nimport type { FactoryRuleStage, FactoryTransitionResult } from './types.js';\n\nexport interface FactoryStartRequest {\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sessionId: string;\n threadTitle: string;\n threadTags?: Record<string, string>;\n kickoffKey: string;\n invocation?: { type: 'prompt'; prompt: string } | { type: 'skill'; skillName: string; arguments: string };\n destinationStage: FactoryRuleStage;\n defaultModelId?: string;\n workItem: {\n id?: string;\n role: string;\n input: CreateWorkItemInput;\n };\n requestContext?: RequestContext;\n /** Arm the item's autonomy in the same transaction that prepares the run. */\n armAutonomy?: boolean;\n}\n\nexport class FactoryStartTransitionError extends Error {\n readonly result: Extract<FactoryTransitionResult, { status: 'rejected' }>;\n\n constructor(result: Extract<FactoryTransitionResult, { status: 'rejected' }>) {\n super(result.reason);\n this.name = 'FactoryStartTransitionError';\n this.result = result;\n }\n}\n\nexport interface FactoryStartPreparedResult {\n workItemId: string;\n bindingId: string;\n threadId: string;\n resourceId: string;\n sessionId: string;\n branch: string;\n revision: number;\n kickoffStatus: 'pending' | 'leased' | 'retry' | 'sent' | 'failed';\n replayed: boolean;\n}\n\ntype FactoryController = AgentController<MastraCodeState>;\ntype FactorySession = Awaited<ReturnType<FactoryController['createSession']>>;\n\nfunction escapeSkillBoundary(value: string): string {\n return value.replaceAll('</skill>', '</skill>');\n}\n\nasync function resolveKickoffMessage(\n session: FactorySession,\n invocation: FactoryStartRequest['invocation'],\n): Promise<string | null> {\n if (!invocation) return null;\n if (invocation.type === 'prompt') return invocation.prompt;\n\n const skills = session.getWorkspace()?.skills;\n await skills?.maybeRefresh();\n const skill = await skills?.get(invocation.skillName);\n if (!skill || skill['user-invocable'] === false) {\n throw new Error(`Skill not found: ${invocation.skillName}.`);\n }\n const args = invocation.arguments.trim();\n const content = `${formatSkillActivation(skill)}${args ? `\\n\\nARGUMENTS: ${args}` : ''}`.trim();\n return `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`;\n}\n\nasync function resolveSourceSession(\n storage: SourceControlStorageHandle,\n request: FactoryStartRequest,\n): Promise<SourceControlSession> {\n const session = await storage.sessions.getBySessionId(request.sessionId);\n if (!session || session.orgId !== request.orgId || session.userId !== request.userId) {\n throw new Error('Factory session not found');\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: request.orgId,\n id: session.projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Factory session repository not found');\n const connection = await storage.connections.get({ orgId: request.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== request.factoryProjectId) {\n throw new Error('Factory session does not belong to this project');\n }\n return session;\n}\n\nasync function configureThread(session: FactorySession, request: FactoryStartRequest): Promise<string> {\n const threadId = session.thread.requireId();\n await session.thread.rename({ title: request.threadTitle });\n const settings = { ...(request.threadTags ?? {}), factorySessionId: request.sessionId };\n await Promise.all(Object.entries(settings).map(([key, value]) => session.thread.setSetting({ key, value })));\n return threadId;\n}\n\nexport class FactoryStartCoordinator {\n readonly #controller: FactoryController;\n readonly #storage: WorkItemsStorage;\n readonly #transitionService?: Pick<FactoryTransitionService, 'transition'>;\n readonly #sourceControl?: SourceControlStorageHandle;\n readonly #memorySettings?: MemorySettingsStorage;\n\n constructor(\n controller: FactoryController,\n storage: WorkItemsStorage,\n transitionService?: Pick<FactoryTransitionService, 'transition'>,\n sourceControl?: SourceControlStorageHandle,\n memorySettings?: MemorySettingsStorage,\n ) {\n this.#controller = controller;\n this.#storage = storage;\n this.#transitionService = transitionService;\n this.#sourceControl = sourceControl;\n this.#memorySettings = memorySettings;\n }\n\n async prepare(request: FactoryStartRequest): Promise<FactoryStartPreparedResult> {\n const storage = this.#storage;\n if (!this.#sourceControl) throw new Error('Factory source control storage is unavailable');\n const sourceSession = await resolveSourceSession(this.#sourceControl, request);\n const requestContext = request.requestContext ?? new RequestContext();\n // Factory runs resolve model credentials org > user: the org's shared keys\n // win, with the acting user's personal credentials as a fallback — a board\n // run should never silently prefer whoever kicked it off. The flag rides\n // the stashed user even when a caller-provided context already has one.\n const existingUser = requestContext.get('user');\n if (existingUser && typeof existingUser === 'object') {\n requestContext.set('user', { ...existingUser, orgFirstCredentials: true });\n } else {\n requestContext.set('user', {\n workosId: request.userId,\n organizationId: request.orgId,\n orgFirstCredentials: true,\n });\n }\n // Sessions kicked off against third-party content (a PR under review, or\n // any pull-request-sourced work item) get `untrustedCheckout` so the SDK\n // never ingests the checkout's AGENTS.md/CLAUDE.md into the system prompt\n // or reminders — those files are attacker-writable in a PR branch.\n const untrustedCheckout =\n request.workItem.input.externalSource?.type === 'pull-request' ||\n (request.invocation?.type === 'skill' &&\n (request.invocation.skillName === 'factory-review' || request.invocation.skillName === 'factory-rereview'));\n // The trusted ref the SDK may serve project instruction files from on an\n // untrusted checkout (the PR's base branch). Prefer the session record's\n // base branch; fall back to the intake metadata captured from the PR.\n const metadataBaseBranch = request.workItem.input.metadata?.baseBranch;\n const baseRef =\n (sourceSession.baseBranch || undefined) ??\n (typeof metadataBaseBranch === 'string' && metadataBaseBranch ? metadataBaseBranch : undefined);\n const sessionTags = {\n factoryProjectId: request.factoryProjectId,\n projectRepositoryId: sourceSession.projectRepositoryId,\n };\n const session = await this.#controller.createSession({\n id: sourceSession.sessionId,\n ownerId: request.userId,\n resourceId: sourceSession.sessionId,\n threadId: sourceSession.sessionId,\n requestContext,\n tags: sessionTags,\n });\n // Bound-agent authority gates (the transition tool, the factory-phase\n // processor, workspace token selection) resolve the session address from\n // controller state. Seed it server-side — `tags` covers fresh creation,\n // the explicit setState covers get-or-create returning a session another\n // caller created without them — so autonomous runs never depend on a\n // browser connecting to populate the state. `untrustedCheckout` is a\n // boolean so it rides only on state (tags are string-valued).\n await session.state.set({\n ...sessionTags,\n // The authoritative org id for every downstream identity read (the\n // memory seam's organizationId): the session owner is a USER id, not an\n // org, so it must never be improvised from ownerId.\n factoryOrgId: request.orgId,\n ...(untrustedCheckout ? { untrustedCheckout: true, ...(baseRef ? { baseRef } : {}) } : {}),\n });\n // Board runs are org-shared: hydrate with the factory's default model and\n // the project's shared memory settings (falling back to the built-in\n // defaults), never any individual user's stored settings.\n await hydrateFactorySession(session, {\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n defaultModelId: request.defaultModelId,\n memorySettings: this.#memorySettings,\n });\n const threadId = await configureThread(session, request);\n const kickoffMessage = await resolveKickoffMessage(session, request.invocation);\n const prepared = await storage.prepareRunStart({\n orgId: request.orgId,\n userId: request.userId,\n factoryProjectId: request.factoryProjectId,\n workItem: { id: request.workItem.id, input: request.workItem.input },\n role: request.workItem.role,\n session: { sessionId: sourceSession.sessionId, branch: sourceSession.branch, threadId },\n resourceId: sourceSession.sessionId,\n kickoffKey: request.kickoffKey,\n kickoffMessage,\n armAutonomy: request.armAutonomy === true,\n });\n await session.thread.setSetting({ key: 'factoryWorkItemId', value: prepared.item.id });\n\n let revision = prepared.item.revision;\n if (prepared.item.stages.length !== 1 || prepared.item.stages[0] !== request.destinationStage) {\n if (!this.#transitionService) throw new Error('Factory transition service is unavailable.');\n const transition = await this.#transitionService.transition({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: prepared.item.id,\n board: prepared.item.externalSource?.type === 'pull-request' ? 'review' : 'work',\n stage: request.destinationStage,\n expectedRevision: prepared.item.revision,\n actor: { type: 'human', id: request.userId },\n ingress: { type: 'human', identity: `start:${request.kickoffKey}:transition` },\n cause: 'run_start',\n });\n if (transition.status === 'rejected') {\n await storage.markPendingStart(prepared.binding.id, 'failed', transition.reason);\n throw new FactoryStartTransitionError(transition);\n }\n revision = transition.revision;\n }\n\n if (kickoffMessage === null) {\n await storage.markPendingStart(prepared.binding.id, 'sent');\n prepared.pendingStart.status = 'sent';\n }\n\n return {\n workItemId: prepared.item.id,\n bindingId: prepared.binding.id,\n threadId,\n resourceId: sourceSession.sessionId,\n sessionId: sourceSession.sessionId,\n branch: sourceSession.branch,\n revision,\n kickoffStatus: prepared.pendingStart.status,\n replayed: prepared.replayed,\n };\n }\n}\n"],"mappings":"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"}
|