@mastra/factory 0.16.1-alpha.0 → 0.17.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -356,6 +356,42 @@ const github = new PlatformGithubIntegration({
356
356
 
357
357
  Pass the integration in `MastraFactory`'s `integrations` array. The direct `GithubIntegration` accepts the same `rules` option alongside its GitHub App credentials. A function replaces one default handler without composing with it. `null` disables that event's handler, not authentication, webhook ingestion, or reconciliation bookkeeping. Omitted events and `undefined` retain their defaults. Each instance copies and freezes its resolved handler map; unknown event names and invalid handler values are rejected during construction.
358
358
 
359
+ When Platform credentials are present, `MastraFactory` installs a `PlatformGithubIntegration` itself, so a deploy can change its handlers without constructing one. Pass the same options under `platform.github` and they are forwarded to that integration's constructor:
360
+
361
+ ```typescript
362
+ import { MastraFactory } from '@mastra/factory';
363
+
364
+ // Your own label → board mapping (Settings › Intake › GitHub label routes remain
365
+ // the stored, per-project alternative when labels alone are enough).
366
+ const boardForLabels = (labels: string[]) => (labels.includes('design') ? 'design' : 'work');
367
+
368
+ new MastraFactory({
369
+ storage,
370
+ platform: {
371
+ github: {
372
+ rules: {
373
+ // Route a new issue to the board its existing labels select, instead of
374
+ // defaulting to Work.
375
+ issueOpened: context => ({
376
+ type: 'upsertLinkedWorkItem',
377
+ idempotencyKey: `${context.ingress.id}:issue-intake`,
378
+ board: boardForLabels(context.issue?.labels ?? []),
379
+ source: 'github-issue',
380
+ sourceKey: `github-issue:${context.issue!.number}`,
381
+ title: context.issue!.title,
382
+ url: context.issue!.url,
383
+ stage: 'intake',
384
+ }),
385
+ },
386
+ // Optional. Overrides the sibling `githubAppSlug` for recognizing Factory's own writes.
387
+ slug: 'factory-app',
388
+ },
389
+ },
390
+ });
391
+ ```
392
+
393
+ `platform.github` applies only to the integration the factory installs itself. An explicit `github` entry in `integrations` takes precedence and makes the key a no-op; the factory warns rather than ignoring it silently, and warns the same way when no Platform credentials and no explicit GitHub integration are present. `GithubRuleOverrides` (the `rules` option type) is exported from `@mastra/factory`.
394
+
359
395
  **Migration:** Move each global `rules.github[event].onEvent` value to the integration constructor's `rules[event]` option:
360
396
 
361
397
  ```typescript
package/dist/factory.d.ts CHANGED
@@ -25,6 +25,7 @@ import type { FactoryStorage } from '@mastra/core/storage';
25
25
  import type { MastraVector } from '@mastra/core/vector';
26
26
  import type { InstalledBoard } from './boards/index.js';
27
27
  import type { FactoryIntegration } from './integrations/base.js';
28
+ import type { GithubRuleOverrides } from './integrations/github/default-rules.js';
28
29
  import type { MastraFactorySandboxConfig } from './sandbox/session-sandbox.js';
29
30
  import type { FactorySecretEncryption } from './secret-encryption.js';
30
31
  import type { FactorySandboxStart } from './workspace.js';
@@ -129,11 +130,27 @@ export interface MastraFactoryConfig {
129
130
  /** Whether the built-in Work and Review boards are installed. Default: true. */
130
131
  includeDefaultBoards?: boolean;
131
132
  /**
132
- * Platform-specific overrides. `githubAppSlug` identifies Factory's own
133
- * GitHub App writes so their webhook deliveries do not retrigger triage.
133
+ * Platform-specific overrides.
134
134
  */
135
135
  platform?: {
136
+ /** Identifies Factory's own GitHub App writes so their webhook deliveries do not retrigger triage. */
136
137
  githubAppSlug?: string;
138
+ /**
139
+ * Overrides for the `PlatformGithubIntegration` the factory installs itself
140
+ * when Platform credentials are present; these options are forwarded to its
141
+ * constructor. An explicit integration with id `github` in `integrations`
142
+ * takes precedence and makes this key a no-op, so the two are not meant to
143
+ * be used together.
144
+ */
145
+ github?: {
146
+ /** Replace an event handler, or disable it with null; omitted events keep defaults. */
147
+ rules?: GithubRuleOverrides;
148
+ /**
149
+ * GitHub App slug used to recognize Factory's own webhook writes. Falls
150
+ * back to `githubAppSlug` when omitted.
151
+ */
152
+ slug?: string;
153
+ };
137
154
  };
138
155
  }
139
156
  export type { MastraFactorySandboxConfig } from './sandbox/session-sandbox.js';
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAGlD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAWxD,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEvE,OAAO,KAAK,EAAE,kBAAkB,EAAgD,MAAM,wBAAwB,CAAC;AAkC/G,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE/E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AA0CtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAI1D,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9E,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;;;OAcG;IACH,IAAI,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClC;;;;;;;OAOG;IACH,OAAO,EAAE,cAAc,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC;;;;OAIG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,mDAAmD;IACnD,UAAU,CAAC,EAAE,6BAA6B,CAAC;IAC3C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACpC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC,gFAAgF;IAChF,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B;;;OAGG;IACH,QAAQ,CAAC,EAAE;QACT,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,YAAY,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC/E,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAmFD,qBAAa,aAAa;;gBASZ,MAAM,EAAE,mBAAmB;IAsBvC;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC;IA+/BpC;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAY/B,iFAAiF;IAC3E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAGhC"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"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"}
package/dist/factory.js CHANGED
@@ -32,6 +32,7 @@ import { AuditDomain } from "./storage/domains/audit/domain.js";
32
32
  import { hydrateSessionModelPack } from "./session/model-pack-hydration.js";
33
33
  import { observeSessionFirstExec } from "./session/first-exec-capture.js";
34
34
  import { observeSessionFilesystem } from "./session/filesystem-capture.js";
35
+ import { resolveFactorySessionAddress } from "./rules/binding-context.js";
35
36
  import { hydrateSupervisorSession, parseSupervisorResourceId, resolveSupervisorScope } from "./supervisor/session.js";
36
37
  import { FactoryWorkspaceRegistry, createWorkspaceFactory } from "./workspace.js";
37
38
  import { QueueHealthStorage } from "./storage/domains/queue-health/base.js";
@@ -195,13 +196,22 @@ var MastraFactory = class {
195
196
  const secretEncryption = this.#config.secretEncryption ?? createPlaintextFactorySecretEncryption();
196
197
  const routeAuth = createFactoryRouteAuth(auth);
197
198
  const integrations = [...this.#config.integrations ?? []];
199
+ const explicitGithub = integrations.some((integration) => integration.id === "github");
200
+ let installedPlatformGithub = false;
198
201
  if (hasPlatformCredentials()) {
199
- if (!integrations.some((integration) => integration.id === "github")) integrations.push(new PlatformGithubIntegration({ slug: this.#config.platform?.githubAppSlug }));
202
+ if (!explicitGithub) {
203
+ integrations.push(new PlatformGithubIntegration({
204
+ rules: this.#config.platform?.github?.rules,
205
+ slug: this.#config.platform?.github?.slug ?? this.#config.platform?.githubAppSlug
206
+ }));
207
+ installedPlatformGithub = true;
208
+ }
200
209
  if (!integrations.some((integration) => integration.id === "incidentio")) integrations.push(new PlatformIncidentioIntegration());
201
210
  if (!integrations.some((integration) => integration.id === "jira")) integrations.push(new PlatformJiraIntegration());
202
211
  if (!integrations.some((integration) => integration.id === "gitlab")) integrations.push(new PlatformGitLabIntegration());
203
212
  if (!integrations.some((integration) => integration.id === "linear")) integrations.push(new PlatformLinearIntegration());
204
213
  }
214
+ if (this.#config.platform?.github && !installedPlatformGithub) console.warn("[factory] 'platform.github' config was provided but the factory installed no GitHub integration of its own " + (explicitGithub ? "(an explicit integration with id 'github' takes precedence, so these options were ignored)." : "(no Platform credentials are configured and no explicit GitHub integration was passed)."));
205
215
  const integrationIds = /* @__PURE__ */ new Set();
206
216
  for (const integration of integrations) {
207
217
  if (integrationIds.has(integration.id)) throw new Error(`MastraFactory: duplicate integration id '${integration.id}' in 'integrations'.`);
@@ -465,8 +475,24 @@ var MastraFactory = class {
465
475
  }),
466
476
  disableGithubSignals: true,
467
477
  disableSettingsOmSeed: true,
468
- hostInstructions: ({ requestContext }) => {
469
- return parseSupervisorResourceId(requestContext.get("controller")?.resourceId) ? SUPERVISOR_INSTRUCTIONS : void 0;
478
+ hostInstructions: async ({ requestContext }) => {
479
+ const context = requestContext.get("controller");
480
+ if (parseSupervisorResourceId(context?.resourceId)) return SUPERVISOR_INSTRUCTIONS;
481
+ if (context?.threadId && context.resourceId) {
482
+ const current = context.getState();
483
+ if (!(Boolean(current.factoryProjectId) && Boolean(current.factoryOrgId) && typeof current.untrustedCheckout === "boolean")) {
484
+ const recovered = await resolveFactorySessionAddress({
485
+ requestContext,
486
+ storage: workItemsStorage,
487
+ forceBindingLookup: true,
488
+ ...storage.isDomainReady("source-control") ? { sessions: sourceControlSessions } : {}
489
+ });
490
+ if (recovered?.binding) {
491
+ const state = context.getState();
492
+ if (state.factoryProjectId !== recovered.binding.factoryProjectId || recovered.binding.role === "review" && state.untrustedCheckout !== true) throw new Error("Factory review session security state could not be restored before prompt creation.");
493
+ }
494
+ }
495
+ }
470
496
  },
471
497
  initialState: {
472
498
  skipGlobalInstructions: true,
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","names":["#config","#boards","#configVersion","#preparing","#prepared","#dispatcher","#factoryProcessor"],"sources":["../src/factory.ts"],"sourcesContent":["/**\n * `MastraFactory` — the single entry point to the whole Mastra Software Factory.\n *\n * The consumer's deploy entry constructs deployment-specific config instances\n * (auth adapter, pubsub) and passes them here explicitly. The only provider\n * defaults constructed here are Platform GitHub, GitLab, Jira, incident.io, and\n * Linear integrations when Platform credentials exist and the caller did not\n * provide those integrations.\n *\n * `prepare()` resolves feature readiness, threads every dependency explicitly,\n * assembles the web routes/middleware, and returns the constructor args for\n * `new Mastra(...)`. The literal `export const mastra = new Mastra(...)` must\n * stay in the entry file — the deployer's `checkConfigExport` Babel plugin\n * only marks the config valid when it finds that literal in the entry AST —\n * so the factory produces args instead of the instance. `finalize()` runs the\n * post-construct boot (controller init + workers).\n *\n * Integration readiness is derived from each instance's declared capabilities\n * and the storage domains those capabilities require.\n */\n\nimport { MastraAuthStudio } from '@mastra/auth-studio';\nimport { prepareAgentControllerMount } from '@mastra/code-sdk';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { AgentControllerChannels } from '@mastra/core/channels';\nimport { EventEmitterPubSub } from '@mastra/core/events';\nimport type { PubSub } from '@mastra/core/events';\nimport type { Mastra } from '@mastra/core/mastra';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { hasAuthInit, isUserProvider } from '@mastra/core/server';\nimport type { IMastraAuthProvider } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\nimport type { MastraVector } from '@mastra/core/vector';\nimport type { WorkspaceSandbox } from '@mastra/core/workspace';\nimport {\n buildAuthRoutes,\n createFactoryAuthGate,\n createFactoryRouteAuth,\n getFactoryAuthOrgId,\n getFactoryAuthUserFromContext,\n getFactoryAuthUserId,\n} from './auth.js';\nimport { createBoardRegistry, isTerminalWorkItem } from './boards/index.js';\nimport type { BoardRegistry, InstalledBoard } from './boards/index.js';\nimport { touchFeed } from './feed-events.js';\nimport type { FactoryIntegration, IntegrationPostToolContext, IntegrationTools } from './integrations/base.js';\nimport { reconcileGithubAcceptanceLabels } from './integrations/github/acceptance-labels.js';\nimport type { GithubIntegration } from './integrations/github/integration.js';\nimport {\n recordFactoryPullRequestProvenance,\n resolveFactoryPullRequestParentWorkItemId,\n} from './integrations/github/provenance.js';\nimport type { FactoryPullRequestProvenanceData } from './integrations/github/provenance.js';\nimport { PlatformApiClient, platformApiClientConfigFromEnv } from './integrations/platform/api-client.js';\nimport { buildPlatformConnectRoutes } from './integrations/platform/connect/routes.js';\nimport { PlatformGithubIntegration } from './integrations/platform/github/integration.js';\nimport { PlatformGitLabIntegration } from './integrations/platform/gitlab/integration.js';\nimport { PlatformIncidentioIntegration } from './integrations/platform/incidentio/integration.js';\nimport { PlatformJiraIntegration } from './integrations/platform/jira/integration.js';\nimport { PlatformLinearIntegration } from './integrations/platform/linear/integration.js';\nimport { createCustomProvidersPrimer, registerCustomProvidersSource } from './routes/custom-provider-source.js';\nimport { ProjectRoutes } from './routes/projects.js';\nimport { assembleFactoryApiRoutes, buildIntegrationContext } from './routes/surface.js';\nimport type { FactoryApiRoutesDeps } from './routes/surface.js';\nimport { TelemetryRoutes } from './routes/telemetry.js';\nimport {\n createTenantCredentialPrimer,\n primeTenantCredentials,\n registerTenantCredentialResolver,\n} from './routes/tenant-credentials.js';\nimport { FactoryDecisionDispatcher } from './rules/dispatcher.js';\nimport type { FactoryRuleActor } from './rules/index.js';\nimport { FactoryPhaseStateProcessor } from './rules/processor.js';\nimport { createTerminalStageCleanup } from './rules/terminal-cleanup.js';\nimport { createFactoryTransitionTools } from './rules/tools.js';\nimport { FactoryTransitionService } from './rules/transition-service.js';\nimport { assertFactoryConfigVersion, DEFAULT_FACTORY_CONFIG_VERSION } from './rules/validation.js';\nimport { isValidGitRef } from './sandbox/git-ref.js';\nimport { SessionRetirementCoordinator } from './sandbox/session-retirement.js';\nimport type { MastraFactorySandboxConfig } from './sandbox/session-sandbox.js';\nimport { createPlaintextFactorySecretEncryption } from './secret-encryption.js';\nimport type { FactorySecretEncryption } from './secret-encryption.js';\nimport { handleServerError } from './server-error.js';\nimport { createSourceControlSessionLookup, refreshFactorySessionMemorySettings } from './session/factory-session.js';\nimport { observeSessionFilesystem } from './session/filesystem-capture.js';\nimport { observeSessionFirstExec } from './session/first-exec-capture.js';\nimport { observeSessionFirstMessage } from './session/first-message-capture.js';\nimport { LiveSessions } from './session/live-sessions.js';\nimport { hydrateSessionMemorySettings } from './session/memory-settings-hydration.js';\nimport { hydrateSessionModelPack } from './session/model-pack-hydration.js';\nimport { observeSessionRunEnd } from './session/run-audit.js';\nimport { createSourceControlTools } from './session/source-control-tools.js';\nimport { observeSessionThreadTitle } from './session/thread-title-mirror.js';\nimport { createSpaStaticMiddleware, resolveUiDistDir } from './spa-static.js';\nimport { createStateSigner } from './state-signing.js';\nimport { observeAgentGitAction } from './storage/domains/audit/agent-audit.js';\nimport { AuditStorage } from './storage/domains/audit/base.js';\nimport { AuditDomain } from './storage/domains/audit/domain.js';\nimport { ChannelIdentityStorage } from './storage/domains/channel-identity/base.js';\nimport { WorkItemCommentsStorage } from './storage/domains/comments/base.js';\nimport { CommentsDomain } from './storage/domains/comments/domain.js';\nimport { FactoryFeedReader } from './storage/domains/comments/feed-context.js';\nimport type { WorkItemFeedPublisher } from './storage/domains/comments/feed-sync.js';\nimport { ModelCredentialsStorage } from './storage/domains/credentials/base.js';\nimport { CustomProvidersStorage } from './storage/domains/custom-providers/base.js';\nimport { FilesystemStorage } from './storage/domains/filesystem/base.js';\nimport { IntakeStorage } from './storage/domains/intake/base.js';\nimport { IntegrationStorage } from './storage/domains/integrations/base.js';\nimport { MemorySettingsStorage } from './storage/domains/memory-settings/base.js';\nimport { ModelPacksStorage } from './storage/domains/model-packs/base.js';\nimport { FactoryProjectsStorage } from './storage/domains/projects/base.js';\nimport { QueueHealthStorage } from './storage/domains/queue-health/base.js';\nimport { SourceControlStorage } from './storage/domains/source-control/base.js';\nimport { WorkItemsStorage } from './storage/domains/work-items/base.js';\nimport type { WorkItemRow } from './storage/domains/work-items/base.js';\nimport { FactorySupervisorHealthWorker } from './supervisor/health-worker.js';\nimport { SUPERVISOR_INSTRUCTIONS } from './supervisor/instructions.js';\nimport { createFactorySupervisorReadTools } from './supervisor/read-tools.js';\nimport { messageWorkerSession } from './supervisor/session-messaging.js';\nimport { hydrateSupervisorSession, parseSupervisorResourceId, resolveSupervisorScope } from './supervisor/session.js';\nimport { createFactorySupervisorWriteTools } from './supervisor/write-tools.js';\nimport { timedPhase } from './timing.js';\nimport { createWorkspaceFactory, FactoryWorkspaceRegistry } from './workspace.js';\nimport type { FactorySandboxStart } from './workspace.js';\n\ntype BuildApiRoutesDeps = Pick<FactoryApiRoutesDeps, 'controller' | 'authStorage'>;\n\n/** Constructor args for the `new Mastra(...)` literal in the deploy entry. */\nexport type MastraArgs = NonNullable<ConstructorParameters<typeof Mastra>[0]>;\n\nexport interface MastraFactoryConfig {\n /**\n * Auth provider instance — `MastraAuthStudio` (`@mastra/auth-studio`),\n * `MastraAuthWorkos` (`@mastra/auth-workos`), `MastraAuthBetterAuth`\n * (`@mastra/auth-better-auth`), or any custom `MastraAuthProvider`. Whatever\n * instance is passed is the active provider; a passed instance is always\n * honored as-is.\n *\n * Omitted → the factory defaults to `MastraAuthStudio`, proxying auth\n * through the shared Mastra platform API. `MastraAuthStudio` resolves its\n * own env (`MASTRA_SHARED_API_URL`, `MASTRA_ORGANIZATION_ID`,\n * `MASTRA_COOKIE_DOMAIN`).\n *\n * Pass `null` to disable auth entirely (open server, local-dev behavior)\n * without falling back to the default.\n */\n auth?: IMastraAuthProvider | null;\n /**\n * REQUIRED. Factory storage backend powering BOTH agent storage (threads,\n * messages, memory, OM — via `getMastraStorage()`) and the app tables\n * (projects/source-control/audit/intake — via the generic ops surface). Pass a\n * `PgFactoryStorage` (`@mastra/pg`) for deployments or a\n * `LibSQLFactoryStorage` (`@mastra/libsql`) for local dev — one backend,\n * one connection, every feature on.\n */\n storage: FactoryStorage;\n /**\n * Vector store instance for recall search — `PgVector` (`@mastra/pg`) on\n * the same database as `storage`. Omitted → the SDK mount's default vector\n * store resolution applies.\n */\n vector?: MastraVector;\n /**\n * Distributed event bus instance (e.g. `new RedisStreamsPubSub({ url })`).\n * When set, streams/workflows/signals ride it across processes and the\n * controller drops file-based thread locks in favor of pubsub-coordinated\n * leases. Omitted → in-process default.\n */\n pubsub?: PubSub;\n /**\n * Browser-facing origin used to build integration OAuth/install callback\n * URLs and to derive the auth redirect URI. On the platform the SPA is\n * hosted separately, so this MUST be the public API origin.\n * Default: `http://localhost:4111` (the local Factory server, which also\n * serves the UI).\n */\n publicUrl?: string;\n /**\n * Allowed cross-origin SPA origins. The SPA may be served from a separate\n * static host, so credentialed requests must be explicitly allowed.\n */\n allowedOrigins?: string[];\n /** Sandbox configuration. Omitted → repository sandboxes are disabled. */\n sandbox?: MastraFactorySandboxConfig;\n /**\n * When a session's sandbox boots: on the agent's first command (`'lazy'`,\n * the default) or as soon as the session's workspace is first resolved\n * (`'eager'`), so the boot overlaps the model's own latency.\n */\n sandboxStart?: FactorySandboxStart;\n /** Background Factory dispatcher configuration. */\n dispatcher?: MastraFactoryDispatcherConfig;\n /**\n * Deployment-stable secret for signing integration OAuth `state` values.\n * Omitted → a per-process random secret, which is fine for single-process\n * local development but rejected for integrations that declare\n * `requiresStableStateSigner`.\n */\n stateSecret?: string;\n /**\n * Encryption boundary for persisted model credentials, custom-provider API\n * keys, integration connections, and integration settings. Strongly\n * recommended whenever auth is enabled — omitting it falls back to explicit\n * plaintext compatibility with a boot-time warning.\n */\n secretEncryption?: FactorySecretEncryption;\n /**\n * Registered capability providers. The factory registers the pieces each\n * `FactoryIntegration` instance provides — HTTP routes, storage domains,\n * agent/session tools, intake, source control, and diagnostics — into the\n * system. When Platform credentials are configured, missing `github`,\n * `gitlab`, and `linear` integrations default to their Platform-backed\n * implementations. Missing `jira` and `incidentio` integrations also default\n * to their Platform-backed implementations, which discover visible `jira` and\n * `incident-io` connections at runtime.\n */\n integrations?: FactoryIntegration[];\n /**\n * Operator-maintained provenance label stamped on transition audit rows,\n * deferred decisions, and session kickoff headers so a row can be traced\n * back to the deployment that produced it. Nothing branches on it.\n * Default: `factory-config-v1`.\n */\n configVersion?: string;\n /** Board definitions installed for this Factory instance. */\n boards?: readonly InstalledBoard[];\n /** Whether the built-in Work and Review boards are installed. Default: true. */\n includeDefaultBoards?: boolean;\n\n /**\n * Platform-specific overrides. `githubAppSlug` identifies Factory's own\n * GitHub App writes so their webhook deliveries do not retrigger triage.\n */\n platform?: {\n githubAppSlug?: string;\n };\n}\n\nexport type { MastraFactorySandboxConfig } from './sandbox/session-sandbox.js';\nexport type { FactorySandboxStart } from './workspace.js';\n\n/**\n * Per-process cap on concurrent background Factory dispatches. Omitted means\n * the dispatcher default; this is a local replica budget, not a global queue\n * limit shared across deployments.\n */\nexport interface MastraFactoryDispatcherConfig {\n maxInFlight?: number;\n}\n\nconst CONTROLLER_ID = 'code';\n\nfunction hasPlatformCredentials(): boolean {\n // MASTRA_PLATFORM_ACCESS_TOKEN is the credential Mastra Platform injects\n // into deployed projects; MASTRA_PLATFORM_SECRET_KEY is the org secret key\n // written by project scaffolding. Either enables the platform integrations.\n return Boolean(process.env.MASTRA_PLATFORM_ACCESS_TOKEN?.trim() || process.env.MASTRA_PLATFORM_SECRET_KEY?.trim());\n}\n\n/**\n * Default auth provider — `MastraAuthStudio`, which proxies identity to the\n * shared Mastra platform API. `MastraAuthStudio` resolves `MASTRA_SHARED_API_URL`,\n * `MASTRA_ORGANIZATION_ID`, and `MASTRA_COOKIE_DOMAIN` from env on its own —\n * this helper only derives a cookie-domain fallback from the factory's\n * `publicUrl`.\n *\n * Cookie-domain resolution (Studio picks the first that wins):\n * 1. explicit `MASTRA_COOKIE_DOMAIN` env, if set;\n * 2. `.mastra.ai` when `sharedApiUrl` is on `.mastra.ai`;\n * 3. this parent-domain fallback derived from `publicUrl` — so a deploy on\n * `https://foo.mastra.cloud` mints cookies with `Domain=.mastra.cloud`\n * without the caller wiring the env var by hand.\n * 4. otherwise host-only (no `Domain=`), which is correct for `localhost`.\n */\nfunction buildDefaultStudioAuth(publicUrl: string): IMastraAuthProvider {\n return new MastraAuthStudio({\n cookieDomain: parentDomainFromPublicUrl(publicUrl),\n });\n}\n\n/**\n * Derive a parent cookie domain from `publicUrl` by stripping the leftmost\n * label — the same shape platform-API's env injection uses (see\n * `platform/servers/api/src/lib/studio-env-vars.ts`: `.${routingDomain.replace(/^[^.]+\\./, '')}`).\n *\n * Rather than a generic `strip-left-label` heuristic — which either emits\n * cookies scoped to a public suffix (`sub.example.co.uk` → `.example.co.uk`\n * requires PSL data to be safe) or misclassifies numeric hostnames like\n * `3scale.example.com` as IPv4 — we only derive a parent domain when the\n * host sits under one of the platform's known registrable domains. Anything\n * else (custom domains, arbitrary tenant hostnames, IPs, `localhost`)\n * falls through to host-only cookies. Callers that need a different scope\n * pass `MASTRA_COOKIE_DOMAIN` explicitly (Studio honors that first).\n */\nconst KNOWN_PLATFORM_COOKIE_PARENTS = ['mastra.cloud', 'mastra.ai'] as const;\n\nfunction isIpLiteral(hostname: string): boolean {\n // IPv6 addresses in URLs are bracketed; `URL.hostname` strips the brackets\n // but the address itself still contains `:`. IPv4 is four dot-separated\n // numeric octets — trust the parser to have already validated shape.\n if (hostname.includes(':')) return true;\n return /^\\d+(?:\\.\\d+){3}$/.test(hostname);\n}\n\nfunction parentDomainFromPublicUrl(publicUrl: string): string | undefined {\n let hostname: string;\n try {\n hostname = new URL(publicUrl).hostname.toLowerCase();\n } catch {\n return undefined;\n }\n if (hostname === 'localhost' || isIpLiteral(hostname)) return undefined;\n for (const parent of KNOWN_PLATFORM_COOKIE_PARENTS) {\n // Exact match → we're already on the parent, host-only is correct.\n // Subdomain match → mint the parent-scoped cookie.\n if (hostname === parent) return undefined;\n if (hostname.endsWith(`.${parent}`)) return `.${parent}`;\n }\n return undefined;\n}\n\n/** A session parking or being answered is inbox news, so the registry bumps the project's feed. */\nfunction liveSessionsTouchingTheFeed(controller: BuildApiRoutesDeps['controller'], eventBus: PubSub): LiveSessions {\n const liveSessions = new LiveSessions(controller);\n liveSessions.onParkedChanged(session => {\n const { factoryOrgId, factoryProjectId } = session.state.get();\n if (factoryOrgId && factoryProjectId) touchFeed(eventBus, { orgId: factoryOrgId, factoryProjectId });\n });\n return liveSessions;\n}\n\nexport class MastraFactory {\n readonly #config: MastraFactoryConfig;\n readonly #boards: BoardRegistry;\n readonly #configVersion: string;\n #prepared: Awaited<ReturnType<typeof prepareAgentControllerMount>> | undefined;\n #dispatcher: FactoryDecisionDispatcher | undefined;\n #factoryProcessor: FactoryPhaseStateProcessor | undefined;\n #preparing = false;\n\n constructor(config: MastraFactoryConfig) {\n if (!config?.storage) {\n throw new Error(\n \"MastraFactory: 'storage' is required. Pass a FactoryStorage backend — e.g. \" +\n \"new PgFactoryStorage({ connectionString }) from '@mastra/pg' for deployments, or \" +\n \"new LibSQLFactoryStorage({ url }) from '@mastra/libsql' for local dev.\",\n );\n }\n this.#boards = createBoardRegistry({\n boards: config.boards,\n includeDefaultBoards: config.includeDefaultBoards,\n });\n if ('rules' in config) {\n throw new Error(\n \"MastraFactory: 'rules' was removed. Set 'configVersion' for the audit label and declare tool-result \" +\n 'rules on the owning board via defineBoard({ tools }).',\n );\n }\n this.#configVersion = assertFactoryConfigVersion(config.configVersion ?? DEFAULT_FACTORY_CONFIG_VERSION);\n this.#config = config;\n }\n\n /**\n * Resolve feature readiness, wire every dependency explicitly, and assemble\n * everything needed to construct the server-owned Mastra. Returns the args\n * for the `new Mastra(...)` literal that must live in the entry file.\n */\n async prepare(): Promise<MastraArgs> {\n // Guard set synchronously (before the first await) so overlapping calls —\n // not just strictly sequential ones — can't double-seed the runtime\n // registry or double-run one-time adapter init.\n if (this.#preparing) throw new Error('MastraFactory.prepare() called twice');\n this.#preparing = true;\n\n const publicOrigin = (this.#config.publicUrl ?? 'http://localhost:4111').replace(/\\/+$/, '');\n const allowedOrigins = (this.#config.allowedOrigins ?? []).map(o => o.replace(/\\/+$/, '')).filter(Boolean);\n const storage = this.#config.storage;\n const vector = this.#config.vector;\n const pubsub = this.#config.pubsub;\n // One bus for feed/attention events: a second fallback instance would leave\n // SSE subscribers and producers on different emitters in single-process runs.\n const eventBus = pubsub ?? new EventEmitterPubSub();\n // Default auth: honor an explicitly-passed provider (including `null` to\n // disable auth) as-is; otherwise fall back to `MastraAuthStudio`\n // (platform-proxied identity). The default derives its cookie domain\n // from `publicUrl` — deploys on `<sub>.mastra.cloud` mint parent-domain\n // cookies without the caller wiring `MASTRA_COOKIE_DOMAIN` explicitly.\n const configuredAuth = this.#config.auth;\n const auth: IMastraAuthProvider | undefined =\n configuredAuth === null ? undefined : (configuredAuth ?? buildDefaultStudioAuth(publicOrigin));\n if (auth && !this.#config.secretEncryption) {\n console.warn(\n \"[factory] auth is enabled but 'secretEncryption' is not configured. Persisted model credentials, \" +\n 'custom-provider API keys, and integration secrets will be stored as plaintext. Provide ' +\n \"'secretEncryption' (e.g. via FACTORY_CREDENTIAL_ENCRYPTION_KEY) to encrypt them at rest.\",\n );\n }\n const secretEncryption = this.#config.secretEncryption ?? createPlaintextFactorySecretEncryption();\n // One RouteAuth seam per boot, closed over the resolved provider. Every\n // factory route module receives this handle — no service locator.\n const routeAuth = createFactoryRouteAuth(auth);\n\n // Explicit integrations win. Platform credentials fill only missing\n // provider slots so callers can override each integration independently.\n const integrations = [...(this.#config.integrations ?? [])];\n if (hasPlatformCredentials()) {\n if (!integrations.some(integration => integration.id === 'github')) {\n integrations.push(new PlatformGithubIntegration({ slug: this.#config.platform?.githubAppSlug }));\n }\n if (!integrations.some(integration => integration.id === 'incidentio')) {\n integrations.push(new PlatformIncidentioIntegration());\n }\n if (!integrations.some(integration => integration.id === 'jira')) {\n integrations.push(new PlatformJiraIntegration());\n }\n if (!integrations.some(integration => integration.id === 'gitlab')) {\n integrations.push(new PlatformGitLabIntegration());\n }\n if (!integrations.some(integration => integration.id === 'linear')) {\n integrations.push(new PlatformLinearIntegration());\n }\n }\n\n // Validate ids up front so a copy-paste duplicate fails loud instead of one\n // instance silently shadowing the other.\n const integrationIds = new Set<string>();\n for (const integration of integrations) {\n if (integrationIds.has(integration.id)) {\n throw new Error(`MastraFactory: duplicate integration id '${integration.id}' in 'integrations'.`);\n }\n integrationIds.add(integration.id);\n }\n const configVersion = this.#configVersion;\n\n // FactoryStorage owns every app-table domain and initializes them through\n // the same lifecycle as the backend connection.\n const intakeStorage = storage.registerDomain(new IntakeStorage());\n const auditStorage = storage.registerDomain(new AuditStorage());\n const workItemsStorage = storage.registerDomain(new WorkItemsStorage());\n workItemsStorage.onAttentionChanged(scope => touchFeed(eventBus, scope));\n workItemsStorage.useTerminalPhasePredicate(item => isTerminalWorkItem(this.#boards, item));\n const modelCredentialsStorage = storage.registerDomain(new ModelCredentialsStorage(secretEncryption));\n const modelPacksStorage = storage.registerDomain(new ModelPacksStorage());\n const memorySettingsStorage = storage.registerDomain(new MemorySettingsStorage());\n const customProvidersStorage = storage.registerDomain(new CustomProvidersStorage(secretEncryption));\n const queueHealthStorage = storage.registerDomain(new QueueHealthStorage());\n // Generic integration storage (connections/subscriptions/settings) — the\n // default persistence surface for integrations without a bespoke domain.\n const integrationStorage = storage.registerDomain(new IntegrationStorage(secretEncryption));\n const factoryProjectsStorage = storage.registerDomain(new FactoryProjectsStorage());\n const filesystemStorage = storage.registerDomain(new FilesystemStorage());\n const sourceControlStorage = storage.registerDomain(new SourceControlStorage());\n // Reverse index from a platform sender (Slack/Discord/...) to a Mastra\n // tenant, so inbound channel events can resolve the sender's model creds.\n const channelIdentityStorage = storage.registerDomain(new ChannelIdentityStorage());\n const workItemCommentsStorage = storage.registerDomain(new WorkItemCommentsStorage());\n // Every app-table domain handle the route builders and integrations need,\n // threaded explicitly (no service locator).\n const domains = {\n intake: intakeStorage,\n modelCredentials: modelCredentialsStorage,\n modelPacks: modelPacksStorage,\n memorySettings: memorySettingsStorage,\n customProviders: customProvidersStorage,\n filesystem: filesystemStorage,\n projects: factoryProjectsStorage,\n queueHealth: queueHealthStorage,\n workItems: workItemsStorage,\n channelIdentity: channelIdentityStorage,\n comments: workItemCommentsStorage,\n };\n const auditDomain = new AuditDomain({\n auth: routeAuth,\n audit: auditStorage,\n projects: factoryProjectsStorage,\n users: auth && isUserProvider(auth) ? auth : undefined,\n sinks: integrations,\n agentTenant: requestContext => {\n const user = getFactoryAuthUserFromContext(requestContext);\n return { orgId: getFactoryAuthOrgId(user), userId: getFactoryAuthUserId(user) };\n },\n });\n // Held by reference: the channel attach below pushes into it, long after this.\n const feedPublishers: WorkItemFeedPublisher[] = [];\n const commentsDomain = new CommentsDomain({\n auth: routeAuth,\n comments: workItemCommentsStorage,\n workItems: workItemsStorage,\n projects: factoryProjectsStorage,\n channelIdentity: channelIdentityStorage,\n audit: auditDomain,\n publishers: feedPublishers,\n pubsub: eventBus,\n });\n\n // The sandbox config is a bare callback constructing a session's sandbox\n // from intent. Shape-only validation: probing it with a synthetic ctx at\n // boot would construct against a fake session, so only the type is\n // checked.\n const sandboxConfig = this.#config.sandbox;\n if (sandboxConfig !== undefined && typeof sandboxConfig !== 'function') {\n // An object here is almost certainly the pre-callback config, which\n // described a fleet the factory managed itself. That fleet is gone:\n // sandboxes are per session and the host constructs them, so say what to\n // write instead rather than only naming the expected type.\n if (typeof sandboxConfig === 'object' && sandboxConfig !== null) {\n throw new Error(\n `MastraFactory: 'sandbox' is now a callback, not an options object. It receives a FactorySandboxContext and returns a MastraSandbox, so the host chooses the provider per session:\\n` +\n ` sandbox: ctx => new E2BSandbox({ id: ctx.sessionId })\\n` +\n `The old options map three ways: 'machine' becomes the provider instance you construct inside the callback (one per session instead of one cloned template); 'workdir' is gone — remote providers clone into the VM's home directory and local providers check out under their own workingDirectory; 'maxSandboxes' is gone with the sandbox fleet — there is one sandbox per session and no pool to cap. Omit 'sandbox' entirely to disable sandboxes.`,\n );\n }\n throw new Error(\n `MastraFactory: 'sandbox' must be a function constructing a MastraSandbox from a FactorySandboxContext.`,\n );\n }\n\n const workspaceRegistry = new FactoryWorkspaceRegistry();\n\n // One shared OAuth state signer per boot. The deploy entry supplies a\n // replica-stable secret when needed; otherwise local development gets a\n // per-process random signer (`stable: false`).\n const stateSigner = createStateSigner(this.#config.stateSecret);\n\n // One-time provider initialization with factory-level context (e.g.\n // better-auth builds its default instance on the backend's auth\n // database, WorkOS derives its redirect URI from the public URL).\n // Failures surface here, at prepare() — a misconfigured provider must\n // not boot.\n if (auth && hasAuthInit(auth)) {\n await timedPhase('prepare.auth.init', () =>\n auth.init({ database: storage.authDatabase?.(), publicUrl: publicOrigin, allowedOrigins }),\n );\n }\n\n // Single init path: backend connection failure is a hard boot error;\n // registered app domains initialize fail-soft inside FactoryStorage.\n await timedPhase('prepare.storage.init', () => storage.init());\n\n // Per-tenant model credentials: once the credentials domain is up, model\n // resolution goes through the caller's own store and the SDK stops\n // mirroring stored API keys into process.env.\n //\n // Only register when a real auth adapter gates callers. In local /\n // auth-disabled mode there is no authenticated tenant, so registering would\n // force every model call through an empty tenant store (fail-closed, no env\n // fallback) and break chat with \"Not logged in\". Leaving it unregistered\n // lets the SDK fall back to the file-backed AuthStorage (auth.json) — the\n // same store the local /login and Settings pages read and write.\n if (auth) {\n registerTenantCredentialResolver(modelCredentialsStorage);\n }\n\n // Custom providers: DB-backed in both modes (org rows in tenant mode, the\n // sentinel `local` org in no-auth mode). Once registered, model resolution\n // and the gateway catalog never read settings.json custom providers.\n registerCustomProvidersSource({ storage: customProvidersStorage, authEnabled: Boolean(auth) });\n\n for (const integration of integrations) {\n const integrationSourceControl = integration.versionControl\n ? sourceControlStorage.forIntegration(integration.id)\n : undefined;\n integration.initialize?.({\n storage: integrationStorage.forIntegration(integration.id),\n projects: factoryProjectsStorage,\n auth: routeAuth,\n intake: intakeStorage,\n ...(integrationSourceControl ? { sourceControl: integrationSourceControl } : {}),\n });\n if (integration.versionControl && integrationSourceControl) {\n integration.versionControl.initialize({ storage: integrationSourceControl });\n }\n }\n // Keep the legacy GitHub partition readable even when no GitHub\n // integration is registered; existing sessions may outlive a config change.\n const sourceControlIntegrationIds = [\n ...new Set(['github', ...integrations.filter(integration => integration.versionControl).map(({ id }) => id)]),\n ];\n const sourceControlHandles = sourceControlIntegrationIds.map(id => sourceControlStorage.forIntegration(id));\n const sourceControlSessions = createSourceControlSessionLookup(sourceControlHandles);\n\n // Every integration uses generic integration storage. Version-control\n // providers additionally require the source-control storage domain. Readiness\n // is derived solely from capability presence, never from provider ids.\n const integrationRegistrations = integrations.map(integration => {\n const requiredDomains = [\n 'integrations',\n ...(integration.versionControl ? ['source-control'] : []),\n // Channels resolve an inbound sender to a tenant through the reverse\n // index; without it every message would dispatch tenant-less.\n ...(integration.channels ? ['channel-identity'] : []),\n ];\n return {\n integration,\n ready: requiredDomains.every(domain => storage.isDomainReady(domain)),\n ensureReady: async () => {\n for (const domain of requiredDomains) await storage.ensureDomainReady(domain);\n },\n };\n });\n const intakeReady =\n integrations.some(integration => integration.intake !== undefined) && storage.isDomainReady('intake');\n const factoryReady = storage.isDomainReady('projects') && storage.isDomainReady('work-items');\n const knowledgeEnabled = process.env.MASTRACODE_EXPERIMENTAL_SUBCONSCIOUS === '1';\n const githubIntegration = integrations.find(integration => integration.id === 'github') as\n | GithubIntegration\n | undefined;\n const gitlabIntegration = integrations.find(\n integration => integration.id === 'gitlab' && integration.intake && integration.versionControl,\n );\n const workItemsReady = storage.isDomainReady('work-items');\n const sessionRetirement =\n sandboxConfig && storage.isDomainReady('source-control')\n ? new SessionRetirementCoordinator({\n invalidateSession: sessionId => workspaceRegistry.invalidateSession(sessionId),\n })\n : undefined;\n const retireTerminalSessions =\n sessionRetirement && workItemsReady\n ? async ({ orgId, workItemId }: { orgId: string; workItemId: string }) => {\n const item = await workItemsStorage.get({ orgId, id: workItemId });\n if (!item) return;\n const sessionIds = [...new Set(Object.values(item.sessions).map(session => session.sessionId))];\n await Promise.all(\n sessionIds.map(async sessionId => {\n const sourceControl = await sourceControlSessions.getSourceControlBySessionId(sessionId);\n if (!sourceControl) return;\n await sessionRetirement.retireSession({\n sourceControl,\n orgId,\n sessionId,\n deleteSession: false,\n });\n }),\n );\n }\n : undefined;\n // Terminal-stage cleanup: ingest any trailing tool results from the item's\n // bound threads, then revoke the bindings so completed items leave the\n // reconcile walk (the active-binding set otherwise grows forever), and\n // finally release the item's sandboxes. Each step is best-effort — a\n // committed transition never fails on cleanup.\n const terminalCleanup = workItemsReady\n ? createTerminalStageCleanup({\n workItems: workItemsStorage,\n // `factoryProcessor` is assigned below in this scope; the cleanup\n // only runs on transitions long after bootstrap completes.\n reconcileBinding: async (binding): Promise<void> => {\n await factoryProcessor?.reconcileBinding(binding);\n },\n // Abort the live run on a retired seat so the model cannot keep\n // executing unbound (or be resumed later) once its binding is revoked.\n // `this.#prepared` is assigned below; cleanup only runs long after.\n abortSession: async (binding): Promise<void> => {\n const controller = this.#prepared?.base.controller;\n if (!controller) return;\n const session = await controller.getSessionByResource(binding.resourceId);\n if (session?.stream.isActive()) session.abort();\n },\n // Session retirement supersedes the older direct sandbox release: it\n // invalidates the session and stops/destroys its sandbox.\n ...(retireTerminalSessions ? { releaseSandboxes: retireTerminalSessions } : {}),\n })\n : undefined;\n const onTerminalStage = terminalCleanup\n ? (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n revision: number;\n actor: FactoryRuleActor;\n }): Promise<void> =>\n terminalCleanup({\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n workItemId: args.workItemId,\n revision: args.revision,\n // Leave the seat that drove its own terminal transition running; it\n // is already returning from its transition tool call.\n ...(args.actor.type === 'agent' ? { initiatingBindingId: args.actor.bindingId } : {}),\n })\n : retireTerminalSessions;\n const transitionService = workItemsReady\n ? new FactoryTransitionService({\n configVersion,\n boards: this.#boards,\n storage: workItemsStorage,\n audit: auditDomain,\n autoApprovePlans: async ({ orgId, factoryProjectId }) => {\n await factoryProjectsStorage.ensureReady();\n const project = await factoryProjectsStorage.get({ orgId, id: factoryProjectId });\n return project?.autoApprovePlans ?? false;\n },\n ...(onTerminalStage ? { onTerminalStage } : {}),\n ...(githubIntegration\n ? {\n onAccepted: (args: { orgId: string; factoryProjectId: string; item: WorkItemRow }) =>\n reconcileGithubAcceptanceLabels(\n githubIntegration,\n sourceControlStorage.forIntegration(githubIntegration.id),\n args,\n ),\n }\n : {}),\n })\n : undefined;\n const projectRoutes = new ProjectRoutes({\n auth: routeAuth,\n projects: factoryProjectsStorage,\n sourceControl: sourceControlStorage,\n versionControlIntegrationIds: integrations\n .filter(integration => integration.versionControl)\n .map(integration => integration.id),\n ...(githubIntegration || gitlabIntegration\n ? {\n resolveRepository: async ({ integrationId, orgId, userId, installationId, externalId, slug }) => {\n if (githubIntegration && integrationId === githubIntegration.id) {\n const installation = await githubIntegration.sourceControlStorage.installations.get({\n orgId,\n id: installationId,\n });\n if (!installation) return null;\n const repositories = await githubIntegration.listInstallationRepos(Number(installation.externalId));\n const selected = repositories.find(repo => repo.id.toString() === externalId && repo.fullName === slug);\n if (!selected) return null;\n return githubIntegration.sourceControlStorage.repositories.upsert({\n orgId,\n input: {\n installationId,\n externalId,\n slug: selected.fullName,\n defaultBranch: isValidGitRef(selected.defaultBranch) ? selected.defaultBranch : 'main',\n providerMetadata: { private: selected.private, owner: selected.owner },\n },\n });\n }\n\n if (\n !gitlabIntegration?.intake ||\n !gitlabIntegration.versionControl ||\n integrationId !== gitlabIntegration.id\n )\n return null;\n const handle = sourceControlStorage.forIntegration(gitlabIntegration.id);\n const installation = await handle.installations.get({ orgId, id: installationId });\n if (!installation) return null;\n const sources = await gitlabIntegration.intake.listSources({ orgId, userId });\n const selected = sources.find(\n source =>\n source.name === slug &&\n typeof source.metadata?.projectId === 'string' &&\n source.metadata.projectId === externalId &&\n source.metadata.connectionId === installation.externalId,\n );\n if (!selected) return null;\n const [repository] = await gitlabIntegration.versionControl.registerRepositories({\n orgId,\n installationId,\n repositories: [\n {\n externalId,\n slug,\n defaultBranch:\n typeof selected.metadata?.defaultBranch === 'string' &&\n isValidGitRef(selected.metadata.defaultBranch)\n ? selected.metadata.defaultBranch\n : 'main',\n metadata: selected.metadata,\n },\n ],\n });\n return repository ?? null;\n },\n }\n : {}),\n ...(sessionRetirement ? { sessionRetirement } : {}),\n ...(workItemsReady ? { workItems: workItemsStorage } : {}),\n });\n const factoryProcessor = workItemsReady\n ? new FactoryPhaseStateProcessor({\n configVersion,\n storage: workItemsStorage,\n boards: this.#boards,\n ...(transitionService ? { transitionService } : {}),\n ...(githubIntegration\n ? {\n recordPullRequestProvenance: (input: Parameters<typeof recordFactoryPullRequestProvenance>[4]) =>\n recordFactoryPullRequestProvenance(\n githubIntegration,\n sourceControlStorage.forIntegration('github'),\n integrationStorage.forIntegration('github'),\n workItemsStorage,\n input,\n ),\n }\n : {}),\n messageReader: {\n listMessages: async input => {\n const memory = await storage.getMastraStorage().getStore('memory');\n return memory ? memory.listMessages(input) : { messages: [], hasMore: false };\n },\n },\n })\n : undefined;\n\n // Boot assertion: an active integration that signs OAuth `state` needs a\n // replica-stable signer — a per-process random secret silently breaks the\n // OAuth callback on any replica that didn't sign the state. Fail loud now\n // instead. (The built-ins also assert this inside their readiness gates.)\n for (const { integration } of integrationRegistrations) {\n if (integration.requiresStableStateSigner && !stateSigner.stable) {\n throw new Error(\n `MastraFactory: integration '${integration.id}' signs OAuth state and requires a ` +\n `replica-stable state secret, but none is configured. Set 'stateSecret' on the factory config.`,\n );\n }\n }\n\n // The SDK needs to know which backend the injected Mastra store uses\n // (its own `instanceof` detection breaks when the dependency graph holds\n // duplicate package copies). Resolve it by walking the FactoryStorage\n // prototype chain by class name — the factory can't import the concrete\n // classes since '@mastra/pg' / '@mastra/libsql' are the user's choice.\n const mastraStorageBackend = (() => {\n for (let proto = Object.getPrototypeOf(storage); proto; proto = Object.getPrototypeOf(proto)) {\n if (proto.constructor?.name === 'PgFactoryStorage') return 'pg' as const;\n if (proto.constructor?.name === 'LibSQLFactoryStorage') return 'libsql' as const;\n }\n return undefined;\n })();\n\n // Integrations contributing tools to agent sessions: org-scoped\n // `agentTools` (resolved per request) + session-scoped `sessionTools`.\n const toolIntegrations = integrationRegistrations.filter(\n ({ integration }) => integration.agentTools || integration.sessionTools,\n );\n const sourceControlToolProviders = integrations.flatMap(integration =>\n integration.versionControl\n ? [\n {\n id: integration.id,\n versionControl: integration.versionControl,\n storage: sourceControlStorage.forIntegration(integration.id),\n },\n ]\n : [],\n );\n\n // Build the real production controller (agents, modes, tools, memory, OM,\n // MCP, providers) — identical to the terminal app. Agent state lives in\n // the storage backend's Mastra store alongside the Factory app tables —\n // one shared database for all users, separated by `resourceId` scoping.\n const prepared = await timedPhase('prepare.controllerMount', () =>\n prepareAgentControllerMount({\n controllerId: CONTROLLER_ID,\n coAuthor: { name: 'mastra-platform[bot]' },\n workspace: createWorkspaceFactory({\n ...(sandboxConfig ? { sandbox: sandboxConfig } : {}),\n ...(this.#config.sandboxStart ? { sandboxStart: this.#config.sandboxStart } : {}),\n sourceControls: integrations.flatMap(integration =>\n integration.versionControl\n ? [\n {\n id: integration.id,\n versionControl: integration.versionControl,\n storage: sourceControlStorage.forIntegration(integration.id),\n ...(integration.id === 'github' && githubIntegration ? { github: githubIntegration } : {}),\n },\n ]\n : [],\n ),\n ...(factoryProjectsStorage ? { projects: factoryProjectsStorage } : {}),\n ...(workItemsStorage ? { workItems: workItemsStorage } : {}),\n workspaceRegistry,\n }),\n disableGithubSignals: true,\n // Memory settings live in the factory's `memory-settings` app table (per\n // org/user), so the host machine's TUI settings.json must not seed them.\n disableSettingsOmSeed: true,\n hostInstructions: ({ requestContext }) => {\n const context = requestContext.get('controller') as\n | AgentControllerRequestContext<MastraCodeState>\n | undefined;\n return parseSupervisorResourceId(context?.resourceId) ? SUPERVISOR_INSTRUCTIONS : undefined;\n },\n // A factory reads the repository it works on and its skill, never the\n // ~/.claude instructions of whoever hosts the process. On the controller\n // rather than per session, so webhook-recreated sessions keep it too.\n // Blank project identity for the same reason: the SDK's defaults seed\n // sessions with the HOST process's own project root / name / branch,\n // which must never reach a hosted session's prompt. Repo-backed\n // sessions get their real workdir pinned by workspace resolution;\n // chat-only sessions legitimately have no project.\n // Factory sessions also start fail-closed on org classification: the\n // unresolved marker is set at birth, a successful org seed clears it,\n // and a resolved `factoryOrgId` always takes precedence — so a failed\n // (best-effort) seed can never leave a session classified as local.\n initialState: {\n skipGlobalInstructions: true,\n factoryOrgUnresolved: true,\n projectPath: '',\n projectName: '',\n gitBranch: '',\n },\n storage: storage.getMastraStorage(),\n ...(mastraStorageBackend ? { storageBackend: mastraStorageBackend } : {}),\n ...(factoryProcessor ? { inputProcessors: [factoryProcessor] } : {}),\n ...(vector ? { vector } : {}),\n ...(toolIntegrations.length > 0 ||\n sourceControlToolProviders.length > 0 ||\n (workItemsStorage && transitionService)\n ? {\n extraTools: async ({ requestContext }: { requestContext: RequestContext }) => {\n const tools: IntegrationTools = {};\n const toolOwners = new Map<string, string>();\n const mergeTools = (ownerId: string, contributed: IntegrationTools) => {\n for (const [name, tool] of Object.entries(contributed)) {\n const owner = toolOwners.get(name);\n if (owner) {\n throw new Error(\n `MastraFactory: integration tool '${name}' from '${ownerId}' conflicts with '${owner}'.`,\n );\n }\n toolOwners.set(name, ownerId);\n tools[name] = tool;\n }\n };\n if (storage.isDomainReady('source-control')) {\n mergeTools(\n 'source-control',\n createSourceControlTools({\n requestContext,\n providers: sourceControlToolProviders,\n audit: auditDomain,\n }),\n );\n }\n if (workItemsStorage && transitionService) {\n mergeTools(\n 'factory',\n await createFactoryTransitionTools({\n requestContext,\n storage: workItemsStorage,\n transitionService,\n boards: this.#boards,\n // Heals crash-resumed sessions: recovered addresses re-seed\n // projectRepositoryId/baseRef from the source session record.\n // Only offered while the source-control domain is ready — a\n // throwing lookup would abort recovery's catch block and also\n // skip the metadata baseRef fallback.\n ...(storage.isDomainReady('source-control') ? { sessions: sourceControlSessions } : {}),\n }),\n );\n // The supervisor session has no seat, so it never gets the\n // transition tool above; it gets the read surface instead,\n // and only once the caller's org is shown to own the project.\n const supervisorScope = await resolveSupervisorScope({\n requestContext,\n projects: factoryProjectsStorage,\n });\n if (supervisorScope) {\n const userId = getFactoryAuthUserId(getFactoryAuthUserFromContext(requestContext));\n mergeTools(\n 'factory-supervisor',\n createFactorySupervisorReadTools({\n scope: supervisorScope,\n workItems: workItemsStorage,\n boards: this.#boards,\n comments: workItemCommentsStorage,\n audit: auditStorage,\n messageReader: {\n listMessages: async input => {\n const memory = await storage.getMastraStorage().getStore('memory');\n return memory ? memory.listMessages(input) : { messages: [], hasMore: false };\n },\n },\n }),\n );\n if (userId) {\n mergeTools(\n 'factory-supervisor-write',\n createFactorySupervisorWriteTools({\n scope: supervisorScope,\n userId,\n workItems: workItemsStorage,\n audit: auditDomain,\n transitionService,\n ...(githubIntegration\n ? {\n reconcileAcceptanceLabels: (args: {\n orgId: string;\n factoryProjectId: string;\n item: WorkItemRow;\n }) =>\n reconcileGithubAcceptanceLabels(\n githubIntegration,\n sourceControlStorage.forIntegration(githubIntegration.id),\n args,\n ),\n }\n : {}),\n messageSession: ({ sessionId, message, delivery }) =>\n messageWorkerSession({\n controller: prepared.base.controller,\n sessionId,\n message,\n delivery,\n ...(requestContext ? { requestContext } : {}),\n }),\n }),\n );\n }\n }\n }\n for (const { integration, ready, ensureReady } of toolIntegrations) {\n if (!ready && ensureReady) {\n try {\n await ensureReady();\n } catch {\n continue;\n }\n }\n if (integration.agentTools) {\n mergeTools(integration.id, await integration.agentTools({ requestContext }));\n }\n if (integration.sessionTools) {\n mergeTools(integration.id, integration.sessionTools({ requestContext }));\n }\n }\n return tools;\n },\n }\n : {}),\n postToolObserver: async (toolContext: IntegrationPostToolContext) => {\n const requestContext = (toolContext.context as { requestContext?: RequestContext } | undefined)\n ?.requestContext;\n if (requestContext) {\n await observeAgentGitAction({\n audit: auditDomain,\n toolContext: { ...toolContext, context: requestContext },\n });\n }\n await Promise.all(\n integrations.map(async integration => {\n if (!integration.postToolObserver) return;\n try {\n await integration.postToolObserver({ toolContext, requestContext });\n } catch (error) {\n console.warn(`[factory] Integration '${integration.id}' post-tool observer failed:`, error);\n }\n }),\n );\n },\n ...(pubsub ? { pubsub, crossProcessPubSub: true } : {}),\n buildApiRoutes: ({ controller, authStorage }: BuildApiRoutesDeps) => [\n // Public `/auth/*` routes (login/callback/logout/me). Folded in as\n // `apiRoutes` (not plain Hono routes) because the entry can't touch the\n // Hono app the deployer generates. `requiresAuth: false`; the gate\n // skips `/auth/*`.\n ...(auth ? buildAuthRoutes(auth, { publicUrl: publicOrigin }) : []),\n ...new TelemetryRoutes({ auth: routeAuth, providerName: auth?.name, publicOrigin, allowedOrigins }).routes(),\n // Custom `/web/*` routes (fs / config / integrations / factory / audit).\n ...assembleFactoryApiRoutes({\n controllerId: CONTROLLER_ID,\n controller,\n liveSessions: liveSessionsTouchingTheFeed(controller, eventBus),\n auth: routeAuth,\n ...(auth && isUserProvider(auth) ? { users: auth } : {}),\n authStorage,\n audit: auditDomain,\n publicOrigin,\n stateSigner,\n sandbox: sandboxConfig,\n sessionRetirement,\n factoryStorage: storage,\n integrationStorage,\n sourceControlStorage,\n domains,\n feed: commentsDomain,\n integrations: integrationRegistrations,\n intakeReady,\n factoryReady,\n knowledgeEnabled,\n configVersion,\n boardRegistry: this.#boards,\n factoryTransitionService: transitionService,\n onFactoryRuntime: ({ transitionService: runtimeTransitionService, prepareBinding }) => {\n this.#dispatcher ??= new FactoryDecisionDispatcher({\n controller,\n transitionService: runtimeTransitionService,\n storage: storage.getDomain<WorkItemsStorage>('work-items'),\n audit: auditDomain,\n boards: this.#boards,\n maxInFlight: this.#config.dispatcher?.maxInFlight,\n isAutoRunEnabled: async ({ orgId, factoryProjectId }) => {\n await factoryProjectsStorage.ensureReady();\n const project = await factoryProjectsStorage.get({ orgId, id: factoryProjectId });\n return project?.autoRunEnabled ?? false;\n },\n autoApprovePlans: async ({ orgId, factoryProjectId }) => {\n await factoryProjectsStorage.ensureReady();\n const project = await factoryProjectsStorage.get({ orgId, id: factoryProjectId });\n return project?.autoApprovePlans ?? false;\n },\n reconcileToolResults: () => factoryProcessor?.reconcileAllBoundThreads() ?? Promise.resolve(),\n prepareBinding,\n refreshManagedMemorySettings: ({ binding, session }) =>\n refreshFactorySessionMemorySettings(session, {\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n projects: factoryProjectsStorage,\n memorySettings: memorySettingsStorage,\n }),\n feedReader: new FactoryFeedReader(workItemCommentsStorage),\n primeCredentials: tenant => primeTenantCredentials({ tenant, credentials: modelCredentialsStorage }),\n resolveLinkedWorkItemParentId: async ({ orgId, factoryProjectId, decision }) => {\n if (decision.source !== 'github-pr') return null;\n const repositoryId = decision.metadata?.githubRepositoryId;\n const pullRequestNumber = decision.metadata?.githubPullRequestNumber;\n if (typeof repositoryId !== 'number' || typeof pullRequestNumber !== 'number') return null;\n return resolveFactoryPullRequestParentWorkItemId(\n integrationStorage.forIntegration<\n Record<string, unknown>,\n Record<string, unknown>,\n FactoryPullRequestProvenanceData\n >('github'),\n { orgId, factoryProjectId, repositoryId, pullRequestNumber },\n );\n },\n });\n },\n }),\n ...projectRoutes.routes(),\n ...auditDomain.routes(),\n ...commentsDomain.routes(),\n // Connect/reconnect session minting for Platform-managed providers.\n // Server-side because only the deploy holds Platform machine\n // credentials; the SPA runs the OAuth popup with the minted token.\n ...(hasPlatformCredentials()\n ? buildPlatformConnectRoutes({\n auth: routeAuth,\n client: new PlatformApiClient(platformApiClientConfigFromEnv()),\n })\n : []),\n ],\n buildServerConfig: () => {\n const cors = allowedOrigins.length ? { cors: { origin: allowedOrigins, credentials: true } } : {};\n // Log route errors with method/path/stack and answer with structured\n // JSON instead of an opaque `Internal Server Error`. Applied by the\n // deployer to both the top-level app and the custom-route sub-app.\n const onError = { onError: handleServerError };\n // Same-origin SPA: when a vite build is present (see resolveUiDistDir),\n // serve it at `/` from this server. Mounted last so the auth gate (when\n // enabled) covers it; it always passes `/api`, `/web`, `/auth` through.\n const uiDist = resolveUiDistDir();\n const spa = uiDist ? [createSpaStaticMiddleware(uiDist)] : [];\n if (!auth) {\n // Auth disabled: no gate. Still prime the sentinel-local custom\n // provider snapshot so model calls see DB rows, then SPA + CORS.\n return {\n middleware: [\n createCustomProvidersPrimer({ auth: routeAuth, storage: customProvidersStorage, authEnabled: false }),\n ...spa,\n ],\n ...cors,\n ...onError,\n };\n }\n\n // Ordered middleware. The deployer applies these AFTER its context\n // middleware sets `c.set('mastra', mastra)` and BEFORE routes, so:\n // 1. gate — validates the auth session, stashes the user, and 401s /\n // redirects unauthenticated requests. Skips public `/auth/*`.\n // 2. primers — hydrate the caller's model-credential and custom\n // provider snapshots so the request's first model call\n // resolves tenant credentials and custom providers.\n // 3. spa — serves the built UI for everything the server doesn't own.\n // `auth` also lands on `server.auth` so the core auth middleware (and\n // Studio's dual-auth routing — see `studio.auth` on the returned args)\n // authenticates core `/api/*` routes with the same provider.\n return {\n auth,\n middleware: [\n createFactoryAuthGate(auth),\n createTenantCredentialPrimer({ auth: routeAuth, credentials: modelCredentialsStorage }),\n createCustomProvidersPrimer({\n auth: routeAuth,\n storage: customProvidersStorage,\n authEnabled: Boolean(auth),\n }),\n ...spa,\n ],\n ...cors,\n ...onError,\n };\n },\n }),\n );\n\n prepared.base.controller.onSessionCreated(session => {\n observeSessionFilesystem(session, {\n filesystem: filesystemStorage,\n sourceControl: { sessions: sourceControlSessions },\n });\n observeSessionFirstMessage(session, {\n sourceControl: { sessions: sourceControlSessions },\n });\n observeSessionFirstExec(session, {\n sourceControl: { sessions: sourceControlSessions },\n });\n observeSessionThreadTitle(session, {\n sourceControl: { sessions: sourceControlSessions },\n });\n observeSessionRunEnd(session, { audit: auditDomain });\n });\n\n // Supervisor sessions carry their project in the resourceId; re-stamp\n // scope, instructions and factory defaults on every (re)creation so a\n // restarted server heals the in-memory state.\n prepared.base.controller.onSessionCreated(\n session =>\n hydrateSupervisorSession(session, {\n projects: factoryProjectsStorage,\n memorySettings: memorySettingsStorage,\n }).catch(error => {\n console.warn('[Factory Supervisor] Failed to hydrate supervisor session', {\n error: error instanceof Error ? error.message : String(error),\n });\n }),\n { blocking: true },\n );\n\n // Blocking: `createSession` awaits this seed, so when hydration succeeds\n // a session's first run starts with the owner's stored OM settings.\n // Best-effort — failures are logged inside the helper, never thrown, and\n // the session then falls back to its persisted/default OM configuration.\n prepared.base.controller.onSessionCreated(\n session =>\n hydrateSessionMemorySettings(session, {\n sourceControl: { sessions: sourceControlSessions },\n projects: factoryProjectsStorage,\n memorySettings: memorySettingsStorage,\n }),\n { blocking: true },\n );\n\n // Personal model packs seed interactive user sessions only. Active Factory\n // run bindings are excluded and continue to use the project default model.\n prepared.base.controller.onSessionCreated(\n session =>\n hydrateSessionModelPack(session, {\n sourceControl: { sessions: sourceControlSessions },\n workItems: workItemsStorage,\n modelPacks: modelPacksStorage,\n }),\n { blocking: true },\n );\n\n this.#prepared = prepared;\n this.#factoryProcessor = factoryProcessor;\n\n // Chat-platform channels (Slack, Discord, …) contributed by integrations,\n // attached to the mounted controller so inbound platform messages reach\n // the same agents the web UI drives. READY integrations only — readiness\n // means the `channel-identity` domain's `init()` succeeded, so its link\n // table is queryable. Without it a sender can't be resolved to a tenant,\n // and attaching anyway would dispatch runs on default credentials.\n const channelRegistrations = integrationRegistrations.filter(\n ({ integration, ready }) => ready && integration.channels,\n );\n // `setChannels` replaces rather than merges, so a second provider would\n // silently never receive a message. Fail loud instead.\n if (channelRegistrations.length > 1) {\n throw new Error(\n `MastraFactory: integrations [${channelRegistrations\n .map(({ integration }) => integration.id)\n .join(', ')}] all provide channels, but only one may. Remove all but one.`,\n );\n }\n for (const { integration } of channelRegistrations) {\n const context = buildIntegrationContext(\n {\n controller: prepared.base.controller,\n publicOrigin,\n auth: routeAuth,\n stateSigner,\n sandbox: sandboxConfig,\n factoryStorage: storage,\n integrationStorage,\n sourceControlStorage,\n integrations: integrationRegistrations,\n configVersion,\n boardRegistry: this.#boards,\n factoryReady,\n domains,\n feed: commentsDomain,\n ...(githubIntegration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n // Integrations return a channels CONFIG; the factory owns construction.\n prepared.base.controller.setChannels(new AgentControllerChannels(integration.channels!(context)));\n }\n\n // Feed publishers mirror web-feed comments outward (a chat bridge, a\n // webhook, an issue tracker). Independent of channels(): an integration may\n // publish without owning a chat channel. READY integrations only — the\n // array is held by reference by CommentsDomain (see `feedPublishers` above),\n // so pushing here wires the publisher into comment dispatch.\n for (const { integration } of integrationRegistrations.filter(\n ({ integration, ready }) => ready && integration.feedPublisher,\n )) {\n const context = buildIntegrationContext(\n {\n controller: prepared.base.controller,\n publicOrigin,\n auth: routeAuth,\n stateSigner,\n sandbox: sandboxConfig,\n factoryStorage: storage,\n integrationStorage,\n sourceControlStorage,\n integrations: integrationRegistrations,\n configVersion,\n boardRegistry: this.#boards,\n factoryReady,\n domains,\n feed: commentsDomain,\n ...(githubIntegration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n const publisher = integration.feedPublisher!(context);\n if (publisher) feedPublishers.push(publisher);\n }\n\n // Integration lifecycle workers (e.g. polling an upstream without\n // webhooks): collected from READY integrations only, folded into the\n // constructor args so `new Mastra(...)` merges them with the default\n // workers and `finalize()`'s `startWorkers()` starts them alongside the\n // built-ins. Never passed for the disabled/not-ready case — a worker for\n // an unavailable integration must not run.\n const integrationWorkers = [\n ...(factoryReady\n ? [\n new FactorySupervisorHealthWorker({\n projects: factoryProjectsStorage,\n workItems: workItemsStorage,\n boards: this.#boards,\n }),\n ]\n : []),\n ...integrationRegistrations\n .filter(({ integration, ready }) => ready && integration.workers)\n .flatMap(({ integration }) =>\n integration.workers!(\n buildIntegrationContext(\n {\n controller: prepared.base.controller,\n publicOrigin,\n auth: routeAuth,\n stateSigner,\n sandbox: sandboxConfig,\n factoryStorage: storage,\n integrationStorage,\n sourceControlStorage,\n integrations: integrationRegistrations,\n configVersion,\n boardRegistry: this.#boards,\n factoryReady,\n domains,\n feed: commentsDomain,\n ...(githubIntegration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n ),\n ),\n ),\n ];\n\n return {\n ...prepared.mastraArgs,\n // Same provider on `studio.auth` as on `server.auth` (buildServerConfig):\n // deployed factories must authenticate BOTH plain API callers and Studio\n // requests (`x-mastra-client-type: studio` routes to `studio.auth`).\n ...(auth ? { studio: { auth } } : {}),\n ...(integrationWorkers.length > 0 ? { workers: integrationWorkers } : {}),\n };\n }\n\n /**\n * Post-construct boot: initialize the controller (which inherits the\n * constructed Mastra's storage) and start its workers. Call AFTER the entry\n * has run `new Mastra(prepare()'s args)`.\n */\n async finalize(): Promise<void> {\n if (!this.#prepared) {\n throw new Error('MastraFactory.finalize() called before prepare()');\n }\n await timedPhase('finalize.controller', () => this.#prepared!.finalize());\n await timedPhase(\n 'finalize.reconcileBoundThreads',\n () => this.#factoryProcessor?.reconcileAllBoundThreads() ?? Promise.resolve(),\n );\n this.#dispatcher?.start();\n }\n\n /** Stop Factory-owned background dispatch before the host process shuts down. */\n async shutdown(): Promise<void> {\n await this.#dispatcher?.stop();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory.js","names":["#config","#boards","#configVersion","#preparing","#prepared","#dispatcher","#factoryProcessor"],"sources":["../src/factory.ts"],"sourcesContent":["/**\n * `MastraFactory` — the single entry point to the whole Mastra Software Factory.\n *\n * The consumer's deploy entry constructs deployment-specific config instances\n * (auth adapter, pubsub) and passes them here explicitly. The only provider\n * defaults constructed here are Platform GitHub, GitLab, Jira, incident.io, and\n * Linear integrations when Platform credentials exist and the caller did not\n * provide those integrations.\n *\n * `prepare()` resolves feature readiness, threads every dependency explicitly,\n * assembles the web routes/middleware, and returns the constructor args for\n * `new Mastra(...)`. The literal `export const mastra = new Mastra(...)` must\n * stay in the entry file — the deployer's `checkConfigExport` Babel plugin\n * only marks the config valid when it finds that literal in the entry AST —\n * so the factory produces args instead of the instance. `finalize()` runs the\n * post-construct boot (controller init + workers).\n *\n * Integration readiness is derived from each instance's declared capabilities\n * and the storage domains those capabilities require.\n */\n\nimport { MastraAuthStudio } from '@mastra/auth-studio';\nimport { prepareAgentControllerMount } from '@mastra/code-sdk';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport { AgentControllerChannels } from '@mastra/core/channels';\nimport { EventEmitterPubSub } from '@mastra/core/events';\nimport type { PubSub } from '@mastra/core/events';\nimport type { Mastra } from '@mastra/core/mastra';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { hasAuthInit, isUserProvider } from '@mastra/core/server';\nimport type { IMastraAuthProvider } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\nimport type { MastraVector } from '@mastra/core/vector';\nimport type { WorkspaceSandbox } from '@mastra/core/workspace';\nimport {\n buildAuthRoutes,\n createFactoryAuthGate,\n createFactoryRouteAuth,\n getFactoryAuthOrgId,\n getFactoryAuthUserFromContext,\n getFactoryAuthUserId,\n} from './auth.js';\nimport { createBoardRegistry, isTerminalWorkItem } from './boards/index.js';\nimport type { BoardRegistry, InstalledBoard } from './boards/index.js';\nimport { touchFeed } from './feed-events.js';\nimport type { FactoryIntegration, IntegrationPostToolContext, IntegrationTools } from './integrations/base.js';\nimport { reconcileGithubAcceptanceLabels } from './integrations/github/acceptance-labels.js';\nimport type { GithubRuleOverrides } from './integrations/github/default-rules.js';\nimport type { GithubIntegration } from './integrations/github/integration.js';\nimport {\n recordFactoryPullRequestProvenance,\n resolveFactoryPullRequestParentWorkItemId,\n} from './integrations/github/provenance.js';\nimport type { FactoryPullRequestProvenanceData } from './integrations/github/provenance.js';\nimport { PlatformApiClient, platformApiClientConfigFromEnv } from './integrations/platform/api-client.js';\nimport { buildPlatformConnectRoutes } from './integrations/platform/connect/routes.js';\nimport { PlatformGithubIntegration } from './integrations/platform/github/integration.js';\nimport { PlatformGitLabIntegration } from './integrations/platform/gitlab/integration.js';\nimport { PlatformIncidentioIntegration } from './integrations/platform/incidentio/integration.js';\nimport { PlatformJiraIntegration } from './integrations/platform/jira/integration.js';\nimport { PlatformLinearIntegration } from './integrations/platform/linear/integration.js';\nimport { createCustomProvidersPrimer, registerCustomProvidersSource } from './routes/custom-provider-source.js';\nimport { ProjectRoutes } from './routes/projects.js';\nimport { assembleFactoryApiRoutes, buildIntegrationContext } from './routes/surface.js';\nimport type { FactoryApiRoutesDeps } from './routes/surface.js';\nimport { TelemetryRoutes } from './routes/telemetry.js';\nimport {\n createTenantCredentialPrimer,\n primeTenantCredentials,\n registerTenantCredentialResolver,\n} from './routes/tenant-credentials.js';\nimport { resolveFactorySessionAddress } from './rules/binding-context.js';\nimport { FactoryDecisionDispatcher } from './rules/dispatcher.js';\nimport type { FactoryRuleActor } from './rules/index.js';\nimport { FactoryPhaseStateProcessor } from './rules/processor.js';\nimport { createTerminalStageCleanup } from './rules/terminal-cleanup.js';\nimport { createFactoryTransitionTools } from './rules/tools.js';\nimport { FactoryTransitionService } from './rules/transition-service.js';\nimport { assertFactoryConfigVersion, DEFAULT_FACTORY_CONFIG_VERSION } from './rules/validation.js';\nimport { isValidGitRef } from './sandbox/git-ref.js';\nimport { SessionRetirementCoordinator } from './sandbox/session-retirement.js';\nimport type { MastraFactorySandboxConfig } from './sandbox/session-sandbox.js';\nimport { createPlaintextFactorySecretEncryption } from './secret-encryption.js';\nimport type { FactorySecretEncryption } from './secret-encryption.js';\nimport { handleServerError } from './server-error.js';\nimport { createSourceControlSessionLookup, refreshFactorySessionMemorySettings } from './session/factory-session.js';\nimport { observeSessionFilesystem } from './session/filesystem-capture.js';\nimport { observeSessionFirstExec } from './session/first-exec-capture.js';\nimport { observeSessionFirstMessage } from './session/first-message-capture.js';\nimport { LiveSessions } from './session/live-sessions.js';\nimport { hydrateSessionMemorySettings } from './session/memory-settings-hydration.js';\nimport { hydrateSessionModelPack } from './session/model-pack-hydration.js';\nimport { observeSessionRunEnd } from './session/run-audit.js';\nimport { createSourceControlTools } from './session/source-control-tools.js';\nimport { observeSessionThreadTitle } from './session/thread-title-mirror.js';\nimport { createSpaStaticMiddleware, resolveUiDistDir } from './spa-static.js';\nimport { createStateSigner } from './state-signing.js';\nimport { observeAgentGitAction } from './storage/domains/audit/agent-audit.js';\nimport { AuditStorage } from './storage/domains/audit/base.js';\nimport { AuditDomain } from './storage/domains/audit/domain.js';\nimport { ChannelIdentityStorage } from './storage/domains/channel-identity/base.js';\nimport { WorkItemCommentsStorage } from './storage/domains/comments/base.js';\nimport { CommentsDomain } from './storage/domains/comments/domain.js';\nimport { FactoryFeedReader } from './storage/domains/comments/feed-context.js';\nimport type { WorkItemFeedPublisher } from './storage/domains/comments/feed-sync.js';\nimport { ModelCredentialsStorage } from './storage/domains/credentials/base.js';\nimport { CustomProvidersStorage } from './storage/domains/custom-providers/base.js';\nimport { FilesystemStorage } from './storage/domains/filesystem/base.js';\nimport { IntakeStorage } from './storage/domains/intake/base.js';\nimport { IntegrationStorage } from './storage/domains/integrations/base.js';\nimport { MemorySettingsStorage } from './storage/domains/memory-settings/base.js';\nimport { ModelPacksStorage } from './storage/domains/model-packs/base.js';\nimport { FactoryProjectsStorage } from './storage/domains/projects/base.js';\nimport { QueueHealthStorage } from './storage/domains/queue-health/base.js';\nimport { SourceControlStorage } from './storage/domains/source-control/base.js';\nimport { WorkItemsStorage } from './storage/domains/work-items/base.js';\nimport type { WorkItemRow } from './storage/domains/work-items/base.js';\nimport { FactorySupervisorHealthWorker } from './supervisor/health-worker.js';\nimport { SUPERVISOR_INSTRUCTIONS } from './supervisor/instructions.js';\nimport { createFactorySupervisorReadTools } from './supervisor/read-tools.js';\nimport { messageWorkerSession } from './supervisor/session-messaging.js';\nimport { hydrateSupervisorSession, parseSupervisorResourceId, resolveSupervisorScope } from './supervisor/session.js';\nimport { createFactorySupervisorWriteTools } from './supervisor/write-tools.js';\nimport { timedPhase } from './timing.js';\nimport { createWorkspaceFactory, FactoryWorkspaceRegistry } from './workspace.js';\nimport type { FactorySandboxStart } from './workspace.js';\n\ntype BuildApiRoutesDeps = Pick<FactoryApiRoutesDeps, 'controller' | 'authStorage'>;\n\n/** Constructor args for the `new Mastra(...)` literal in the deploy entry. */\nexport type MastraArgs = NonNullable<ConstructorParameters<typeof Mastra>[0]>;\n\nexport interface MastraFactoryConfig {\n /**\n * Auth provider instance — `MastraAuthStudio` (`@mastra/auth-studio`),\n * `MastraAuthWorkos` (`@mastra/auth-workos`), `MastraAuthBetterAuth`\n * (`@mastra/auth-better-auth`), or any custom `MastraAuthProvider`. Whatever\n * instance is passed is the active provider; a passed instance is always\n * honored as-is.\n *\n * Omitted → the factory defaults to `MastraAuthStudio`, proxying auth\n * through the shared Mastra platform API. `MastraAuthStudio` resolves its\n * own env (`MASTRA_SHARED_API_URL`, `MASTRA_ORGANIZATION_ID`,\n * `MASTRA_COOKIE_DOMAIN`).\n *\n * Pass `null` to disable auth entirely (open server, local-dev behavior)\n * without falling back to the default.\n */\n auth?: IMastraAuthProvider | null;\n /**\n * REQUIRED. Factory storage backend powering BOTH agent storage (threads,\n * messages, memory, OM — via `getMastraStorage()`) and the app tables\n * (projects/source-control/audit/intake — via the generic ops surface). Pass a\n * `PgFactoryStorage` (`@mastra/pg`) for deployments or a\n * `LibSQLFactoryStorage` (`@mastra/libsql`) for local dev — one backend,\n * one connection, every feature on.\n */\n storage: FactoryStorage;\n /**\n * Vector store instance for recall search — `PgVector` (`@mastra/pg`) on\n * the same database as `storage`. Omitted → the SDK mount's default vector\n * store resolution applies.\n */\n vector?: MastraVector;\n /**\n * Distributed event bus instance (e.g. `new RedisStreamsPubSub({ url })`).\n * When set, streams/workflows/signals ride it across processes and the\n * controller drops file-based thread locks in favor of pubsub-coordinated\n * leases. Omitted → in-process default.\n */\n pubsub?: PubSub;\n /**\n * Browser-facing origin used to build integration OAuth/install callback\n * URLs and to derive the auth redirect URI. On the platform the SPA is\n * hosted separately, so this MUST be the public API origin.\n * Default: `http://localhost:4111` (the local Factory server, which also\n * serves the UI).\n */\n publicUrl?: string;\n /**\n * Allowed cross-origin SPA origins. The SPA may be served from a separate\n * static host, so credentialed requests must be explicitly allowed.\n */\n allowedOrigins?: string[];\n /** Sandbox configuration. Omitted → repository sandboxes are disabled. */\n sandbox?: MastraFactorySandboxConfig;\n /**\n * When a session's sandbox boots: on the agent's first command (`'lazy'`,\n * the default) or as soon as the session's workspace is first resolved\n * (`'eager'`), so the boot overlaps the model's own latency.\n */\n sandboxStart?: FactorySandboxStart;\n /** Background Factory dispatcher configuration. */\n dispatcher?: MastraFactoryDispatcherConfig;\n /**\n * Deployment-stable secret for signing integration OAuth `state` values.\n * Omitted → a per-process random secret, which is fine for single-process\n * local development but rejected for integrations that declare\n * `requiresStableStateSigner`.\n */\n stateSecret?: string;\n /**\n * Encryption boundary for persisted model credentials, custom-provider API\n * keys, integration connections, and integration settings. Strongly\n * recommended whenever auth is enabled — omitting it falls back to explicit\n * plaintext compatibility with a boot-time warning.\n */\n secretEncryption?: FactorySecretEncryption;\n /**\n * Registered capability providers. The factory registers the pieces each\n * `FactoryIntegration` instance provides — HTTP routes, storage domains,\n * agent/session tools, intake, source control, and diagnostics — into the\n * system. When Platform credentials are configured, missing `github`,\n * `gitlab`, and `linear` integrations default to their Platform-backed\n * implementations. Missing `jira` and `incidentio` integrations also default\n * to their Platform-backed implementations, which discover visible `jira` and\n * `incident-io` connections at runtime.\n */\n integrations?: FactoryIntegration[];\n /**\n * Operator-maintained provenance label stamped on transition audit rows,\n * deferred decisions, and session kickoff headers so a row can be traced\n * back to the deployment that produced it. Nothing branches on it.\n * Default: `factory-config-v1`.\n */\n configVersion?: string;\n /** Board definitions installed for this Factory instance. */\n boards?: readonly InstalledBoard[];\n /** Whether the built-in Work and Review boards are installed. Default: true. */\n includeDefaultBoards?: boolean;\n\n /**\n * Platform-specific overrides.\n */\n platform?: {\n /** Identifies Factory's own GitHub App writes so their webhook deliveries do not retrigger triage. */\n githubAppSlug?: string;\n /**\n * Overrides for the `PlatformGithubIntegration` the factory installs itself\n * when Platform credentials are present; these options are forwarded to its\n * constructor. An explicit integration with id `github` in `integrations`\n * takes precedence and makes this key a no-op, so the two are not meant to\n * be used together.\n */\n github?: {\n /** Replace an event handler, or disable it with null; omitted events keep defaults. */\n rules?: GithubRuleOverrides;\n /**\n * GitHub App slug used to recognize Factory's own webhook writes. Falls\n * back to `githubAppSlug` when omitted.\n */\n slug?: string;\n };\n };\n}\n\nexport type { MastraFactorySandboxConfig } from './sandbox/session-sandbox.js';\nexport type { FactorySandboxStart } from './workspace.js';\n\n/**\n * Per-process cap on concurrent background Factory dispatches. Omitted means\n * the dispatcher default; this is a local replica budget, not a global queue\n * limit shared across deployments.\n */\nexport interface MastraFactoryDispatcherConfig {\n maxInFlight?: number;\n}\n\nconst CONTROLLER_ID = 'code';\n\nfunction hasPlatformCredentials(): boolean {\n // MASTRA_PLATFORM_ACCESS_TOKEN is the credential Mastra Platform injects\n // into deployed projects; MASTRA_PLATFORM_SECRET_KEY is the org secret key\n // written by project scaffolding. Either enables the platform integrations.\n return Boolean(process.env.MASTRA_PLATFORM_ACCESS_TOKEN?.trim() || process.env.MASTRA_PLATFORM_SECRET_KEY?.trim());\n}\n\n/**\n * Default auth provider — `MastraAuthStudio`, which proxies identity to the\n * shared Mastra platform API. `MastraAuthStudio` resolves `MASTRA_SHARED_API_URL`,\n * `MASTRA_ORGANIZATION_ID`, and `MASTRA_COOKIE_DOMAIN` from env on its own —\n * this helper only derives a cookie-domain fallback from the factory's\n * `publicUrl`.\n *\n * Cookie-domain resolution (Studio picks the first that wins):\n * 1. explicit `MASTRA_COOKIE_DOMAIN` env, if set;\n * 2. `.mastra.ai` when `sharedApiUrl` is on `.mastra.ai`;\n * 3. this parent-domain fallback derived from `publicUrl` — so a deploy on\n * `https://foo.mastra.cloud` mints cookies with `Domain=.mastra.cloud`\n * without the caller wiring the env var by hand.\n * 4. otherwise host-only (no `Domain=`), which is correct for `localhost`.\n */\nfunction buildDefaultStudioAuth(publicUrl: string): IMastraAuthProvider {\n return new MastraAuthStudio({\n cookieDomain: parentDomainFromPublicUrl(publicUrl),\n });\n}\n\n/**\n * Derive a parent cookie domain from `publicUrl` by stripping the leftmost\n * label — the same shape platform-API's env injection uses (see\n * `platform/servers/api/src/lib/studio-env-vars.ts`: `.${routingDomain.replace(/^[^.]+\\./, '')}`).\n *\n * Rather than a generic `strip-left-label` heuristic — which either emits\n * cookies scoped to a public suffix (`sub.example.co.uk` → `.example.co.uk`\n * requires PSL data to be safe) or misclassifies numeric hostnames like\n * `3scale.example.com` as IPv4 — we only derive a parent domain when the\n * host sits under one of the platform's known registrable domains. Anything\n * else (custom domains, arbitrary tenant hostnames, IPs, `localhost`)\n * falls through to host-only cookies. Callers that need a different scope\n * pass `MASTRA_COOKIE_DOMAIN` explicitly (Studio honors that first).\n */\nconst KNOWN_PLATFORM_COOKIE_PARENTS = ['mastra.cloud', 'mastra.ai'] as const;\n\nfunction isIpLiteral(hostname: string): boolean {\n // IPv6 addresses in URLs are bracketed; `URL.hostname` strips the brackets\n // but the address itself still contains `:`. IPv4 is four dot-separated\n // numeric octets — trust the parser to have already validated shape.\n if (hostname.includes(':')) return true;\n return /^\\d+(?:\\.\\d+){3}$/.test(hostname);\n}\n\nfunction parentDomainFromPublicUrl(publicUrl: string): string | undefined {\n let hostname: string;\n try {\n hostname = new URL(publicUrl).hostname.toLowerCase();\n } catch {\n return undefined;\n }\n if (hostname === 'localhost' || isIpLiteral(hostname)) return undefined;\n for (const parent of KNOWN_PLATFORM_COOKIE_PARENTS) {\n // Exact match → we're already on the parent, host-only is correct.\n // Subdomain match → mint the parent-scoped cookie.\n if (hostname === parent) return undefined;\n if (hostname.endsWith(`.${parent}`)) return `.${parent}`;\n }\n return undefined;\n}\n\n/** A session parking or being answered is inbox news, so the registry bumps the project's feed. */\nfunction liveSessionsTouchingTheFeed(controller: BuildApiRoutesDeps['controller'], eventBus: PubSub): LiveSessions {\n const liveSessions = new LiveSessions(controller);\n liveSessions.onParkedChanged(session => {\n const { factoryOrgId, factoryProjectId } = session.state.get();\n if (factoryOrgId && factoryProjectId) touchFeed(eventBus, { orgId: factoryOrgId, factoryProjectId });\n });\n return liveSessions;\n}\n\nexport class MastraFactory {\n readonly #config: MastraFactoryConfig;\n readonly #boards: BoardRegistry;\n readonly #configVersion: string;\n #prepared: Awaited<ReturnType<typeof prepareAgentControllerMount>> | undefined;\n #dispatcher: FactoryDecisionDispatcher | undefined;\n #factoryProcessor: FactoryPhaseStateProcessor | undefined;\n #preparing = false;\n\n constructor(config: MastraFactoryConfig) {\n if (!config?.storage) {\n throw new Error(\n \"MastraFactory: 'storage' is required. Pass a FactoryStorage backend — e.g. \" +\n \"new PgFactoryStorage({ connectionString }) from '@mastra/pg' for deployments, or \" +\n \"new LibSQLFactoryStorage({ url }) from '@mastra/libsql' for local dev.\",\n );\n }\n this.#boards = createBoardRegistry({\n boards: config.boards,\n includeDefaultBoards: config.includeDefaultBoards,\n });\n if ('rules' in config) {\n throw new Error(\n \"MastraFactory: 'rules' was removed. Set 'configVersion' for the audit label and declare tool-result \" +\n 'rules on the owning board via defineBoard({ tools }).',\n );\n }\n this.#configVersion = assertFactoryConfigVersion(config.configVersion ?? DEFAULT_FACTORY_CONFIG_VERSION);\n this.#config = config;\n }\n\n /**\n * Resolve feature readiness, wire every dependency explicitly, and assemble\n * everything needed to construct the server-owned Mastra. Returns the args\n * for the `new Mastra(...)` literal that must live in the entry file.\n */\n async prepare(): Promise<MastraArgs> {\n // Guard set synchronously (before the first await) so overlapping calls —\n // not just strictly sequential ones — can't double-seed the runtime\n // registry or double-run one-time adapter init.\n if (this.#preparing) throw new Error('MastraFactory.prepare() called twice');\n this.#preparing = true;\n\n const publicOrigin = (this.#config.publicUrl ?? 'http://localhost:4111').replace(/\\/+$/, '');\n const allowedOrigins = (this.#config.allowedOrigins ?? []).map(o => o.replace(/\\/+$/, '')).filter(Boolean);\n const storage = this.#config.storage;\n const vector = this.#config.vector;\n const pubsub = this.#config.pubsub;\n // One bus for feed/attention events: a second fallback instance would leave\n // SSE subscribers and producers on different emitters in single-process runs.\n const eventBus = pubsub ?? new EventEmitterPubSub();\n // Default auth: honor an explicitly-passed provider (including `null` to\n // disable auth) as-is; otherwise fall back to `MastraAuthStudio`\n // (platform-proxied identity). The default derives its cookie domain\n // from `publicUrl` — deploys on `<sub>.mastra.cloud` mint parent-domain\n // cookies without the caller wiring `MASTRA_COOKIE_DOMAIN` explicitly.\n const configuredAuth = this.#config.auth;\n const auth: IMastraAuthProvider | undefined =\n configuredAuth === null ? undefined : (configuredAuth ?? buildDefaultStudioAuth(publicOrigin));\n if (auth && !this.#config.secretEncryption) {\n console.warn(\n \"[factory] auth is enabled but 'secretEncryption' is not configured. Persisted model credentials, \" +\n 'custom-provider API keys, and integration secrets will be stored as plaintext. Provide ' +\n \"'secretEncryption' (e.g. via FACTORY_CREDENTIAL_ENCRYPTION_KEY) to encrypt them at rest.\",\n );\n }\n const secretEncryption = this.#config.secretEncryption ?? createPlaintextFactorySecretEncryption();\n // One RouteAuth seam per boot, closed over the resolved provider. Every\n // factory route module receives this handle — no service locator.\n const routeAuth = createFactoryRouteAuth(auth);\n\n // Explicit integrations win. Platform credentials fill only missing\n // provider slots so callers can override each integration independently.\n const integrations = [...(this.#config.integrations ?? [])];\n const explicitGithub = integrations.some(integration => integration.id === 'github');\n // Whether this boot constructed the GitHub integration on the caller's\n // behalf — `platform.github` is only meaningful in that case.\n let installedPlatformGithub = false;\n if (hasPlatformCredentials()) {\n if (!explicitGithub) {\n // Forward `platform.github` to the integration the factory installs\n // itself. `slug` falls back to the sibling `githubAppSlug` so existing\n // deploys keep working; the constructor validates and freezes `rules`,\n // so invalid ones fail boot.\n integrations.push(\n new PlatformGithubIntegration({\n rules: this.#config.platform?.github?.rules,\n slug: this.#config.platform?.github?.slug ?? this.#config.platform?.githubAppSlug,\n }),\n );\n installedPlatformGithub = true;\n }\n if (!integrations.some(integration => integration.id === 'incidentio')) {\n integrations.push(new PlatformIncidentioIntegration());\n }\n if (!integrations.some(integration => integration.id === 'jira')) {\n integrations.push(new PlatformJiraIntegration());\n }\n if (!integrations.some(integration => integration.id === 'gitlab')) {\n integrations.push(new PlatformGitLabIntegration());\n }\n if (!integrations.some(integration => integration.id === 'linear')) {\n integrations.push(new PlatformLinearIntegration());\n }\n }\n // Never let the key be a silent no-op: it only applies to the integration\n // the factory installs itself, so warn when it had nothing to apply to.\n if (this.#config.platform?.github && !installedPlatformGithub) {\n console.warn(\n \"[factory] 'platform.github' config was provided but the factory installed no GitHub integration of its \" +\n 'own ' +\n (explicitGithub\n ? \"(an explicit integration with id 'github' takes precedence, so these options were ignored).\"\n : '(no Platform credentials are configured and no explicit GitHub integration was passed).'),\n );\n }\n\n // Validate ids up front so a copy-paste duplicate fails loud instead of one\n // instance silently shadowing the other.\n const integrationIds = new Set<string>();\n for (const integration of integrations) {\n if (integrationIds.has(integration.id)) {\n throw new Error(`MastraFactory: duplicate integration id '${integration.id}' in 'integrations'.`);\n }\n integrationIds.add(integration.id);\n }\n const configVersion = this.#configVersion;\n\n // FactoryStorage owns every app-table domain and initializes them through\n // the same lifecycle as the backend connection.\n const intakeStorage = storage.registerDomain(new IntakeStorage());\n const auditStorage = storage.registerDomain(new AuditStorage());\n const workItemsStorage = storage.registerDomain(new WorkItemsStorage());\n workItemsStorage.onAttentionChanged(scope => touchFeed(eventBus, scope));\n workItemsStorage.useTerminalPhasePredicate(item => isTerminalWorkItem(this.#boards, item));\n const modelCredentialsStorage = storage.registerDomain(new ModelCredentialsStorage(secretEncryption));\n const modelPacksStorage = storage.registerDomain(new ModelPacksStorage());\n const memorySettingsStorage = storage.registerDomain(new MemorySettingsStorage());\n const customProvidersStorage = storage.registerDomain(new CustomProvidersStorage(secretEncryption));\n const queueHealthStorage = storage.registerDomain(new QueueHealthStorage());\n // Generic integration storage (connections/subscriptions/settings) — the\n // default persistence surface for integrations without a bespoke domain.\n const integrationStorage = storage.registerDomain(new IntegrationStorage(secretEncryption));\n const factoryProjectsStorage = storage.registerDomain(new FactoryProjectsStorage());\n const filesystemStorage = storage.registerDomain(new FilesystemStorage());\n const sourceControlStorage = storage.registerDomain(new SourceControlStorage());\n // Reverse index from a platform sender (Slack/Discord/...) to a Mastra\n // tenant, so inbound channel events can resolve the sender's model creds.\n const channelIdentityStorage = storage.registerDomain(new ChannelIdentityStorage());\n const workItemCommentsStorage = storage.registerDomain(new WorkItemCommentsStorage());\n // Every app-table domain handle the route builders and integrations need,\n // threaded explicitly (no service locator).\n const domains = {\n intake: intakeStorage,\n modelCredentials: modelCredentialsStorage,\n modelPacks: modelPacksStorage,\n memorySettings: memorySettingsStorage,\n customProviders: customProvidersStorage,\n filesystem: filesystemStorage,\n projects: factoryProjectsStorage,\n queueHealth: queueHealthStorage,\n workItems: workItemsStorage,\n channelIdentity: channelIdentityStorage,\n comments: workItemCommentsStorage,\n };\n const auditDomain = new AuditDomain({\n auth: routeAuth,\n audit: auditStorage,\n projects: factoryProjectsStorage,\n users: auth && isUserProvider(auth) ? auth : undefined,\n sinks: integrations,\n agentTenant: requestContext => {\n const user = getFactoryAuthUserFromContext(requestContext);\n return { orgId: getFactoryAuthOrgId(user), userId: getFactoryAuthUserId(user) };\n },\n });\n // Held by reference: the channel attach below pushes into it, long after this.\n const feedPublishers: WorkItemFeedPublisher[] = [];\n const commentsDomain = new CommentsDomain({\n auth: routeAuth,\n comments: workItemCommentsStorage,\n workItems: workItemsStorage,\n projects: factoryProjectsStorage,\n channelIdentity: channelIdentityStorage,\n audit: auditDomain,\n publishers: feedPublishers,\n pubsub: eventBus,\n });\n\n // The sandbox config is a bare callback constructing a session's sandbox\n // from intent. Shape-only validation: probing it with a synthetic ctx at\n // boot would construct against a fake session, so only the type is\n // checked.\n const sandboxConfig = this.#config.sandbox;\n if (sandboxConfig !== undefined && typeof sandboxConfig !== 'function') {\n // An object here is almost certainly the pre-callback config, which\n // described a fleet the factory managed itself. That fleet is gone:\n // sandboxes are per session and the host constructs them, so say what to\n // write instead rather than only naming the expected type.\n if (typeof sandboxConfig === 'object' && sandboxConfig !== null) {\n throw new Error(\n `MastraFactory: 'sandbox' is now a callback, not an options object. It receives a FactorySandboxContext and returns a MastraSandbox, so the host chooses the provider per session:\\n` +\n ` sandbox: ctx => new E2BSandbox({ id: ctx.sessionId })\\n` +\n `The old options map three ways: 'machine' becomes the provider instance you construct inside the callback (one per session instead of one cloned template); 'workdir' is gone — remote providers clone into the VM's home directory and local providers check out under their own workingDirectory; 'maxSandboxes' is gone with the sandbox fleet — there is one sandbox per session and no pool to cap. Omit 'sandbox' entirely to disable sandboxes.`,\n );\n }\n throw new Error(\n `MastraFactory: 'sandbox' must be a function constructing a MastraSandbox from a FactorySandboxContext.`,\n );\n }\n\n const workspaceRegistry = new FactoryWorkspaceRegistry();\n\n // One shared OAuth state signer per boot. The deploy entry supplies a\n // replica-stable secret when needed; otherwise local development gets a\n // per-process random signer (`stable: false`).\n const stateSigner = createStateSigner(this.#config.stateSecret);\n\n // One-time provider initialization with factory-level context (e.g.\n // better-auth builds its default instance on the backend's auth\n // database, WorkOS derives its redirect URI from the public URL).\n // Failures surface here, at prepare() — a misconfigured provider must\n // not boot.\n if (auth && hasAuthInit(auth)) {\n await timedPhase('prepare.auth.init', () =>\n auth.init({ database: storage.authDatabase?.(), publicUrl: publicOrigin, allowedOrigins }),\n );\n }\n\n // Single init path: backend connection failure is a hard boot error;\n // registered app domains initialize fail-soft inside FactoryStorage.\n await timedPhase('prepare.storage.init', () => storage.init());\n\n // Per-tenant model credentials: once the credentials domain is up, model\n // resolution goes through the caller's own store and the SDK stops\n // mirroring stored API keys into process.env.\n //\n // Only register when a real auth adapter gates callers. In local /\n // auth-disabled mode there is no authenticated tenant, so registering would\n // force every model call through an empty tenant store (fail-closed, no env\n // fallback) and break chat with \"Not logged in\". Leaving it unregistered\n // lets the SDK fall back to the file-backed AuthStorage (auth.json) — the\n // same store the local /login and Settings pages read and write.\n if (auth) {\n registerTenantCredentialResolver(modelCredentialsStorage);\n }\n\n // Custom providers: DB-backed in both modes (org rows in tenant mode, the\n // sentinel `local` org in no-auth mode). Once registered, model resolution\n // and the gateway catalog never read settings.json custom providers.\n registerCustomProvidersSource({ storage: customProvidersStorage, authEnabled: Boolean(auth) });\n\n for (const integration of integrations) {\n const integrationSourceControl = integration.versionControl\n ? sourceControlStorage.forIntegration(integration.id)\n : undefined;\n integration.initialize?.({\n storage: integrationStorage.forIntegration(integration.id),\n projects: factoryProjectsStorage,\n auth: routeAuth,\n intake: intakeStorage,\n ...(integrationSourceControl ? { sourceControl: integrationSourceControl } : {}),\n });\n if (integration.versionControl && integrationSourceControl) {\n integration.versionControl.initialize({ storage: integrationSourceControl });\n }\n }\n // Keep the legacy GitHub partition readable even when no GitHub\n // integration is registered; existing sessions may outlive a config change.\n const sourceControlIntegrationIds = [\n ...new Set(['github', ...integrations.filter(integration => integration.versionControl).map(({ id }) => id)]),\n ];\n const sourceControlHandles = sourceControlIntegrationIds.map(id => sourceControlStorage.forIntegration(id));\n const sourceControlSessions = createSourceControlSessionLookup(sourceControlHandles);\n\n // Every integration uses generic integration storage. Version-control\n // providers additionally require the source-control storage domain. Readiness\n // is derived solely from capability presence, never from provider ids.\n const integrationRegistrations = integrations.map(integration => {\n const requiredDomains = [\n 'integrations',\n ...(integration.versionControl ? ['source-control'] : []),\n // Channels resolve an inbound sender to a tenant through the reverse\n // index; without it every message would dispatch tenant-less.\n ...(integration.channels ? ['channel-identity'] : []),\n ];\n return {\n integration,\n ready: requiredDomains.every(domain => storage.isDomainReady(domain)),\n ensureReady: async () => {\n for (const domain of requiredDomains) await storage.ensureDomainReady(domain);\n },\n };\n });\n const intakeReady =\n integrations.some(integration => integration.intake !== undefined) && storage.isDomainReady('intake');\n const factoryReady = storage.isDomainReady('projects') && storage.isDomainReady('work-items');\n const knowledgeEnabled = process.env.MASTRACODE_EXPERIMENTAL_SUBCONSCIOUS === '1';\n const githubIntegration = integrations.find(integration => integration.id === 'github') as\n | GithubIntegration\n | undefined;\n const gitlabIntegration = integrations.find(\n integration => integration.id === 'gitlab' && integration.intake && integration.versionControl,\n );\n const workItemsReady = storage.isDomainReady('work-items');\n const sessionRetirement =\n sandboxConfig && storage.isDomainReady('source-control')\n ? new SessionRetirementCoordinator({\n invalidateSession: sessionId => workspaceRegistry.invalidateSession(sessionId),\n })\n : undefined;\n const retireTerminalSessions =\n sessionRetirement && workItemsReady\n ? async ({ orgId, workItemId }: { orgId: string; workItemId: string }) => {\n const item = await workItemsStorage.get({ orgId, id: workItemId });\n if (!item) return;\n const sessionIds = [...new Set(Object.values(item.sessions).map(session => session.sessionId))];\n await Promise.all(\n sessionIds.map(async sessionId => {\n const sourceControl = await sourceControlSessions.getSourceControlBySessionId(sessionId);\n if (!sourceControl) return;\n await sessionRetirement.retireSession({\n sourceControl,\n orgId,\n sessionId,\n deleteSession: false,\n });\n }),\n );\n }\n : undefined;\n // Terminal-stage cleanup: ingest any trailing tool results from the item's\n // bound threads, then revoke the bindings so completed items leave the\n // reconcile walk (the active-binding set otherwise grows forever), and\n // finally release the item's sandboxes. Each step is best-effort — a\n // committed transition never fails on cleanup.\n const terminalCleanup = workItemsReady\n ? createTerminalStageCleanup({\n workItems: workItemsStorage,\n // `factoryProcessor` is assigned below in this scope; the cleanup\n // only runs on transitions long after bootstrap completes.\n reconcileBinding: async (binding): Promise<void> => {\n await factoryProcessor?.reconcileBinding(binding);\n },\n // Abort the live run on a retired seat so the model cannot keep\n // executing unbound (or be resumed later) once its binding is revoked.\n // `this.#prepared` is assigned below; cleanup only runs long after.\n abortSession: async (binding): Promise<void> => {\n const controller = this.#prepared?.base.controller;\n if (!controller) return;\n const session = await controller.getSessionByResource(binding.resourceId);\n if (session?.stream.isActive()) session.abort();\n },\n // Session retirement supersedes the older direct sandbox release: it\n // invalidates the session and stops/destroys its sandbox.\n ...(retireTerminalSessions ? { releaseSandboxes: retireTerminalSessions } : {}),\n })\n : undefined;\n const onTerminalStage = terminalCleanup\n ? (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n revision: number;\n actor: FactoryRuleActor;\n }): Promise<void> =>\n terminalCleanup({\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n workItemId: args.workItemId,\n revision: args.revision,\n // Leave the seat that drove its own terminal transition running; it\n // is already returning from its transition tool call.\n ...(args.actor.type === 'agent' ? { initiatingBindingId: args.actor.bindingId } : {}),\n })\n : retireTerminalSessions;\n const transitionService = workItemsReady\n ? new FactoryTransitionService({\n configVersion,\n boards: this.#boards,\n storage: workItemsStorage,\n audit: auditDomain,\n autoApprovePlans: async ({ orgId, factoryProjectId }) => {\n await factoryProjectsStorage.ensureReady();\n const project = await factoryProjectsStorage.get({ orgId, id: factoryProjectId });\n return project?.autoApprovePlans ?? false;\n },\n ...(onTerminalStage ? { onTerminalStage } : {}),\n ...(githubIntegration\n ? {\n onAccepted: (args: { orgId: string; factoryProjectId: string; item: WorkItemRow }) =>\n reconcileGithubAcceptanceLabels(\n githubIntegration,\n sourceControlStorage.forIntegration(githubIntegration.id),\n args,\n ),\n }\n : {}),\n })\n : undefined;\n const projectRoutes = new ProjectRoutes({\n auth: routeAuth,\n projects: factoryProjectsStorage,\n sourceControl: sourceControlStorage,\n versionControlIntegrationIds: integrations\n .filter(integration => integration.versionControl)\n .map(integration => integration.id),\n ...(githubIntegration || gitlabIntegration\n ? {\n resolveRepository: async ({ integrationId, orgId, userId, installationId, externalId, slug }) => {\n if (githubIntegration && integrationId === githubIntegration.id) {\n const installation = await githubIntegration.sourceControlStorage.installations.get({\n orgId,\n id: installationId,\n });\n if (!installation) return null;\n const repositories = await githubIntegration.listInstallationRepos(Number(installation.externalId));\n const selected = repositories.find(repo => repo.id.toString() === externalId && repo.fullName === slug);\n if (!selected) return null;\n return githubIntegration.sourceControlStorage.repositories.upsert({\n orgId,\n input: {\n installationId,\n externalId,\n slug: selected.fullName,\n defaultBranch: isValidGitRef(selected.defaultBranch) ? selected.defaultBranch : 'main',\n providerMetadata: { private: selected.private, owner: selected.owner },\n },\n });\n }\n\n if (\n !gitlabIntegration?.intake ||\n !gitlabIntegration.versionControl ||\n integrationId !== gitlabIntegration.id\n )\n return null;\n const handle = sourceControlStorage.forIntegration(gitlabIntegration.id);\n const installation = await handle.installations.get({ orgId, id: installationId });\n if (!installation) return null;\n const sources = await gitlabIntegration.intake.listSources({ orgId, userId });\n const selected = sources.find(\n source =>\n source.name === slug &&\n typeof source.metadata?.projectId === 'string' &&\n source.metadata.projectId === externalId &&\n source.metadata.connectionId === installation.externalId,\n );\n if (!selected) return null;\n const [repository] = await gitlabIntegration.versionControl.registerRepositories({\n orgId,\n installationId,\n repositories: [\n {\n externalId,\n slug,\n defaultBranch:\n typeof selected.metadata?.defaultBranch === 'string' &&\n isValidGitRef(selected.metadata.defaultBranch)\n ? selected.metadata.defaultBranch\n : 'main',\n metadata: selected.metadata,\n },\n ],\n });\n return repository ?? null;\n },\n }\n : {}),\n ...(sessionRetirement ? { sessionRetirement } : {}),\n ...(workItemsReady ? { workItems: workItemsStorage } : {}),\n });\n const factoryProcessor = workItemsReady\n ? new FactoryPhaseStateProcessor({\n configVersion,\n storage: workItemsStorage,\n boards: this.#boards,\n ...(transitionService ? { transitionService } : {}),\n ...(githubIntegration\n ? {\n recordPullRequestProvenance: (input: Parameters<typeof recordFactoryPullRequestProvenance>[4]) =>\n recordFactoryPullRequestProvenance(\n githubIntegration,\n sourceControlStorage.forIntegration('github'),\n integrationStorage.forIntegration('github'),\n workItemsStorage,\n input,\n ),\n }\n : {}),\n messageReader: {\n listMessages: async input => {\n const memory = await storage.getMastraStorage().getStore('memory');\n return memory ? memory.listMessages(input) : { messages: [], hasMore: false };\n },\n },\n })\n : undefined;\n\n // Boot assertion: an active integration that signs OAuth `state` needs a\n // replica-stable signer — a per-process random secret silently breaks the\n // OAuth callback on any replica that didn't sign the state. Fail loud now\n // instead. (The built-ins also assert this inside their readiness gates.)\n for (const { integration } of integrationRegistrations) {\n if (integration.requiresStableStateSigner && !stateSigner.stable) {\n throw new Error(\n `MastraFactory: integration '${integration.id}' signs OAuth state and requires a ` +\n `replica-stable state secret, but none is configured. Set 'stateSecret' on the factory config.`,\n );\n }\n }\n\n // The SDK needs to know which backend the injected Mastra store uses\n // (its own `instanceof` detection breaks when the dependency graph holds\n // duplicate package copies). Resolve it by walking the FactoryStorage\n // prototype chain by class name — the factory can't import the concrete\n // classes since '@mastra/pg' / '@mastra/libsql' are the user's choice.\n const mastraStorageBackend = (() => {\n for (let proto = Object.getPrototypeOf(storage); proto; proto = Object.getPrototypeOf(proto)) {\n if (proto.constructor?.name === 'PgFactoryStorage') return 'pg' as const;\n if (proto.constructor?.name === 'LibSQLFactoryStorage') return 'libsql' as const;\n }\n return undefined;\n })();\n\n // Integrations contributing tools to agent sessions: org-scoped\n // `agentTools` (resolved per request) + session-scoped `sessionTools`.\n const toolIntegrations = integrationRegistrations.filter(\n ({ integration }) => integration.agentTools || integration.sessionTools,\n );\n const sourceControlToolProviders = integrations.flatMap(integration =>\n integration.versionControl\n ? [\n {\n id: integration.id,\n versionControl: integration.versionControl,\n storage: sourceControlStorage.forIntegration(integration.id),\n },\n ]\n : [],\n );\n\n // Build the real production controller (agents, modes, tools, memory, OM,\n // MCP, providers) — identical to the terminal app. Agent state lives in\n // the storage backend's Mastra store alongside the Factory app tables —\n // one shared database for all users, separated by `resourceId` scoping.\n const prepared = await timedPhase('prepare.controllerMount', () =>\n prepareAgentControllerMount({\n controllerId: CONTROLLER_ID,\n coAuthor: { name: 'mastra-platform[bot]' },\n workspace: createWorkspaceFactory({\n ...(sandboxConfig ? { sandbox: sandboxConfig } : {}),\n ...(this.#config.sandboxStart ? { sandboxStart: this.#config.sandboxStart } : {}),\n sourceControls: integrations.flatMap(integration =>\n integration.versionControl\n ? [\n {\n id: integration.id,\n versionControl: integration.versionControl,\n storage: sourceControlStorage.forIntegration(integration.id),\n ...(integration.id === 'github' && githubIntegration ? { github: githubIntegration } : {}),\n },\n ]\n : [],\n ),\n ...(factoryProjectsStorage ? { projects: factoryProjectsStorage } : {}),\n ...(workItemsStorage ? { workItems: workItemsStorage } : {}),\n workspaceRegistry,\n }),\n disableGithubSignals: true,\n // Memory settings live in the factory's `memory-settings` app table (per\n // org/user), so the host machine's TUI settings.json must not seed them.\n disableSettingsOmSeed: true,\n hostInstructions: async ({ requestContext }) => {\n const context = requestContext.get('controller') as\n | AgentControllerRequestContext<MastraCodeState>\n | undefined;\n if (parseSupervisorResourceId(context?.resourceId)) return SUPERVISOR_INSTRUCTIONS;\n // The SDK resolves this callback before it loads repository\n // AGENTS.md/CLAUDE.md. A controller recreated after restart has\n // only initialState, and a partially persisted one can keep\n // `factoryProjectId` while `untrustedCheckout` is gone, so the\n // presence of the project id alone proves nothing. Heal whenever any\n // trust field the recovery writes is missing, now, not later when\n // the agent's tools are assembled. Unbound sessions never gain these\n // fields and pay one binding lookup per prompt.\n if (context?.threadId && context.resourceId) {\n const current = context.getState();\n const trustStateComplete =\n Boolean(current.factoryProjectId) &&\n Boolean(current.factoryOrgId) &&\n typeof current.untrustedCheckout === 'boolean';\n if (!trustStateComplete) {\n const recovered = await resolveFactorySessionAddress({\n requestContext,\n storage: workItemsStorage,\n forceBindingLookup: true,\n ...(storage.isDomainReady('source-control') ? { sessions: sourceControlSessions } : {}),\n });\n if (recovered?.binding) {\n const state = context.getState();\n if (\n state.factoryProjectId !== recovered.binding.factoryProjectId ||\n (recovered.binding.role === 'review' && state.untrustedCheckout !== true)\n ) {\n throw new Error(\n 'Factory review session security state could not be restored before prompt creation.',\n );\n }\n }\n }\n }\n return undefined;\n },\n // A factory reads the repository it works on and its skill, never the\n // ~/.claude instructions of whoever hosts the process. On the controller\n // rather than per session, so webhook-recreated sessions keep it too.\n // Blank project identity for the same reason: the SDK's defaults seed\n // sessions with the HOST process's own project root / name / branch,\n // which must never reach a hosted session's prompt. Repo-backed\n // sessions get their real workdir pinned by workspace resolution;\n // chat-only sessions legitimately have no project.\n // Factory sessions also start fail-closed on org classification: the\n // unresolved marker is set at birth, a successful org seed clears it,\n // and a resolved `factoryOrgId` always takes precedence — so a failed\n // (best-effort) seed can never leave a session classified as local.\n initialState: {\n skipGlobalInstructions: true,\n factoryOrgUnresolved: true,\n projectPath: '',\n projectName: '',\n gitBranch: '',\n },\n storage: storage.getMastraStorage(),\n ...(mastraStorageBackend ? { storageBackend: mastraStorageBackend } : {}),\n ...(factoryProcessor ? { inputProcessors: [factoryProcessor] } : {}),\n ...(vector ? { vector } : {}),\n ...(toolIntegrations.length > 0 ||\n sourceControlToolProviders.length > 0 ||\n (workItemsStorage && transitionService)\n ? {\n extraTools: async ({ requestContext }: { requestContext: RequestContext }) => {\n const tools: IntegrationTools = {};\n const toolOwners = new Map<string, string>();\n const mergeTools = (ownerId: string, contributed: IntegrationTools) => {\n for (const [name, tool] of Object.entries(contributed)) {\n const owner = toolOwners.get(name);\n if (owner) {\n throw new Error(\n `MastraFactory: integration tool '${name}' from '${ownerId}' conflicts with '${owner}'.`,\n );\n }\n toolOwners.set(name, ownerId);\n tools[name] = tool;\n }\n };\n if (storage.isDomainReady('source-control')) {\n mergeTools(\n 'source-control',\n createSourceControlTools({\n requestContext,\n providers: sourceControlToolProviders,\n audit: auditDomain,\n }),\n );\n }\n if (workItemsStorage && transitionService) {\n mergeTools(\n 'factory',\n await createFactoryTransitionTools({\n requestContext,\n storage: workItemsStorage,\n transitionService,\n boards: this.#boards,\n // Heals crash-resumed sessions: recovered addresses re-seed\n // projectRepositoryId/baseRef from the source session record.\n // Only offered while the source-control domain is ready — a\n // throwing lookup would abort recovery's catch block and also\n // skip the metadata baseRef fallback.\n ...(storage.isDomainReady('source-control') ? { sessions: sourceControlSessions } : {}),\n }),\n );\n // The supervisor session has no seat, so it never gets the\n // transition tool above; it gets the read surface instead,\n // and only once the caller's org is shown to own the project.\n const supervisorScope = await resolveSupervisorScope({\n requestContext,\n projects: factoryProjectsStorage,\n });\n if (supervisorScope) {\n const userId = getFactoryAuthUserId(getFactoryAuthUserFromContext(requestContext));\n mergeTools(\n 'factory-supervisor',\n createFactorySupervisorReadTools({\n scope: supervisorScope,\n workItems: workItemsStorage,\n boards: this.#boards,\n comments: workItemCommentsStorage,\n audit: auditStorage,\n messageReader: {\n listMessages: async input => {\n const memory = await storage.getMastraStorage().getStore('memory');\n return memory ? memory.listMessages(input) : { messages: [], hasMore: false };\n },\n },\n }),\n );\n if (userId) {\n mergeTools(\n 'factory-supervisor-write',\n createFactorySupervisorWriteTools({\n scope: supervisorScope,\n userId,\n workItems: workItemsStorage,\n audit: auditDomain,\n transitionService,\n ...(githubIntegration\n ? {\n reconcileAcceptanceLabels: (args: {\n orgId: string;\n factoryProjectId: string;\n item: WorkItemRow;\n }) =>\n reconcileGithubAcceptanceLabels(\n githubIntegration,\n sourceControlStorage.forIntegration(githubIntegration.id),\n args,\n ),\n }\n : {}),\n messageSession: ({ sessionId, message, delivery }) =>\n messageWorkerSession({\n controller: prepared.base.controller,\n sessionId,\n message,\n delivery,\n ...(requestContext ? { requestContext } : {}),\n }),\n }),\n );\n }\n }\n }\n for (const { integration, ready, ensureReady } of toolIntegrations) {\n if (!ready && ensureReady) {\n try {\n await ensureReady();\n } catch {\n continue;\n }\n }\n if (integration.agentTools) {\n mergeTools(integration.id, await integration.agentTools({ requestContext }));\n }\n if (integration.sessionTools) {\n mergeTools(integration.id, integration.sessionTools({ requestContext }));\n }\n }\n return tools;\n },\n }\n : {}),\n postToolObserver: async (toolContext: IntegrationPostToolContext) => {\n const requestContext = (toolContext.context as { requestContext?: RequestContext } | undefined)\n ?.requestContext;\n if (requestContext) {\n await observeAgentGitAction({\n audit: auditDomain,\n toolContext: { ...toolContext, context: requestContext },\n });\n }\n await Promise.all(\n integrations.map(async integration => {\n if (!integration.postToolObserver) return;\n try {\n await integration.postToolObserver({ toolContext, requestContext });\n } catch (error) {\n console.warn(`[factory] Integration '${integration.id}' post-tool observer failed:`, error);\n }\n }),\n );\n },\n ...(pubsub ? { pubsub, crossProcessPubSub: true } : {}),\n buildApiRoutes: ({ controller, authStorage }: BuildApiRoutesDeps) => [\n // Public `/auth/*` routes (login/callback/logout/me). Folded in as\n // `apiRoutes` (not plain Hono routes) because the entry can't touch the\n // Hono app the deployer generates. `requiresAuth: false`; the gate\n // skips `/auth/*`.\n ...(auth ? buildAuthRoutes(auth, { publicUrl: publicOrigin }) : []),\n ...new TelemetryRoutes({ auth: routeAuth, providerName: auth?.name, publicOrigin, allowedOrigins }).routes(),\n // Custom `/web/*` routes (fs / config / integrations / factory / audit).\n ...assembleFactoryApiRoutes({\n controllerId: CONTROLLER_ID,\n controller,\n liveSessions: liveSessionsTouchingTheFeed(controller, eventBus),\n auth: routeAuth,\n ...(auth && isUserProvider(auth) ? { users: auth } : {}),\n authStorage,\n audit: auditDomain,\n publicOrigin,\n stateSigner,\n sandbox: sandboxConfig,\n sessionRetirement,\n factoryStorage: storage,\n integrationStorage,\n sourceControlStorage,\n domains,\n feed: commentsDomain,\n integrations: integrationRegistrations,\n intakeReady,\n factoryReady,\n knowledgeEnabled,\n configVersion,\n boardRegistry: this.#boards,\n factoryTransitionService: transitionService,\n onFactoryRuntime: ({ transitionService: runtimeTransitionService, prepareBinding }) => {\n this.#dispatcher ??= new FactoryDecisionDispatcher({\n controller,\n transitionService: runtimeTransitionService,\n storage: storage.getDomain<WorkItemsStorage>('work-items'),\n audit: auditDomain,\n boards: this.#boards,\n maxInFlight: this.#config.dispatcher?.maxInFlight,\n isAutoRunEnabled: async ({ orgId, factoryProjectId }) => {\n await factoryProjectsStorage.ensureReady();\n const project = await factoryProjectsStorage.get({ orgId, id: factoryProjectId });\n return project?.autoRunEnabled ?? false;\n },\n autoApprovePlans: async ({ orgId, factoryProjectId }) => {\n await factoryProjectsStorage.ensureReady();\n const project = await factoryProjectsStorage.get({ orgId, id: factoryProjectId });\n return project?.autoApprovePlans ?? false;\n },\n reconcileToolResults: () => factoryProcessor?.reconcileAllBoundThreads() ?? Promise.resolve(),\n prepareBinding,\n refreshManagedMemorySettings: ({ binding, session }) =>\n refreshFactorySessionMemorySettings(session, {\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n projects: factoryProjectsStorage,\n memorySettings: memorySettingsStorage,\n }),\n feedReader: new FactoryFeedReader(workItemCommentsStorage),\n primeCredentials: tenant => primeTenantCredentials({ tenant, credentials: modelCredentialsStorage }),\n resolveLinkedWorkItemParentId: async ({ orgId, factoryProjectId, decision }) => {\n if (decision.source !== 'github-pr') return null;\n const repositoryId = decision.metadata?.githubRepositoryId;\n const pullRequestNumber = decision.metadata?.githubPullRequestNumber;\n if (typeof repositoryId !== 'number' || typeof pullRequestNumber !== 'number') return null;\n return resolveFactoryPullRequestParentWorkItemId(\n integrationStorage.forIntegration<\n Record<string, unknown>,\n Record<string, unknown>,\n FactoryPullRequestProvenanceData\n >('github'),\n { orgId, factoryProjectId, repositoryId, pullRequestNumber },\n );\n },\n });\n },\n }),\n ...projectRoutes.routes(),\n ...auditDomain.routes(),\n ...commentsDomain.routes(),\n // Connect/reconnect session minting for Platform-managed providers.\n // Server-side because only the deploy holds Platform machine\n // credentials; the SPA runs the OAuth popup with the minted token.\n ...(hasPlatformCredentials()\n ? buildPlatformConnectRoutes({\n auth: routeAuth,\n client: new PlatformApiClient(platformApiClientConfigFromEnv()),\n })\n : []),\n ],\n buildServerConfig: () => {\n const cors = allowedOrigins.length ? { cors: { origin: allowedOrigins, credentials: true } } : {};\n // Log route errors with method/path/stack and answer with structured\n // JSON instead of an opaque `Internal Server Error`. Applied by the\n // deployer to both the top-level app and the custom-route sub-app.\n const onError = { onError: handleServerError };\n // Same-origin SPA: when a vite build is present (see resolveUiDistDir),\n // serve it at `/` from this server. Mounted last so the auth gate (when\n // enabled) covers it; it always passes `/api`, `/web`, `/auth` through.\n const uiDist = resolveUiDistDir();\n const spa = uiDist ? [createSpaStaticMiddleware(uiDist)] : [];\n if (!auth) {\n // Auth disabled: no gate. Still prime the sentinel-local custom\n // provider snapshot so model calls see DB rows, then SPA + CORS.\n return {\n middleware: [\n createCustomProvidersPrimer({ auth: routeAuth, storage: customProvidersStorage, authEnabled: false }),\n ...spa,\n ],\n ...cors,\n ...onError,\n };\n }\n\n // Ordered middleware. The deployer applies these AFTER its context\n // middleware sets `c.set('mastra', mastra)` and BEFORE routes, so:\n // 1. gate — validates the auth session, stashes the user, and 401s /\n // redirects unauthenticated requests. Skips public `/auth/*`.\n // 2. primers — hydrate the caller's model-credential and custom\n // provider snapshots so the request's first model call\n // resolves tenant credentials and custom providers.\n // 3. spa — serves the built UI for everything the server doesn't own.\n // `auth` also lands on `server.auth` so the core auth middleware (and\n // Studio's dual-auth routing — see `studio.auth` on the returned args)\n // authenticates core `/api/*` routes with the same provider.\n return {\n auth,\n middleware: [\n createFactoryAuthGate(auth),\n createTenantCredentialPrimer({ auth: routeAuth, credentials: modelCredentialsStorage }),\n createCustomProvidersPrimer({\n auth: routeAuth,\n storage: customProvidersStorage,\n authEnabled: Boolean(auth),\n }),\n ...spa,\n ],\n ...cors,\n ...onError,\n };\n },\n }),\n );\n\n prepared.base.controller.onSessionCreated(session => {\n observeSessionFilesystem(session, {\n filesystem: filesystemStorage,\n sourceControl: { sessions: sourceControlSessions },\n });\n observeSessionFirstMessage(session, {\n sourceControl: { sessions: sourceControlSessions },\n });\n observeSessionFirstExec(session, {\n sourceControl: { sessions: sourceControlSessions },\n });\n observeSessionThreadTitle(session, {\n sourceControl: { sessions: sourceControlSessions },\n });\n observeSessionRunEnd(session, { audit: auditDomain });\n });\n\n // Supervisor sessions carry their project in the resourceId; re-stamp\n // scope, instructions and factory defaults on every (re)creation so a\n // restarted server heals the in-memory state.\n prepared.base.controller.onSessionCreated(\n session =>\n hydrateSupervisorSession(session, {\n projects: factoryProjectsStorage,\n memorySettings: memorySettingsStorage,\n }).catch(error => {\n console.warn('[Factory Supervisor] Failed to hydrate supervisor session', {\n error: error instanceof Error ? error.message : String(error),\n });\n }),\n { blocking: true },\n );\n\n // Blocking: `createSession` awaits this seed, so when hydration succeeds\n // a session's first run starts with the owner's stored OM settings.\n // Best-effort — failures are logged inside the helper, never thrown, and\n // the session then falls back to its persisted/default OM configuration.\n prepared.base.controller.onSessionCreated(\n session =>\n hydrateSessionMemorySettings(session, {\n sourceControl: { sessions: sourceControlSessions },\n projects: factoryProjectsStorage,\n memorySettings: memorySettingsStorage,\n }),\n { blocking: true },\n );\n\n // Personal model packs seed interactive user sessions only. Active Factory\n // run bindings are excluded and continue to use the project default model.\n prepared.base.controller.onSessionCreated(\n session =>\n hydrateSessionModelPack(session, {\n sourceControl: { sessions: sourceControlSessions },\n workItems: workItemsStorage,\n modelPacks: modelPacksStorage,\n }),\n { blocking: true },\n );\n\n this.#prepared = prepared;\n this.#factoryProcessor = factoryProcessor;\n\n // Chat-platform channels (Slack, Discord, …) contributed by integrations,\n // attached to the mounted controller so inbound platform messages reach\n // the same agents the web UI drives. READY integrations only — readiness\n // means the `channel-identity` domain's `init()` succeeded, so its link\n // table is queryable. Without it a sender can't be resolved to a tenant,\n // and attaching anyway would dispatch runs on default credentials.\n const channelRegistrations = integrationRegistrations.filter(\n ({ integration, ready }) => ready && integration.channels,\n );\n // `setChannels` replaces rather than merges, so a second provider would\n // silently never receive a message. Fail loud instead.\n if (channelRegistrations.length > 1) {\n throw new Error(\n `MastraFactory: integrations [${channelRegistrations\n .map(({ integration }) => integration.id)\n .join(', ')}] all provide channels, but only one may. Remove all but one.`,\n );\n }\n for (const { integration } of channelRegistrations) {\n const context = buildIntegrationContext(\n {\n controller: prepared.base.controller,\n publicOrigin,\n auth: routeAuth,\n stateSigner,\n sandbox: sandboxConfig,\n factoryStorage: storage,\n integrationStorage,\n sourceControlStorage,\n integrations: integrationRegistrations,\n configVersion,\n boardRegistry: this.#boards,\n factoryReady,\n domains,\n feed: commentsDomain,\n ...(githubIntegration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n // Integrations return a channels CONFIG; the factory owns construction.\n prepared.base.controller.setChannels(new AgentControllerChannels(integration.channels!(context)));\n }\n\n // Feed publishers mirror web-feed comments outward (a chat bridge, a\n // webhook, an issue tracker). Independent of channels(): an integration may\n // publish without owning a chat channel. READY integrations only — the\n // array is held by reference by CommentsDomain (see `feedPublishers` above),\n // so pushing here wires the publisher into comment dispatch.\n for (const { integration } of integrationRegistrations.filter(\n ({ integration, ready }) => ready && integration.feedPublisher,\n )) {\n const context = buildIntegrationContext(\n {\n controller: prepared.base.controller,\n publicOrigin,\n auth: routeAuth,\n stateSigner,\n sandbox: sandboxConfig,\n factoryStorage: storage,\n integrationStorage,\n sourceControlStorage,\n integrations: integrationRegistrations,\n configVersion,\n boardRegistry: this.#boards,\n factoryReady,\n domains,\n feed: commentsDomain,\n ...(githubIntegration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n const publisher = integration.feedPublisher!(context);\n if (publisher) feedPublishers.push(publisher);\n }\n\n // Integration lifecycle workers (e.g. polling an upstream without\n // webhooks): collected from READY integrations only, folded into the\n // constructor args so `new Mastra(...)` merges them with the default\n // workers and `finalize()`'s `startWorkers()` starts them alongside the\n // built-ins. Never passed for the disabled/not-ready case — a worker for\n // an unavailable integration must not run.\n const integrationWorkers = [\n ...(factoryReady\n ? [\n new FactorySupervisorHealthWorker({\n projects: factoryProjectsStorage,\n workItems: workItemsStorage,\n boards: this.#boards,\n }),\n ]\n : []),\n ...integrationRegistrations\n .filter(({ integration, ready }) => ready && integration.workers)\n .flatMap(({ integration }) =>\n integration.workers!(\n buildIntegrationContext(\n {\n controller: prepared.base.controller,\n publicOrigin,\n auth: routeAuth,\n stateSigner,\n sandbox: sandboxConfig,\n factoryStorage: storage,\n integrationStorage,\n sourceControlStorage,\n integrations: integrationRegistrations,\n configVersion,\n boardRegistry: this.#boards,\n factoryReady,\n domains,\n feed: commentsDomain,\n ...(githubIntegration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n ),\n ),\n ),\n ];\n\n return {\n ...prepared.mastraArgs,\n // Same provider on `studio.auth` as on `server.auth` (buildServerConfig):\n // deployed factories must authenticate BOTH plain API callers and Studio\n // requests (`x-mastra-client-type: studio` routes to `studio.auth`).\n ...(auth ? { studio: { auth } } : {}),\n ...(integrationWorkers.length > 0 ? { workers: integrationWorkers } : {}),\n };\n }\n\n /**\n * Post-construct boot: initialize the controller (which inherits the\n * constructed Mastra's storage) and start its workers. Call AFTER the entry\n * has run `new Mastra(prepare()'s args)`.\n */\n async finalize(): Promise<void> {\n if (!this.#prepared) {\n throw new Error('MastraFactory.finalize() called before prepare()');\n }\n await timedPhase('finalize.controller', () => this.#prepared!.finalize());\n await timedPhase(\n 'finalize.reconcileBoundThreads',\n () => this.#factoryProcessor?.reconcileAllBoundThreads() ?? Promise.resolve(),\n );\n this.#dispatcher?.start();\n }\n\n /** Stop Factory-owned background dispatch before the host process shuts down. */\n async shutdown(): Promise<void> {\n await this.#dispatcher?.stop();\n }\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { BoardDefinitionError, createBoardRegistry, defaultBoards, defineBoard,
2
2
  export type { BoardDefinition, BoardPhaseDefinition, BoardRegistry, BoardTransition, BoardTransitionPolicy, BoardTransitionPolicyContext, BoardTransitionPolicyResult, InstalledBoard, ReviewBoardPhase, WorkBoardPhase, } from './boards/index.js';
3
3
  export { MastraFactory } from './factory.js';
4
4
  export type { MastraArgs, MastraFactoryConfig, MastraFactorySandboxConfig, FactorySandboxStart } from './factory.js';
5
+ export type { GithubRuleOverrides } from './integrations/github/default-rules.js';
5
6
  export type { FactorySandboxContext, SessionSetupGate, SessionSetupRun } from './sandbox/session-sandbox.js';
6
7
  export { ChannelIdentityStorage } from './storage/domains/channel-identity/base.js';
7
8
  export type { ChannelAccountLink, ChannelAccountLinkEntry, ChannelAccountLinkKey, ChannelAccountLinkNames, } from './storage/domains/channel-identity/base.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,cAAc,EACd,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACrH,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC7G,OAAO,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AACpF,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACxE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,sCAAsC,EAAE,MAAM,wBAAwB,CAAC;AAC/G,YAAY,EACV,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,EAC7B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,cAAc,EACd,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGrH,YAAY,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAClF,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC7G,OAAO,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AACpF,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACxE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,sCAAsC,EAAE,MAAM,wBAAwB,CAAC;AAC/G,YAAY,EACV,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,EAC7B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"session-subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/session-subscriptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAWnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAyI1D,wBAAsB,oCAAoC,CACxD,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,mBAAmB,GAAG,eAAe,EAC7C,MAAM,EAAE,iBAAiB,+BAY1B;AAED,wBAAsB,wCAAwC,CAC5D,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,iBAAiB,mBAM1B;AAED,wBAAsB,0BAA0B,CAC9C,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC5C,MAAM,EAAE,iBAAiB,uEAa1B;AAED,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjH;AAED,wBAAgB,6BAA6B,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB;;;;;;;;;;;;;;;;;EA0CtG;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,sBASA"}
1
+ {"version":3,"file":"session-subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/session-subscriptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAWnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAyI1D,wBAAsB,oCAAoC,CACxD,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,mBAAmB,GAAG,eAAe,EAC7C,MAAM,EAAE,iBAAiB,+BAY1B;AAED,wBAAsB,wCAAwC,CAC5D,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,iBAAiB,mBAM1B;AAED,wBAAsB,0BAA0B,CAC9C,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC5C,MAAM,EAAE,iBAAiB,uEAa1B;AAED,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjH;AAED,wBAAgB,6BAA6B,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB;;;;;;;;;;;;;;;;;EA0CtG;AAID,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,sBAgBA"}
@@ -205,8 +205,14 @@ function createGithubSubscriptionTools(requestContext, github) {
205
205
  })
206
206
  };
207
207
  }
208
+ const GITHUB_PULL_REQUEST_URL = /^https:\/\/github\.com\/[^\s/]+\/[^\s/]+\/pull\/\d+\/?$/;
208
209
  function parseCreatedPullRequest(context) {
209
- if (context.toolName !== "execute_command" || context.error) return void 0;
210
+ if (context.error) return void 0;
211
+ if (context.toolName === "source_control_create_change_request") {
212
+ const url = context.output?.url;
213
+ return typeof url === "string" && GITHUB_PULL_REQUEST_URL.test(url) ? url.replace(/\/$/, "") : void 0;
214
+ }
215
+ if (context.toolName !== "execute_command") return void 0;
210
216
  const command = context.input?.command;
211
217
  if (typeof command !== "string" || !runsPullRequestCreate(command)) return void 0;
212
218
  const output = context.output;
@@ -1 +1 @@
1
- {"version":3,"file":"session-subscriptions.js","names":[],"sources":["../../../src/integrations/github/session-subscriptions.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from '../../auth.js';\nimport { runsPullRequestCreate } from '../../session/shell-commands.js';\nimport type {\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GithubIntegration } from './integration.js';\nimport { getGithubPat } from './pat.js';\nimport { subscribeToPullRequest, unsubscribeFromPullRequest } from './subscriptions.js';\nimport { getRegisteredGithubPatKind, injectGithubToken } from './token-refresh.js';\n\ntype RepositorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\n/**\n * The host-authenticated user placed on the request context under the `user`\n * key, read through the host's own normalizer. A local mirror of that shape\n * used to live here; it silently missed the provider shapes the normalizer\n * knows about, which turned every subscription tool into a no-op for those\n * users instead of an error anybody could see.\n */\nfunction sessionUserId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthUserId(getFactoryAuthUserFromContext(requestContext));\n}\n\nfunction sessionOrgId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n}\n\nconst pullRequestInputSchema = z.object({\n pullRequest: z.union([z.number().int().positive(), z.string().min(1)]),\n});\n\nconst TRIAGE_COMMENT_MARKER = '<!-- mastra-factory-triage -->';\nconst triageCommentInputSchema = z.object({\n issueNumber: z.number().int().positive(),\n body: z.string().startsWith(TRIAGE_COMMENT_MARKER),\n});\n\nconst triageCommentLocks = new Map<string, Promise<void>>();\n\nasync function serializeTriageComment<T>(key: string, operation: () => Promise<T>): Promise<T> {\n const previous = triageCommentLocks.get(key) ?? Promise.resolve();\n let release: (() => void) | undefined;\n const current = new Promise<void>(resolve => {\n release = resolve;\n });\n const queued = previous.then(() => current);\n triageCommentLocks.set(key, queued);\n await previous;\n try {\n return await operation();\n } finally {\n release!();\n if (triageCommentLocks.get(key) === queued) triageCommentLocks.delete(key);\n }\n}\n\ninterface SessionTarget {\n context: AgentControllerRequestContext<RepositorySessionState>;\n projectRepository: ProjectRepository;\n connection: ProjectSourceControlConnection;\n installation: SourceControlInstallation;\n repository: SourceControlRepository;\n orgId: string;\n userId: string;\n}\n\nfunction parsePullRequest(value: number | string, expectedRepo: string): number {\n if (typeof value === 'number') return value;\n if (/^\\d+$/.test(value)) return Number(value);\n const match = value.match(/^https:\\/\\/github\\.com\\/([^/]+\\/[^/]+)\\/pull\\/(\\d+)\\/?$/i);\n if (!match || match[1]!.toLowerCase() !== expectedRepo.toLowerCase()) {\n throw new Error(`Pull request must belong to ${expectedRepo}.`);\n }\n return Number(match[2]);\n}\n\n/**\n * Whether the current request comes from a session that GitHub subscriptions\n * can ever apply to: an authenticated org user on a GitHub-project session\n * with an active thread. Mirrors the gate in `resolveSessionTarget` without\n * throwing, for passive callers that should no-op instead of erroring.\n */\nfunction isGithubProjectSession(requestContext: RequestContext): boolean {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n return Boolean(\n context?.threadId &&\n context.getState().projectRepositoryId &&\n sessionOrgId(requestContext) &&\n sessionUserId(requestContext),\n );\n}\n\nasync function resolveSessionTarget(requestContext: RequestContext, github: GithubIntegration): Promise<SessionTarget> {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const orgId = sessionOrgId(requestContext);\n const userId = sessionUserId(requestContext);\n const projectRepositoryId = context?.getState().projectRepositoryId;\n if (!context || !context.threadId || !projectRepositoryId || !orgId || !userId) {\n throw new Error('GitHub subscriptions require an authenticated repository session with an active thread.');\n }\n\n const projectRepository = await github.sourceControlStorage.projectRepositories.get({\n orgId,\n id: projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Project repository not found for this organization.');\n const connection = await github.sourceControlStorage.connections.get({ orgId, id: projectRepository.connectionId });\n if (!connection) throw new Error('Source-control connection not found for this organization.');\n const repository = await github.sourceControlStorage.repositories.get({ orgId, id: projectRepository.repositoryId });\n if (!repository) throw new Error('Repository not found for this organization.');\n const installation = await github.sourceControlStorage.installations.get({ orgId, id: connection.installationId });\n if (!installation) throw new Error('Source-control installation not found for this organization.');\n return { context, projectRepository, connection, installation, repository, orgId, userId };\n}\n\nasync function verifyPullRequest(target: SessionTarget, pullRequest: number, github: GithubIntegration) {\n const [owner, repo] = target.repository.slug.split('/');\n if (!owner || !repo) throw new Error('GitHub repository is invalid.');\n const octokit = github.getInstallationOctokit(Number(target.installation.externalId));\n const { data } = await octokit.pulls.get({ owner, repo, pull_number: pullRequest });\n if (String(data.base.repo.id) !== target.repository.externalId)\n throw new Error('Pull request repository does not match the active project repository.');\n}\n\nasync function subscriptionInput(target: SessionTarget, pullRequestNumber: number) {\n return {\n orgId: target.orgId,\n installationExternalId: target.installation.externalId,\n projectRepositoryId: target.projectRepository.id,\n repositoryExternalId: target.repository.externalId,\n repositorySlug: target.repository.slug,\n changeRequestId: String(pullRequestNumber),\n sessionId: target.context.session.id,\n ownerId: target.context.session.ownerId,\n resourceId: target.connection.factoryProjectId,\n threadId: target.context.threadId!,\n sessionScope: target.context.scope,\n source: 'explicit-tool' as const,\n subscribedByUserId: target.userId,\n };\n}\n\nexport async function subscribeCurrentSessionToPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n source: 'auto-gh-pr-create' | 'explicit-tool',\n github: GithubIntegration,\n) {\n // The auto path observes every successful `gh pr create` in every session,\n // including local and non-GitHub-project sessions where subscriptions can\n // never apply. Skip silently there; only the explicit tool should surface\n // \"this session cannot subscribe\" as an error.\n if (source === 'auto-gh-pr-create' && !isGithubProjectSession(requestContext)) return undefined;\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await verifyPullRequest(target, number, github);\n await subscribeToPullRequest({ ...(await subscriptionInput(target, number)), source }, github.integrationStorage);\n return number;\n}\n\nexport async function unsubscribeCurrentSessionFromPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await unsubscribeFromPullRequest(await subscriptionInput(target, number), github.integrationStorage);\n return number;\n}\n\nexport async function upsertFactoryTriageComment(\n requestContext: RequestContext,\n input: { issueNumber: number; body: string },\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const installationId = Number(target.installation.externalId);\n if (!Number.isSafeInteger(installationId) || installationId <= 0) throw new Error('GitHub installation is invalid.');\n return serializeTriageComment(`${installationId}:${target.repository.externalId}:${input.issueNumber}`, () =>\n github.upsertFactoryTriageComment({\n installationId,\n repository: target.repository.slug,\n issueNumber: input.issueNumber,\n body: input.body,\n }),\n );\n}\n\nexport async function refreshGithubToken(requestContext: RequestContext, github: GithubIntegration): Promise<void> {\n const target = await resolveSessionTarget(requestContext, github);\n // `GH_TOKEN` feeds the `gh` CLI, so a configured org PAT wins over a minted\n // installation token (which 403s on integration-restricted endpoints). The\n // workspace records which PAT kind the sandbox was provisioned with, so a\n // review-board sandbox keeps its reviewer token on refresh.\n const pat = await getGithubPat(\n () => github.integrationStorage,\n target.orgId,\n getRegisteredGithubPatKind(requestContext),\n );\n if (pat) {\n injectGithubToken(requestContext, pat);\n return;\n }\n const access = await github.versionControl.getRepositoryAccess({\n orgId: target.orgId,\n repositoryId: target.repository.id,\n });\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session.');\n injectGithubToken(requestContext, token);\n}\n\nexport function createGithubSubscriptionTools(requestContext: RequestContext, github: GithubIntegration) {\n if (!isGithubProjectSession(requestContext)) return {};\n\n return {\n github_refresh_token: createTool({\n id: 'github_refresh_token',\n description:\n 'Refresh GitHub CLI authentication in the active Factory sandbox. Use this after a gh command fails because authentication is expired, invalid, or missing. It installs a fresh GH_TOKEN for subsequent sandbox commands. After this tool succeeds, retry the failed gh command. Takes no arguments and never returns the token.',\n inputSchema: z.object({}),\n execute: async () => {\n await refreshGithubToken(requestContext, github);\n return { refreshed: true };\n },\n }),\n github_upsert_factory_triage_comment: createTool({\n id: 'github_upsert_factory_triage_comment',\n description:\n 'Create or update this Factory App’s canonical triage handoff comment on an issue in the active repository. Use this for every marked pending or final Factory triage handoff; never use gh to create or edit that handoff.',\n inputSchema: triageCommentInputSchema,\n execute: async input => upsertFactoryTriageComment(requestContext, input, github),\n }),\n github_subscribe_pr: createTool({\n id: 'github_subscribe_pr',\n description:\n 'Subscribe this thread to GitHub pull request activity. You usually do not need this tool: successful gh pr create commands subscribe automatically. Use it for an existing PR or to recover when automatic subscription did not occur. Closed or merged PRs are unsubscribed automatically. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await subscribeCurrentSessionToPullRequest(requestContext, pullRequest, 'explicit-tool', github);\n return { subscribed: true, pullRequestNumber: number };\n },\n }),\n github_unsubscribe_pr: createTool({\n id: 'github_unsubscribe_pr',\n description:\n 'Manually unsubscribe this thread from GitHub pull request activity. You usually do not need this tool because closed or merged PRs are unsubscribed automatically. Use it to stop notifications before then. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequest, github);\n return { subscribed: false, pullRequestNumber: number };\n },\n }),\n };\n}\n\nexport function parseCreatedPullRequest(context: {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n}) {\n if (context.toolName !== 'execute_command' || context.error) return undefined;\n const command = (context.input as { command?: unknown } | undefined)?.command;\n if (typeof command !== 'string' || !runsPullRequestCreate(command)) return undefined;\n const output = context.output as { stdout?: unknown; result?: unknown } | undefined;\n const stdout = typeof context.output === 'string' ? context.output : (output?.stdout ?? output?.result);\n if (typeof stdout !== 'string') return undefined;\n const urls = stdout.match(/https:\\/\\/github\\.com\\/[^\\s/]+\\/[^\\s/]+\\/pull\\/\\d+/g) ?? [];\n return urls.length === 1 ? urls[0] : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-subscriptions.js","names":[],"sources":["../../../src/integrations/github/session-subscriptions.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from '../../auth.js';\nimport { runsPullRequestCreate } from '../../session/shell-commands.js';\nimport type {\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GithubIntegration } from './integration.js';\nimport { getGithubPat } from './pat.js';\nimport { subscribeToPullRequest, unsubscribeFromPullRequest } from './subscriptions.js';\nimport { getRegisteredGithubPatKind, injectGithubToken } from './token-refresh.js';\n\ntype RepositorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\n/**\n * The host-authenticated user placed on the request context under the `user`\n * key, read through the host's own normalizer. A local mirror of that shape\n * used to live here; it silently missed the provider shapes the normalizer\n * knows about, which turned every subscription tool into a no-op for those\n * users instead of an error anybody could see.\n */\nfunction sessionUserId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthUserId(getFactoryAuthUserFromContext(requestContext));\n}\n\nfunction sessionOrgId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n}\n\nconst pullRequestInputSchema = z.object({\n pullRequest: z.union([z.number().int().positive(), z.string().min(1)]),\n});\n\nconst TRIAGE_COMMENT_MARKER = '<!-- mastra-factory-triage -->';\nconst triageCommentInputSchema = z.object({\n issueNumber: z.number().int().positive(),\n body: z.string().startsWith(TRIAGE_COMMENT_MARKER),\n});\n\nconst triageCommentLocks = new Map<string, Promise<void>>();\n\nasync function serializeTriageComment<T>(key: string, operation: () => Promise<T>): Promise<T> {\n const previous = triageCommentLocks.get(key) ?? Promise.resolve();\n let release: (() => void) | undefined;\n const current = new Promise<void>(resolve => {\n release = resolve;\n });\n const queued = previous.then(() => current);\n triageCommentLocks.set(key, queued);\n await previous;\n try {\n return await operation();\n } finally {\n release!();\n if (triageCommentLocks.get(key) === queued) triageCommentLocks.delete(key);\n }\n}\n\ninterface SessionTarget {\n context: AgentControllerRequestContext<RepositorySessionState>;\n projectRepository: ProjectRepository;\n connection: ProjectSourceControlConnection;\n installation: SourceControlInstallation;\n repository: SourceControlRepository;\n orgId: string;\n userId: string;\n}\n\nfunction parsePullRequest(value: number | string, expectedRepo: string): number {\n if (typeof value === 'number') return value;\n if (/^\\d+$/.test(value)) return Number(value);\n const match = value.match(/^https:\\/\\/github\\.com\\/([^/]+\\/[^/]+)\\/pull\\/(\\d+)\\/?$/i);\n if (!match || match[1]!.toLowerCase() !== expectedRepo.toLowerCase()) {\n throw new Error(`Pull request must belong to ${expectedRepo}.`);\n }\n return Number(match[2]);\n}\n\n/**\n * Whether the current request comes from a session that GitHub subscriptions\n * can ever apply to: an authenticated org user on a GitHub-project session\n * with an active thread. Mirrors the gate in `resolveSessionTarget` without\n * throwing, for passive callers that should no-op instead of erroring.\n */\nfunction isGithubProjectSession(requestContext: RequestContext): boolean {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n return Boolean(\n context?.threadId &&\n context.getState().projectRepositoryId &&\n sessionOrgId(requestContext) &&\n sessionUserId(requestContext),\n );\n}\n\nasync function resolveSessionTarget(requestContext: RequestContext, github: GithubIntegration): Promise<SessionTarget> {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const orgId = sessionOrgId(requestContext);\n const userId = sessionUserId(requestContext);\n const projectRepositoryId = context?.getState().projectRepositoryId;\n if (!context || !context.threadId || !projectRepositoryId || !orgId || !userId) {\n throw new Error('GitHub subscriptions require an authenticated repository session with an active thread.');\n }\n\n const projectRepository = await github.sourceControlStorage.projectRepositories.get({\n orgId,\n id: projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Project repository not found for this organization.');\n const connection = await github.sourceControlStorage.connections.get({ orgId, id: projectRepository.connectionId });\n if (!connection) throw new Error('Source-control connection not found for this organization.');\n const repository = await github.sourceControlStorage.repositories.get({ orgId, id: projectRepository.repositoryId });\n if (!repository) throw new Error('Repository not found for this organization.');\n const installation = await github.sourceControlStorage.installations.get({ orgId, id: connection.installationId });\n if (!installation) throw new Error('Source-control installation not found for this organization.');\n return { context, projectRepository, connection, installation, repository, orgId, userId };\n}\n\nasync function verifyPullRequest(target: SessionTarget, pullRequest: number, github: GithubIntegration) {\n const [owner, repo] = target.repository.slug.split('/');\n if (!owner || !repo) throw new Error('GitHub repository is invalid.');\n const octokit = github.getInstallationOctokit(Number(target.installation.externalId));\n const { data } = await octokit.pulls.get({ owner, repo, pull_number: pullRequest });\n if (String(data.base.repo.id) !== target.repository.externalId)\n throw new Error('Pull request repository does not match the active project repository.');\n}\n\nasync function subscriptionInput(target: SessionTarget, pullRequestNumber: number) {\n return {\n orgId: target.orgId,\n installationExternalId: target.installation.externalId,\n projectRepositoryId: target.projectRepository.id,\n repositoryExternalId: target.repository.externalId,\n repositorySlug: target.repository.slug,\n changeRequestId: String(pullRequestNumber),\n sessionId: target.context.session.id,\n ownerId: target.context.session.ownerId,\n resourceId: target.connection.factoryProjectId,\n threadId: target.context.threadId!,\n sessionScope: target.context.scope,\n source: 'explicit-tool' as const,\n subscribedByUserId: target.userId,\n };\n}\n\nexport async function subscribeCurrentSessionToPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n source: 'auto-gh-pr-create' | 'explicit-tool',\n github: GithubIntegration,\n) {\n // The auto path observes every successful `gh pr create` in every session,\n // including local and non-GitHub-project sessions where subscriptions can\n // never apply. Skip silently there; only the explicit tool should surface\n // \"this session cannot subscribe\" as an error.\n if (source === 'auto-gh-pr-create' && !isGithubProjectSession(requestContext)) return undefined;\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await verifyPullRequest(target, number, github);\n await subscribeToPullRequest({ ...(await subscriptionInput(target, number)), source }, github.integrationStorage);\n return number;\n}\n\nexport async function unsubscribeCurrentSessionFromPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await unsubscribeFromPullRequest(await subscriptionInput(target, number), github.integrationStorage);\n return number;\n}\n\nexport async function upsertFactoryTriageComment(\n requestContext: RequestContext,\n input: { issueNumber: number; body: string },\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const installationId = Number(target.installation.externalId);\n if (!Number.isSafeInteger(installationId) || installationId <= 0) throw new Error('GitHub installation is invalid.');\n return serializeTriageComment(`${installationId}:${target.repository.externalId}:${input.issueNumber}`, () =>\n github.upsertFactoryTriageComment({\n installationId,\n repository: target.repository.slug,\n issueNumber: input.issueNumber,\n body: input.body,\n }),\n );\n}\n\nexport async function refreshGithubToken(requestContext: RequestContext, github: GithubIntegration): Promise<void> {\n const target = await resolveSessionTarget(requestContext, github);\n // `GH_TOKEN` feeds the `gh` CLI, so a configured org PAT wins over a minted\n // installation token (which 403s on integration-restricted endpoints). The\n // workspace records which PAT kind the sandbox was provisioned with, so a\n // review-board sandbox keeps its reviewer token on refresh.\n const pat = await getGithubPat(\n () => github.integrationStorage,\n target.orgId,\n getRegisteredGithubPatKind(requestContext),\n );\n if (pat) {\n injectGithubToken(requestContext, pat);\n return;\n }\n const access = await github.versionControl.getRepositoryAccess({\n orgId: target.orgId,\n repositoryId: target.repository.id,\n });\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session.');\n injectGithubToken(requestContext, token);\n}\n\nexport function createGithubSubscriptionTools(requestContext: RequestContext, github: GithubIntegration) {\n if (!isGithubProjectSession(requestContext)) return {};\n\n return {\n github_refresh_token: createTool({\n id: 'github_refresh_token',\n description:\n 'Refresh GitHub CLI authentication in the active Factory sandbox. Use this after a gh command fails because authentication is expired, invalid, or missing. It installs a fresh GH_TOKEN for subsequent sandbox commands. After this tool succeeds, retry the failed gh command. Takes no arguments and never returns the token.',\n inputSchema: z.object({}),\n execute: async () => {\n await refreshGithubToken(requestContext, github);\n return { refreshed: true };\n },\n }),\n github_upsert_factory_triage_comment: createTool({\n id: 'github_upsert_factory_triage_comment',\n description:\n 'Create or update this Factory App’s canonical triage handoff comment on an issue in the active repository. Use this for every marked pending or final Factory triage handoff; never use gh to create or edit that handoff.',\n inputSchema: triageCommentInputSchema,\n execute: async input => upsertFactoryTriageComment(requestContext, input, github),\n }),\n github_subscribe_pr: createTool({\n id: 'github_subscribe_pr',\n description:\n 'Subscribe this thread to GitHub pull request activity. You usually do not need this tool: successful gh pr create commands subscribe automatically. Use it for an existing PR or to recover when automatic subscription did not occur. Closed or merged PRs are unsubscribed automatically. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await subscribeCurrentSessionToPullRequest(requestContext, pullRequest, 'explicit-tool', github);\n return { subscribed: true, pullRequestNumber: number };\n },\n }),\n github_unsubscribe_pr: createTool({\n id: 'github_unsubscribe_pr',\n description:\n 'Manually unsubscribe this thread from GitHub pull request activity. You usually do not need this tool because closed or merged PRs are unsubscribed automatically. Use it to stop notifications before then. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequest, github);\n return { subscribed: false, pullRequestNumber: number };\n },\n }),\n };\n}\n\nconst GITHUB_PULL_REQUEST_URL = /^https:\\/\\/github\\.com\\/[^\\s/]+\\/[^\\s/]+\\/pull\\/\\d+\\/?$/;\n\nexport function parseCreatedPullRequest(context: {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n}) {\n if (context.error) return undefined;\n // The provider-neutral change-request tool reports the created PR directly;\n // subscribe on it exactly as on a successful `gh pr create`.\n if (context.toolName === 'source_control_create_change_request') {\n const url = (context.output as { url?: unknown } | undefined)?.url;\n return typeof url === 'string' && GITHUB_PULL_REQUEST_URL.test(url) ? url.replace(/\\/$/, '') : undefined;\n }\n if (context.toolName !== 'execute_command') return undefined;\n const command = (context.input as { command?: unknown } | undefined)?.command;\n if (typeof command !== 'string' || !runsPullRequestCreate(command)) return undefined;\n const output = context.output as { stdout?: unknown; result?: unknown } | undefined;\n const stdout = typeof context.output === 'string' ? context.output : (output?.stdout ?? output?.result);\n if (typeof stdout !== 'string') return undefined;\n const urls = stdout.match(/https:\\/\\/github\\.com\\/[^\\s/]+\\/[^\\s/]+\\/pull\\/\\d+/g) ?? [];\n return urls.length === 1 ? urls[0] : undefined;\n}\n"],"mappings":"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"}
@@ -28,5 +28,13 @@ export declare function resolveFactorySessionAddress(options: {
28
28
  requestContext: RequestContext | undefined;
29
29
  storage: Pick<WorkItemsStorage, 'findActiveRunBindingByThread' | 'get'>;
30
30
  sessions?: FactorySessionSourceLookup;
31
+ /**
32
+ * Resolve through the binding table even when state already carries
33
+ * `factoryProjectId`, so the row heals the rest of the session state. Prompt
34
+ * creation uses this: a partially persisted state can keep the project id
35
+ * while the untrusted-checkout posture is gone, and the direct short-circuit
36
+ * would take that id as proof the session is intact.
37
+ */
38
+ forceBindingLookup?: boolean;
31
39
  }): Promise<FactorySessionAddressResolution | null>;
32
40
  //# sourceMappingURL=binding-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"binding-context.d.ts","sourceRoot":"","sources":["../../src/rules/binding-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACvB,+BAA+B,EAC/B,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAY/C,kFAAkF;AAClF,MAAM,WAAW,0BAA0B;IACzC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACvH;AAED,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,cAAc,GAAG,SAAS,GACzC,+BAA+B,GAAG,IAAI,CAWxC;AAED,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,cAAc,GAAG,SAAS,GAAG,wBAAwB,GAAG,IAAI,CAOpH;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,wBAAwB,CAAC;IAClC,0EAA0E;IAC1E,OAAO,CAAC,EAAE,uBAAuB,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IAC1D,cAAc,EAAE,cAAc,GAAG,SAAS,CAAC;IAC3C,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,8BAA8B,GAAG,KAAK,CAAC,CAAC;IACxE,QAAQ,CAAC,EAAE,0BAA0B,CAAC;CACvC,GAAG,OAAO,CAAC,+BAA+B,GAAG,IAAI,CAAC,CAgClD"}
1
+ {"version":3,"file":"binding-context.d.ts","sourceRoot":"","sources":["../../src/rules/binding-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACvB,+BAA+B,EAC/B,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAY/C,kFAAkF;AAClF,MAAM,WAAW,0BAA0B;IACzC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACvH;AAED,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,cAAc,GAAG,SAAS,GACzC,+BAA+B,GAAG,IAAI,CAWxC;AAED,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,cAAc,GAAG,SAAS,GAAG,wBAAwB,GAAG,IAAI,CAOpH;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,wBAAwB,CAAC;IAClC,0EAA0E;IAC1E,OAAO,CAAC,EAAE,uBAAuB,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IAC1D,cAAc,EAAE,cAAc,GAAG,SAAS,CAAC;IAC3C,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,8BAA8B,GAAG,KAAK,CAAC,CAAC;IACxE,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,GAAG,OAAO,CAAC,+BAA+B,GAAG,IAAI,CAAC,CAgClD"}
@@ -32,7 +32,7 @@ function getFactorySessionAddress(requestContext) {
32
32
  * originally seeded) so later reads resolve directly.
33
33
  */
34
34
  async function resolveFactorySessionAddress(options) {
35
- const direct = getFactorySessionAddress(options.requestContext);
35
+ const direct = options.forceBindingLookup ? null : getFactorySessionAddress(options.requestContext);
36
36
  if (direct) return { address: direct };
37
37
  const requestContext = options.requestContext;
38
38
  if (!requestContext || typeof requestContext.get !== "function") return null;
@@ -84,7 +84,7 @@ async function healRecoveredSessionState(options) {
84
84
  const metadataBaseBranch = item?.metadata?.baseBranch;
85
85
  const baseRef = sourceSession?.baseBranch || (typeof metadataBaseBranch === "string" && metadataBaseBranch ? metadataBaseBranch : void 0);
86
86
  if (baseRef) updates.baseRef = baseRef;
87
- }
87
+ } else updates.untrustedCheckout = false;
88
88
  } catch {}
89
89
  try {
90
90
  await options.context.setState(updates);
@@ -1 +1 @@
1
- {"version":3,"file":"binding-context.js","names":[],"sources":["../../src/rules/binding-context.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext } from '../auth.js';\nimport type {\n FactoryRunBindingAddress,\n FactoryRunBindingRecord,\n FactoryRunBindingSessionAddress,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\n\ninterface FactorySessionState {\n factoryProjectId?: string;\n factoryOrgId?: string;\n projectRepositoryId?: string;\n untrustedCheckout?: boolean;\n baseRef?: string;\n}\n\ntype FactorySessionControllerContext = AgentControllerRequestContext<FactorySessionState>;\n\n/** Minimal source-control session lookup used to heal recovered session state. */\nexport interface FactorySessionSourceLookup {\n getBySessionId(sessionId: string): Promise<{ orgId: string; projectRepositoryId: string; baseBranch: string } | null>;\n}\n\nexport function getFactorySessionCoordinates(\n requestContext: RequestContext | undefined,\n): FactoryRunBindingSessionAddress | null {\n if (!requestContext || typeof requestContext.get !== 'function') return null;\n const context = requestContext.get('controller') as FactorySessionControllerContext | undefined;\n const factoryProjectId = context?.getState().factoryProjectId;\n if (!context?.threadId || !context.resourceId || !factoryProjectId) return null;\n return {\n factoryProjectId,\n threadId: context.threadId,\n resourceId: context.resourceId,\n sessionId: context.resourceId,\n };\n}\n\nexport function getFactorySessionAddress(requestContext: RequestContext | undefined): FactoryRunBindingAddress | null {\n const coordinates = getFactorySessionCoordinates(requestContext);\n if (!coordinates || !requestContext || typeof requestContext.get !== 'function') return null;\n const user = getFactoryAuthUserFromContext(requestContext);\n const orgId = getFactoryAuthOrgId(user);\n if (!orgId) return null;\n return { orgId, ...coordinates };\n}\n\nexport interface FactorySessionAddressResolution {\n address: FactoryRunBindingAddress;\n /** Present only when the address was recovered from the binding table. */\n binding?: FactoryRunBindingRecord;\n}\n\n/**\n * Resolve the bound session address, falling back to the active binding row\n * when the in-memory session state is missing `factoryProjectId` — a server\n * crash drops controller sessions, and a resumed session (\"Continue\") is\n * recreated with empty state, which silently unregistered the transition\n * tool. On recovery the lost state is healed back onto the session (including\n * the `untrustedCheckout`/`baseRef` security posture the start coordinator\n * originally seeded) so later reads resolve directly.\n */\nexport async function resolveFactorySessionAddress(options: {\n requestContext: RequestContext | undefined;\n storage: Pick<WorkItemsStorage, 'findActiveRunBindingByThread' | 'get'>;\n sessions?: FactorySessionSourceLookup;\n}): Promise<FactorySessionAddressResolution | null> {\n const direct = getFactorySessionAddress(options.requestContext);\n if (direct) return { address: direct };\n\n const requestContext = options.requestContext;\n if (!requestContext || typeof requestContext.get !== 'function') return null;\n const context = requestContext.get('controller') as FactorySessionControllerContext | undefined;\n if (!context?.threadId || !context.resourceId) return null;\n const user = getFactoryAuthUserFromContext(requestContext);\n const orgId = getFactoryAuthOrgId(user);\n if (!orgId) return null;\n\n const binding = await options.storage.findActiveRunBindingByThread({\n orgId,\n threadId: context.threadId,\n resourceId: context.resourceId,\n sessionId: context.resourceId,\n });\n if (!binding) return null;\n\n await healRecoveredSessionState({ context, binding, storage: options.storage, sessions: options.sessions });\n\n return {\n address: {\n orgId,\n factoryProjectId: binding.factoryProjectId,\n threadId: context.threadId,\n resourceId: context.resourceId,\n sessionId: context.resourceId,\n },\n binding,\n };\n}\n\nasync function healRecoveredSessionState(options: {\n context: FactorySessionControllerContext;\n binding: FactoryRunBindingRecord;\n storage: Pick<WorkItemsStorage, 'get'>;\n sessions?: FactorySessionSourceLookup;\n}): Promise<void> {\n const { binding } = options;\n // `factoryOrgId` mirrors the start coordinator's seed: the binding row holds\n // the authoritative org id, so crash-recovered sessions heal it for free.\n const updates: FactorySessionState = { factoryProjectId: binding.factoryProjectId, factoryOrgId: binding.orgId };\n // Security posture first, from the binding row alone (no I/O): review-bound\n // sessions run against attacker-writable checkouts, so `untrustedCheckout`\n // must survive even if the enrichment lookups below fail transiently.\n if (binding.role === 'review') {\n updates.untrustedCheckout = true;\n }\n // Best-effort enrichment mirroring the start coordinator's original seed:\n // PR-sourced work items are untrusted regardless of role, and `baseRef`\n // restores the trusted instruction source for untrusted checkouts.\n try {\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n const foundSession = (await options.sessions?.getBySessionId(binding.sessionId)) ?? null;\n const sourceSession = foundSession?.orgId === binding.orgId ? foundSession : null;\n if (sourceSession) {\n updates.projectRepositoryId = sourceSession.projectRepositoryId;\n }\n const untrusted = updates.untrustedCheckout === true || item?.externalSource?.type === 'pull-request';\n if (untrusted) {\n updates.untrustedCheckout = true;\n const metadataBaseBranch = item?.metadata?.baseBranch;\n const baseRef =\n sourceSession?.baseBranch ||\n (typeof metadataBaseBranch === 'string' && metadataBaseBranch ? metadataBaseBranch : undefined);\n if (baseRef) updates.baseRef = baseRef;\n }\n } catch {\n // Enrichment is best-effort; the address itself only needs the binding.\n }\n try {\n await options.context.setState(updates);\n } catch {\n // State healing must never block tool registration on the recovered address.\n }\n}\n"],"mappings":";;AA0BA,SAAgB,6BACd,gBACwC;CACxC,IAAI,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY,OAAO;CACxE,MAAM,UAAU,eAAe,IAAI,YAAY;CAC/C,MAAM,mBAAmB,SAAS,SAAS,CAAC,CAAC;CAC7C,IAAI,CAAC,SAAS,YAAY,CAAC,QAAQ,cAAc,CAAC,kBAAkB,OAAO;CAC3E,OAAO;EACL;EACA,UAAU,QAAQ;EAClB,YAAY,QAAQ;EACpB,WAAW,QAAQ;CACrB;AACF;AAEA,SAAgB,yBAAyB,gBAA6E;CACpH,MAAM,cAAc,6BAA6B,cAAc;CAC/D,IAAI,CAAC,eAAe,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY,OAAO;CAExF,MAAM,QAAQ,oBADD,8BAA8B,cACN,CAAC;CACtC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO;EAAE;EAAO,GAAG;CAAY;AACjC;;;;;;;;;;AAiBA,eAAsB,6BAA6B,SAIC;CAClD,MAAM,SAAS,yBAAyB,QAAQ,cAAc;CAC9D,IAAI,QAAQ,OAAO,EAAE,SAAS,OAAO;CAErC,MAAM,iBAAiB,QAAQ;CAC/B,IAAI,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY,OAAO;CACxE,MAAM,UAAU,eAAe,IAAI,YAAY;CAC/C,IAAI,CAAC,SAAS,YAAY,CAAC,QAAQ,YAAY,OAAO;CAEtD,MAAM,QAAQ,oBADD,8BAA8B,cACN,CAAC;CACtC,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,UAAU,MAAM,QAAQ,QAAQ,6BAA6B;EACjE;EACA,UAAU,QAAQ;EAClB,YAAY,QAAQ;EACpB,WAAW,QAAQ;CACrB,CAAC;CACD,IAAI,CAAC,SAAS,OAAO;CAErB,MAAM,0BAA0B;EAAE;EAAS;EAAS,SAAS,QAAQ;EAAS,UAAU,QAAQ;CAAS,CAAC;CAE1G,OAAO;EACL,SAAS;GACP;GACA,kBAAkB,QAAQ;GAC1B,UAAU,QAAQ;GAClB,YAAY,QAAQ;GACpB,WAAW,QAAQ;EACrB;EACA;CACF;AACF;AAEA,eAAe,0BAA0B,SAKvB;CAChB,MAAM,EAAE,YAAY;CAGpB,MAAM,UAA+B;EAAE,kBAAkB,QAAQ;EAAkB,cAAc,QAAQ;CAAM;CAI/G,IAAI,QAAQ,SAAS,UACnB,QAAQ,oBAAoB;CAK9B,IAAI;EACF,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;GAAE,OAAO,QAAQ;GAAO,IAAI,QAAQ;EAAW,CAAC;EACvF,MAAM,eAAgB,MAAM,QAAQ,UAAU,eAAe,QAAQ,SAAS,KAAM;EACpF,MAAM,gBAAgB,cAAc,UAAU,QAAQ,QAAQ,eAAe;EAC7E,IAAI,eACF,QAAQ,sBAAsB,cAAc;EAG9C,IADkB,QAAQ,sBAAsB,QAAQ,MAAM,gBAAgB,SAAS,gBACxE;GACb,QAAQ,oBAAoB;GAC5B,MAAM,qBAAqB,MAAM,UAAU;GAC3C,MAAM,UACJ,eAAe,eACd,OAAO,uBAAuB,YAAY,qBAAqB,qBAAqB,KAAA;GACvF,IAAI,SAAS,QAAQ,UAAU;EACjC;CACF,QAAQ,CAER;CACA,IAAI;EACF,MAAM,QAAQ,QAAQ,SAAS,OAAO;CACxC,QAAQ,CAER;AACF"}
1
+ {"version":3,"file":"binding-context.js","names":[],"sources":["../../src/rules/binding-context.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext } from '../auth.js';\nimport type {\n FactoryRunBindingAddress,\n FactoryRunBindingRecord,\n FactoryRunBindingSessionAddress,\n WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\n\ninterface FactorySessionState {\n factoryProjectId?: string;\n factoryOrgId?: string;\n projectRepositoryId?: string;\n untrustedCheckout?: boolean;\n baseRef?: string;\n}\n\ntype FactorySessionControllerContext = AgentControllerRequestContext<FactorySessionState>;\n\n/** Minimal source-control session lookup used to heal recovered session state. */\nexport interface FactorySessionSourceLookup {\n getBySessionId(sessionId: string): Promise<{ orgId: string; projectRepositoryId: string; baseBranch: string } | null>;\n}\n\nexport function getFactorySessionCoordinates(\n requestContext: RequestContext | undefined,\n): FactoryRunBindingSessionAddress | null {\n if (!requestContext || typeof requestContext.get !== 'function') return null;\n const context = requestContext.get('controller') as FactorySessionControllerContext | undefined;\n const factoryProjectId = context?.getState().factoryProjectId;\n if (!context?.threadId || !context.resourceId || !factoryProjectId) return null;\n return {\n factoryProjectId,\n threadId: context.threadId,\n resourceId: context.resourceId,\n sessionId: context.resourceId,\n };\n}\n\nexport function getFactorySessionAddress(requestContext: RequestContext | undefined): FactoryRunBindingAddress | null {\n const coordinates = getFactorySessionCoordinates(requestContext);\n if (!coordinates || !requestContext || typeof requestContext.get !== 'function') return null;\n const user = getFactoryAuthUserFromContext(requestContext);\n const orgId = getFactoryAuthOrgId(user);\n if (!orgId) return null;\n return { orgId, ...coordinates };\n}\n\nexport interface FactorySessionAddressResolution {\n address: FactoryRunBindingAddress;\n /** Present only when the address was recovered from the binding table. */\n binding?: FactoryRunBindingRecord;\n}\n\n/**\n * Resolve the bound session address, falling back to the active binding row\n * when the in-memory session state is missing `factoryProjectId` — a server\n * crash drops controller sessions, and a resumed session (\"Continue\") is\n * recreated with empty state, which silently unregistered the transition\n * tool. On recovery the lost state is healed back onto the session (including\n * the `untrustedCheckout`/`baseRef` security posture the start coordinator\n * originally seeded) so later reads resolve directly.\n */\nexport async function resolveFactorySessionAddress(options: {\n requestContext: RequestContext | undefined;\n storage: Pick<WorkItemsStorage, 'findActiveRunBindingByThread' | 'get'>;\n sessions?: FactorySessionSourceLookup;\n /**\n * Resolve through the binding table even when state already carries\n * `factoryProjectId`, so the row heals the rest of the session state. Prompt\n * creation uses this: a partially persisted state can keep the project id\n * while the untrusted-checkout posture is gone, and the direct short-circuit\n * would take that id as proof the session is intact.\n */\n forceBindingLookup?: boolean;\n}): Promise<FactorySessionAddressResolution | null> {\n const direct = options.forceBindingLookup ? null : getFactorySessionAddress(options.requestContext);\n if (direct) return { address: direct };\n\n const requestContext = options.requestContext;\n if (!requestContext || typeof requestContext.get !== 'function') return null;\n const context = requestContext.get('controller') as FactorySessionControllerContext | undefined;\n if (!context?.threadId || !context.resourceId) return null;\n const user = getFactoryAuthUserFromContext(requestContext);\n const orgId = getFactoryAuthOrgId(user);\n if (!orgId) return null;\n\n const binding = await options.storage.findActiveRunBindingByThread({\n orgId,\n threadId: context.threadId,\n resourceId: context.resourceId,\n sessionId: context.resourceId,\n });\n if (!binding) return null;\n\n await healRecoveredSessionState({ context, binding, storage: options.storage, sessions: options.sessions });\n\n return {\n address: {\n orgId,\n factoryProjectId: binding.factoryProjectId,\n threadId: context.threadId,\n resourceId: context.resourceId,\n sessionId: context.resourceId,\n },\n binding,\n };\n}\n\nasync function healRecoveredSessionState(options: {\n context: FactorySessionControllerContext;\n binding: FactoryRunBindingRecord;\n storage: Pick<WorkItemsStorage, 'get'>;\n sessions?: FactorySessionSourceLookup;\n}): Promise<void> {\n const { binding } = options;\n // `factoryOrgId` mirrors the start coordinator's seed: the binding row holds\n // the authoritative org id, so crash-recovered sessions heal it for free.\n const updates: FactorySessionState = { factoryProjectId: binding.factoryProjectId, factoryOrgId: binding.orgId };\n // Security posture first, from the binding row alone (no I/O): review-bound\n // sessions run against attacker-writable checkouts, so `untrustedCheckout`\n // must survive even if the enrichment lookups below fail transiently.\n if (binding.role === 'review') {\n updates.untrustedCheckout = true;\n }\n // Best-effort enrichment mirroring the start coordinator's original seed:\n // PR-sourced work items are untrusted regardless of role, and `baseRef`\n // restores the trusted instruction source for untrusted checkouts.\n try {\n const item = await options.storage.get({ orgId: binding.orgId, id: binding.workItemId });\n const foundSession = (await options.sessions?.getBySessionId(binding.sessionId)) ?? null;\n const sourceSession = foundSession?.orgId === binding.orgId ? foundSession : null;\n if (sourceSession) {\n updates.projectRepositoryId = sourceSession.projectRepositoryId;\n }\n const untrusted = updates.untrustedCheckout === true || item?.externalSource?.type === 'pull-request';\n if (untrusted) {\n updates.untrustedCheckout = true;\n const metadataBaseBranch = item?.metadata?.baseBranch;\n const baseRef =\n sourceSession?.baseBranch ||\n (typeof metadataBaseBranch === 'string' && metadataBaseBranch ? metadataBaseBranch : undefined);\n if (baseRef) updates.baseRef = baseRef;\n } else {\n // Record the trusted verdict explicitly so a healed session carries every\n // trust field; the prompt-time recovery check treats a missing flag as\n // \"not yet verified\" and would otherwise re-run on every prompt. Left\n // unset when enrichment fails so the next prompt retries the lookup.\n updates.untrustedCheckout = false;\n }\n } catch {\n // Enrichment is best-effort; the address itself only needs the binding.\n }\n try {\n await options.context.setState(updates);\n } catch {\n // State healing must never block tool registration on the recovered address.\n }\n}\n"],"mappings":";;AA0BA,SAAgB,6BACd,gBACwC;CACxC,IAAI,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY,OAAO;CACxE,MAAM,UAAU,eAAe,IAAI,YAAY;CAC/C,MAAM,mBAAmB,SAAS,SAAS,CAAC,CAAC;CAC7C,IAAI,CAAC,SAAS,YAAY,CAAC,QAAQ,cAAc,CAAC,kBAAkB,OAAO;CAC3E,OAAO;EACL;EACA,UAAU,QAAQ;EAClB,YAAY,QAAQ;EACpB,WAAW,QAAQ;CACrB;AACF;AAEA,SAAgB,yBAAyB,gBAA6E;CACpH,MAAM,cAAc,6BAA6B,cAAc;CAC/D,IAAI,CAAC,eAAe,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY,OAAO;CAExF,MAAM,QAAQ,oBADD,8BAA8B,cACN,CAAC;CACtC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO;EAAE;EAAO,GAAG;CAAY;AACjC;;;;;;;;;;AAiBA,eAAsB,6BAA6B,SAYC;CAClD,MAAM,SAAS,QAAQ,qBAAqB,OAAO,yBAAyB,QAAQ,cAAc;CAClG,IAAI,QAAQ,OAAO,EAAE,SAAS,OAAO;CAErC,MAAM,iBAAiB,QAAQ;CAC/B,IAAI,CAAC,kBAAkB,OAAO,eAAe,QAAQ,YAAY,OAAO;CACxE,MAAM,UAAU,eAAe,IAAI,YAAY;CAC/C,IAAI,CAAC,SAAS,YAAY,CAAC,QAAQ,YAAY,OAAO;CAEtD,MAAM,QAAQ,oBADD,8BAA8B,cACN,CAAC;CACtC,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,UAAU,MAAM,QAAQ,QAAQ,6BAA6B;EACjE;EACA,UAAU,QAAQ;EAClB,YAAY,QAAQ;EACpB,WAAW,QAAQ;CACrB,CAAC;CACD,IAAI,CAAC,SAAS,OAAO;CAErB,MAAM,0BAA0B;EAAE;EAAS;EAAS,SAAS,QAAQ;EAAS,UAAU,QAAQ;CAAS,CAAC;CAE1G,OAAO;EACL,SAAS;GACP;GACA,kBAAkB,QAAQ;GAC1B,UAAU,QAAQ;GAClB,YAAY,QAAQ;GACpB,WAAW,QAAQ;EACrB;EACA;CACF;AACF;AAEA,eAAe,0BAA0B,SAKvB;CAChB,MAAM,EAAE,YAAY;CAGpB,MAAM,UAA+B;EAAE,kBAAkB,QAAQ;EAAkB,cAAc,QAAQ;CAAM;CAI/G,IAAI,QAAQ,SAAS,UACnB,QAAQ,oBAAoB;CAK9B,IAAI;EACF,MAAM,OAAO,MAAM,QAAQ,QAAQ,IAAI;GAAE,OAAO,QAAQ;GAAO,IAAI,QAAQ;EAAW,CAAC;EACvF,MAAM,eAAgB,MAAM,QAAQ,UAAU,eAAe,QAAQ,SAAS,KAAM;EACpF,MAAM,gBAAgB,cAAc,UAAU,QAAQ,QAAQ,eAAe;EAC7E,IAAI,eACF,QAAQ,sBAAsB,cAAc;EAG9C,IADkB,QAAQ,sBAAsB,QAAQ,MAAM,gBAAgB,SAAS,gBACxE;GACb,QAAQ,oBAAoB;GAC5B,MAAM,qBAAqB,MAAM,UAAU;GAC3C,MAAM,UACJ,eAAe,eACd,OAAO,uBAAuB,YAAY,qBAAqB,qBAAqB,KAAA;GACvF,IAAI,SAAS,QAAQ,UAAU;EACjC,OAKE,QAAQ,oBAAoB;CAEhC,QAAQ,CAER;CACA,IAAI;EACF,MAAM,QAAQ,QAAQ,SAAS,OAAO;CACxC,QAAQ,CAER;AACF"}
@@ -31,6 +31,8 @@ new PlatformGithubIntegration({ rules: { issueCommentCreated: null } });
31
31
  new PlatformLinearIntegration({ rules: { issueClosed: null } });
32
32
  ```
33
33
 
34
+ When Platform credentials are present the factory installs `PlatformGithubIntegration` itself, so forward the same options with `new MastraFactory({ platform: { github: { rules, slug } } })` rather than constructing the integration; an explicit `integrations` entry with id `github` still takes precedence and makes that key a no-op, and the factory warns instead of ignoring it silently.
35
+
34
36
  GitHub and Linear integrations exclusively own their event handlers; configure them through constructor `rules[event]`, not the global Factory rules tree. Move former `rules.linear[event].onEvent` values to the Linear constructor's `rules[event]`. Every built-in handler is enabled automatically; never import or spread defaults just to install an integration. A function replaces the default, `null` disables that event's handler, and omitted or `undefined` values retain defaults. Constructors validate names and handler values, then copy and freeze the effective map per instance. Disabling a handler does not disable authentication, ingestion, or reconciliation bookkeeping. Linear fetch, platform polling, and reconciliation all use the owning instance's handlers.
35
37
 
36
38
  Do not create an `actions` config or execute authoritative policy in React. Each handler returns one typed `FactoryRuleDecision` or `undefined`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/factory",
3
- "version": "0.16.1-alpha.0",
3
+ "version": "0.17.0-alpha.1",
4
4
  "description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -57,11 +57,11 @@
57
57
  "hono": "^4.13.7",
58
58
  "posthog-node": "^5.46.1",
59
59
  "zod": "^4.6.4",
60
- "@mastra/auth-studio": "1.3.6",
60
+ "@mastra/auth-studio": "1.3.7-alpha.0",
61
61
  "@mastra/auth-workos": "1.6.5",
62
- "@mastra/code-sdk": "1.8.0",
63
- "@mastra/core": "1.68.0",
64
- "@mastra/slack": "1.6.4"
62
+ "@mastra/code-sdk": "1.8.1-alpha.0",
63
+ "@mastra/slack": "1.6.4",
64
+ "@mastra/core": "1.69.0-alpha.0"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "22.20.1",