@mastra/factory 0.1.0-alpha.2 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/factory.d.ts.map +1 -1
  3. package/dist/factory.js +812 -209
  4. package/dist/factory.js.map +1 -1
  5. package/dist/index.js +812 -209
  6. package/dist/index.js.map +1 -1
  7. package/dist/integrations/github/integration.d.ts +2 -2
  8. package/dist/integrations/github/integration.js +45 -11
  9. package/dist/integrations/github/integration.js.map +1 -1
  10. package/dist/integrations/github/provenance.js.map +1 -1
  11. package/dist/integrations/github/routes.d.ts.map +1 -1
  12. package/dist/integrations/github/routes.js +12 -9
  13. package/dist/integrations/github/routes.js.map +1 -1
  14. package/dist/integrations/github/sandbox.d.ts +1 -0
  15. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  16. package/dist/integrations/github/sandbox.js +2 -2
  17. package/dist/integrations/github/sandbox.js.map +1 -1
  18. package/dist/integrations/github/session-subscriptions.d.ts +3 -0
  19. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  20. package/dist/integrations/github/session-subscriptions.js +30 -0
  21. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  22. package/dist/integrations/github/token-refresh.d.ts +6 -0
  23. package/dist/integrations/github/token-refresh.d.ts.map +1 -0
  24. package/dist/integrations/github/token-refresh.js +17 -0
  25. package/dist/integrations/github/token-refresh.js.map +1 -0
  26. package/dist/integrations/platform/github/event-worker.d.ts +2 -0
  27. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  28. package/dist/integrations/platform/github/event-worker.js +8 -0
  29. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  30. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  31. package/dist/integrations/platform/github/integration.js +52 -9
  32. package/dist/integrations/platform/github/integration.js.map +1 -1
  33. package/dist/routes/config.d.ts +16 -11
  34. package/dist/routes/config.d.ts.map +1 -1
  35. package/dist/routes/config.js +222 -109
  36. package/dist/routes/config.js.map +1 -1
  37. package/dist/routes/custom-provider-source.d.ts +52 -0
  38. package/dist/routes/custom-provider-source.d.ts.map +1 -0
  39. package/dist/routes/custom-provider-source.js +107 -0
  40. package/dist/routes/custom-provider-source.js.map +1 -0
  41. package/dist/routes/oauth.js +33 -1
  42. package/dist/routes/oauth.js.map +1 -1
  43. package/dist/routes/surface.d.ts +4 -0
  44. package/dist/routes/surface.d.ts.map +1 -1
  45. package/dist/routes/surface.js +282 -111
  46. package/dist/routes/surface.js.map +1 -1
  47. package/dist/routes/work-items.js.map +1 -1
  48. package/dist/rules/defaults.d.ts.map +1 -1
  49. package/dist/rules/defaults.js +8 -2
  50. package/dist/rules/defaults.js.map +1 -1
  51. package/dist/rules/dispatcher.js.map +1 -1
  52. package/dist/rules/github-service.d.ts +2 -0
  53. package/dist/rules/github-service.d.ts.map +1 -1
  54. package/dist/rules/github-service.js +15 -1
  55. package/dist/rules/github-service.js.map +1 -1
  56. package/dist/rules/index.js +8 -2
  57. package/dist/rules/index.js.map +1 -1
  58. package/dist/rules/linear-service.js.map +1 -1
  59. package/dist/rules/processor.js.map +1 -1
  60. package/dist/rules/tools.js.map +1 -1
  61. package/dist/rules/transition-service.js.map +1 -1
  62. package/dist/rules/types.d.ts +5 -0
  63. package/dist/rules/types.d.ts.map +1 -1
  64. package/dist/rules/types.js.map +1 -1
  65. package/dist/rules/validation.js.map +1 -1
  66. package/dist/sandbox/fleet.d.ts +3 -0
  67. package/dist/sandbox/fleet.d.ts.map +1 -1
  68. package/dist/sandbox/fleet.js +10 -3
  69. package/dist/sandbox/fleet.js.map +1 -1
  70. package/dist/spa-static.d.ts +4 -5
  71. package/dist/spa-static.d.ts.map +1 -1
  72. package/dist/spa-static.js +5 -2
  73. package/dist/spa-static.js.map +1 -1
  74. package/dist/storage/domains/custom-providers/base.d.ts +57 -0
  75. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
  76. package/dist/storage/domains/custom-providers/base.js +150 -0
  77. package/dist/storage/domains/custom-providers/base.js.map +1 -0
  78. package/dist/storage/domains/memory-settings/base.d.ts +62 -0
  79. package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
  80. package/dist/storage/domains/memory-settings/base.js +111 -0
  81. package/dist/storage/domains/memory-settings/base.js.map +1 -0
  82. package/dist/workspace.d.ts.map +1 -1
  83. package/dist/workspace.js +17 -0
  84. package/dist/workspace.js.map +1 -1
  85. package/package.json +7 -6
package/dist/factory.js CHANGED
@@ -11163,8 +11163,10 @@ function assertIdentifier(kind, name) {
11163
11163
  }
11164
11164
  }
11165
11165
  function isUniqueViolation(error) {
11166
+ const code = typeof error === "object" && error !== null ? error.code : void 0;
11167
+ if (code === "SQLITE_CONSTRAINT_UNIQUE" || code === "SQLITE_CONSTRAINT_PRIMARYKEY") return true;
11166
11168
  const message = error instanceof Error ? error.message : String(error);
11167
- return message.includes("UNIQUE constraint failed") || message.includes("SQLITE_CONSTRAINT");
11169
+ return message.includes("UNIQUE constraint failed") || message.includes("SQLITE_CONSTRAINT_UNIQUE") || message.includes("SQLITE_CONSTRAINT_PRIMARYKEY");
11168
11170
  }
11169
11171
  function primaryKeyOf(schema) {
11170
11172
  const pks = Object.entries(schema.columns).filter(([, spec]) => spec.type === "uuid-pk" || spec.primaryKey);
@@ -24103,6 +24105,35 @@ ${unreflectedContent}` : bufferedReflection;
24103
24105
  if (spec.default !== void 0) ddl += ` DEFAULT ${serializeDefault(spec.default)}`;
24104
24106
  return ddl;
24105
24107
  }
24108
+ /**
24109
+ * A table created by an older schema may still say NOT NULL on a column the
24110
+ * current schema declares nullable — inserts of null then fail on databases
24111
+ * that predate the change. SQLite has no `ALTER COLUMN DROP NOT NULL`, so
24112
+ * relaxation swaps in a table rebuilt from the current schema (create shadow
24113
+ * → copy → drop → rename) atomically.
24114
+ */
24115
+ async #relaxDriftedNotNulls(schema) {
24116
+ const info = await this.#client.execute(`PRAGMA table_info("${schema.name}")`);
24117
+ const hasDrift = info.rows.some((row) => {
24118
+ const spec = schema.columns[String(row.name)];
24119
+ if (!spec || spec.type === "uuid-pk" || spec.primaryKey) return false;
24120
+ return spec.nullable === true && Number(row.notnull) === 1;
24121
+ });
24122
+ if (!hasDrift) return;
24123
+ const ddl = Object.entries(schema.columns).map(([name, spec]) => this.#columnDdl(name, spec));
24124
+ const shadow = `${schema.name}__nullable_rebuild`;
24125
+ const cols = Object.keys(schema.columns).map((name) => `"${name}"`).join(", ");
24126
+ await this.#client.batch(
24127
+ [
24128
+ `DROP TABLE IF EXISTS "${shadow}"`,
24129
+ `CREATE TABLE "${shadow}" (${ddl.join(", ")})`,
24130
+ `INSERT INTO "${shadow}" (${cols}) SELECT ${cols} FROM "${schema.name}"`,
24131
+ `DROP TABLE "${schema.name}"`,
24132
+ `ALTER TABLE "${shadow}" RENAME TO "${schema.name}"`
24133
+ ],
24134
+ "write"
24135
+ );
24136
+ }
24106
24137
  async #ensureCollection(schema) {
24107
24138
  assertIdentifier("collection", schema.name);
24108
24139
  primaryKeyOf(schema);
@@ -24114,6 +24145,7 @@ ${unreflectedContent}` : bufferedReflection;
24114
24145
  if (existing.has(name)) continue;
24115
24146
  await this.#client.execute(`ALTER TABLE "${schema.name}" ADD COLUMN ${this.#columnDdl(name, spec)}`);
24116
24147
  }
24148
+ await this.#relaxDriftedNotNulls(schema);
24117
24149
  for (const index of schema.uniqueIndexes ?? []) {
24118
24150
  assertIdentifier("index", index.name);
24119
24151
  index.columns.forEach((column) => assertIdentifier("column", column));
@@ -24798,6 +24830,19 @@ function retirePullRequestSubscription(id, status, storage) {
24798
24830
  return storage.subscriptions.updateStatus(id, status);
24799
24831
  }
24800
24832
 
24833
+ // src/integrations/github/token-refresh.ts
24834
+ var GITHUB_TOKEN_INJECTOR_CONTEXT_KEY = "factoryGithubTokenInjector";
24835
+ function registerGithubTokenInjector(requestContext, injector) {
24836
+ requestContext.set(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY, injector);
24837
+ }
24838
+ function injectGithubToken(requestContext, token) {
24839
+ const injector = requestContext.get(GITHUB_TOKEN_INJECTOR_CONTEXT_KEY);
24840
+ if (!injector) {
24841
+ throw new Error("GitHub token refresh requires an active Factory sandbox workspace.");
24842
+ }
24843
+ injector(token);
24844
+ }
24845
+
24801
24846
  // src/integrations/github/session-subscriptions.ts
24802
24847
  function sessionUserId(user) {
24803
24848
  return user?.workosId ?? user?.id;
@@ -24885,11 +24930,30 @@ async function unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequ
24885
24930
  await unsubscribeFromPullRequest(await subscriptionInput(target, number3), github.integrationStorage);
24886
24931
  return number3;
24887
24932
  }
24933
+ async function refreshGithubToken(requestContext, github) {
24934
+ const target = await resolveSessionTarget(requestContext, github);
24935
+ const access = await github.versionControl.getRepositoryAccess({
24936
+ orgId: target.orgId,
24937
+ repositoryId: target.repository.id
24938
+ });
24939
+ const token = access.authorization?.token;
24940
+ if (!token) throw new Error("Repository access did not include a bearer token for the Factory session.");
24941
+ injectGithubToken(requestContext, token);
24942
+ }
24888
24943
  function createGithubSubscriptionTools(requestContext, github) {
24889
24944
  const context = requestContext.get("controller");
24890
24945
  const user = requestContext.get("user");
24891
24946
  if (!context?.getState().projectRepositoryId || !sessionOrgId(user) || !sessionUserId(user)) return {};
24892
24947
  return {
24948
+ github_refresh_token: createTool({
24949
+ id: "github_refresh_token",
24950
+ description: "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.",
24951
+ inputSchema: z.object({}),
24952
+ execute: async () => {
24953
+ await refreshGithubToken(requestContext, github);
24954
+ return { refreshed: true };
24955
+ }
24956
+ }),
24893
24957
  github_subscribe_pr: createTool({
24894
24958
  id: "github_subscribe_pr",
24895
24959
  description: "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.",
@@ -25033,20 +25097,27 @@ var SandboxBudgetError = class extends Error {
25033
25097
  max;
25034
25098
  code = "sandbox-budget-exceeded";
25035
25099
  };
25036
- function toMaterializationSandbox(sandbox) {
25100
+ function toMaterializationSandbox(sandbox, initialEnvironment = {}) {
25037
25101
  if (typeof sandbox.executeCommand !== "function") {
25038
25102
  throw new Error(
25039
25103
  `Sandbox provider '${sandbox.provider}' does not implement executeCommand() \u2014 cannot materialize repos.`
25040
25104
  );
25041
25105
  }
25042
25106
  const lifecycle = sandbox;
25107
+ const environment = { ...initialEnvironment };
25043
25108
  return {
25044
25109
  id: sandbox.id,
25045
25110
  start: async () => {
25046
25111
  await (lifecycle._start ?? sandbox.start)?.call(sandbox);
25047
25112
  },
25048
25113
  getInfo: async () => await sandbox.getInfo?.() ?? {},
25049
- executeCommand: (command, args, options) => sandbox.executeCommand(command, args, options),
25114
+ executeCommand: (command, args, options) => sandbox.executeCommand(command, args, {
25115
+ ...options,
25116
+ env: { ...environment, ...options?.env }
25117
+ }),
25118
+ setEnvironmentVariable: (name, value) => {
25119
+ environment[name] = value;
25120
+ },
25050
25121
  stop: async () => {
25051
25122
  await (lifecycle._stop ?? sandbox.stop)?.call(sandbox);
25052
25123
  }
@@ -25186,7 +25257,7 @@ var SandboxFleet = class {
25186
25257
  ...opts.idleTimeoutMinutes !== void 0 ? { idleTimeoutMinutes: opts.idleTimeoutMinutes } : {},
25187
25258
  ...opts.checkpointName ? { checkpointName: opts.checkpointName } : {}
25188
25259
  });
25189
- return toMaterializationSandbox(clone);
25260
+ return toMaterializationSandbox(clone, opts.env);
25190
25261
  }
25191
25262
  async ensureSandbox(store, envOrProgress, progressOrOptions, maybeOptions = {}) {
25192
25263
  const env = typeof envOrProgress === "function" ? void 0 : envOrProgress;
@@ -25356,8 +25427,8 @@ function bindingStore(row, storage) {
25356
25427
  };
25357
25428
  }
25358
25429
  async function ensureProjectSandbox(options) {
25359
- const { fleet, row, storage, onProgress } = options;
25360
- return fleet.ensureSandbox(bindingStore(row, storage), onProgress);
25430
+ const { fleet, row, storage, token, onProgress } = options;
25431
+ return fleet.ensureSandbox(bindingStore(row, storage), { GH_TOKEN: token }, onProgress);
25361
25432
  }
25362
25433
  async function teardownProjectSandbox(options) {
25363
25434
  const { fleet, row, storage, sandbox } = options;
@@ -26073,6 +26144,7 @@ function polledIssueEvent(project, issue) {
26073
26144
  number: issue.number,
26074
26145
  title: issue.title,
26075
26146
  html_url: issue.url,
26147
+ created_at: issue.createdAt,
26076
26148
  labels: issue.labels.map((name) => ({ name }))
26077
26149
  }
26078
26150
  }
@@ -26092,6 +26164,7 @@ function polledPullRequestEvent(project, pullRequest) {
26092
26164
  number: pullRequest.number,
26093
26165
  title: pullRequest.title,
26094
26166
  html_url: pullRequest.url,
26167
+ created_at: pullRequest.createdAt,
26095
26168
  state: "open",
26096
26169
  merged: false,
26097
26170
  head: { ref: pullRequest.headBranch },
@@ -26621,13 +26694,6 @@ async function loadOrCreateSandboxRow(github, project, userId) {
26621
26694
  async function prepareProject(options) {
26622
26695
  const { github, fleet, project, userId, onProgress } = options;
26623
26696
  const sandboxRow = await loadOrCreateSandboxRow(github, project, userId);
26624
- const sandbox = await ensureProjectSandbox({
26625
- fleet,
26626
- row: sandboxRow,
26627
- storage: github.sourceControlStorage.sandboxes,
26628
- onProgress
26629
- });
26630
- const fresh = await github.sourceControlStorage.sandboxes.getById({ id: sandboxRow.id });
26631
26697
  const access = await github.versionControl.getRepositoryAccess({
26632
26698
  orgId: project.installation.orgId,
26633
26699
  repositoryId: project.repository.id
@@ -26635,6 +26701,14 @@ async function prepareProject(options) {
26635
26701
  if (!access.authorization) {
26636
26702
  throw new MaterializeError("Repository access did not include a bearer token.", "clone-failed");
26637
26703
  }
26704
+ const sandbox = await ensureProjectSandbox({
26705
+ fleet,
26706
+ row: sandboxRow,
26707
+ storage: github.sourceControlStorage.sandboxes,
26708
+ token: access.authorization.token,
26709
+ onProgress
26710
+ });
26711
+ const fresh = await github.sourceControlStorage.sandboxes.getById({ id: sandboxRow.id });
26638
26712
  const finalRow = fresh ?? sandboxRow;
26639
26713
  await materializeRepo({
26640
26714
  row: finalRow,
@@ -27240,6 +27314,7 @@ var PlatformGithubEventWorker = class extends MastraWorker {
27240
27314
  #controller;
27241
27315
  #github;
27242
27316
  #storage;
27317
+ #ingestFactoryEvent;
27243
27318
  #intervalMs;
27244
27319
  #now;
27245
27320
  #dispatch;
@@ -27259,6 +27334,7 @@ var PlatformGithubEventWorker = class extends MastraWorker {
27259
27334
  this.#controller = config.controller;
27260
27335
  this.#github = config.github;
27261
27336
  this.#storage = config.storage;
27337
+ this.#ingestFactoryEvent = config.ingestFactoryEvent;
27262
27338
  this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
27263
27339
  if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {
27264
27340
  throw new Error("Platform GitHub event polling interval must be a positive number.");
@@ -27416,6 +27492,9 @@ var PlatformGithubEventWorker = class extends MastraWorker {
27416
27492
  });
27417
27493
  continue;
27418
27494
  }
27495
+ if (isFactoryClosureEvent(parsed)) {
27496
+ await this.#ingestFactoryEvent?.(parsed);
27497
+ }
27419
27498
  const result = await this.#dispatch(parsed, {
27420
27499
  controller: this.#controller,
27421
27500
  listSubscriptions: (target, options) => listPullRequestSubscriptionsForWebhook(target, options, this.#github.integrationStorage),
@@ -27481,6 +27560,9 @@ function normalizeSettings(value) {
27481
27560
  }
27482
27561
  return { version: 1, repositories: { ...value.repositories } };
27483
27562
  }
27563
+ function isFactoryClosureEvent(event) {
27564
+ return (event.event === "issues" || event.event === "pull_request") && event.payload.action === "closed";
27565
+ }
27484
27566
  function parseEvent(event) {
27485
27567
  if (!event.id || !event.deliveryId || !SUPPORTED_EVENTS.has(event.event) || !event.payload || typeof event.payload !== "object" || Array.isArray(event.payload)) {
27486
27568
  return null;
@@ -27829,6 +27911,7 @@ var PlatformGithubIntegration = class {
27829
27911
  controller: ctx.controller,
27830
27912
  github: this,
27831
27913
  storage: ctx.storage.generic,
27914
+ ingestFactoryEvent: ctx.hooks?.ingestGithubEvent,
27832
27915
  intervalMs: this.#pollingIntervalMs
27833
27916
  })
27834
27917
  ];
@@ -29065,6 +29148,158 @@ function isNotFound2(error) {
29065
29148
  return error instanceof PlatformApiError && error.status === 404;
29066
29149
  }
29067
29150
 
29151
+ // src/routes/custom-provider-source.ts
29152
+ import { setCustomProvidersSource } from "@mastra/code-sdk/agents/custom-provider-source";
29153
+
29154
+ // src/routes/provider-credentials.ts
29155
+ function getAuthProviderId(provider) {
29156
+ return provider === "openai" ? "openai-codex" : provider;
29157
+ }
29158
+ var WEB_OAUTH_FLOW_KINDS = {
29159
+ anthropic: "paste-code",
29160
+ openai: "device-code",
29161
+ "github-copilot": "device-code",
29162
+ xai: "device-code"
29163
+ };
29164
+ async function getTenantCredentialsStorage(credentials) {
29165
+ if (!credentials) return void 0;
29166
+ try {
29167
+ await credentials.ensureReady();
29168
+ } catch {
29169
+ return void 0;
29170
+ }
29171
+ return credentials;
29172
+ }
29173
+ function tenantOrgId(tenant) {
29174
+ return tenant.orgId ?? `user:${tenant.userId}`;
29175
+ }
29176
+ async function resolveCredentialContext({
29177
+ c,
29178
+ auth,
29179
+ credentials
29180
+ }) {
29181
+ await auth.ensureUser(c);
29182
+ const tenant = auth.tenant(c);
29183
+ if (!tenant) {
29184
+ if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
29185
+ return { mode: "local" };
29186
+ }
29187
+ const storage = await getTenantCredentialsStorage(credentials);
29188
+ if (!storage) {
29189
+ return {
29190
+ response: c.json(
29191
+ {
29192
+ error: "credentials_unavailable",
29193
+ message: "Tenant credential storage is unavailable \u2014 the app database is not configured or failed to start."
29194
+ },
29195
+ 503
29196
+ )
29197
+ };
29198
+ }
29199
+ return { mode: "tenant", storage, orgId: tenantOrgId(tenant), userId: tenant.userId };
29200
+ }
29201
+ async function listTenantCredentialsForRequest({
29202
+ c,
29203
+ auth,
29204
+ credentials
29205
+ }) {
29206
+ await auth.ensureUser(c);
29207
+ const tenant = auth.tenant(c);
29208
+ if (!tenant) return auth.enabled() ? [] : void 0;
29209
+ const storage = await getTenantCredentialsStorage(credentials);
29210
+ if (!storage) return [];
29211
+ return storage.listCredentials(tenantOrgId(tenant), tenant.userId);
29212
+ }
29213
+
29214
+ // src/routes/custom-provider-source.ts
29215
+ var SNAPSHOT_TTL_MS = 15e3;
29216
+ var MAX_CACHED_ORGS = 1e3;
29217
+ var LOCAL_ORG = "local";
29218
+ var OrgCustomProvidersSnapshot = class {
29219
+ #orgId;
29220
+ #storage;
29221
+ #snapshot = [];
29222
+ #fetchedAt = 0;
29223
+ #hydrating;
29224
+ constructor(orgId, storage) {
29225
+ this.#orgId = orgId;
29226
+ this.#storage = storage;
29227
+ }
29228
+ /** Hydrate the snapshot when stale; coalesces concurrent callers. */
29229
+ async ensureFresh(now = Date.now()) {
29230
+ if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;
29231
+ this.#hydrating ??= this.#hydrate().finally(() => {
29232
+ this.#hydrating = void 0;
29233
+ });
29234
+ await this.#hydrating;
29235
+ }
29236
+ async #hydrate() {
29237
+ await this.#storage.ensureReady();
29238
+ const records = await this.#storage.list({ orgId: this.#orgId });
29239
+ this.#snapshot = records.map((record) => ({
29240
+ name: record.name,
29241
+ url: record.url,
29242
+ apiKey: record.apiKey ?? void 0,
29243
+ models: record.models
29244
+ }));
29245
+ this.#fetchedAt = Date.now();
29246
+ }
29247
+ /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */
29248
+ get() {
29249
+ if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {
29250
+ void this.ensureFresh().catch(() => {
29251
+ });
29252
+ }
29253
+ return this.#snapshot;
29254
+ }
29255
+ };
29256
+ var orgSnapshots = /* @__PURE__ */ new Map();
29257
+ function snapshotFor(orgId, storage) {
29258
+ let snapshot = orgSnapshots.get(orgId);
29259
+ if (!snapshot) {
29260
+ if (orgSnapshots.size >= MAX_CACHED_ORGS) {
29261
+ const oldest = orgSnapshots.keys().next().value;
29262
+ if (oldest !== void 0) orgSnapshots.delete(oldest);
29263
+ }
29264
+ snapshot = new OrgCustomProvidersSnapshot(orgId, storage);
29265
+ orgSnapshots.set(orgId, snapshot);
29266
+ }
29267
+ return snapshot;
29268
+ }
29269
+ function orgForTenant(tenant, authEnabled) {
29270
+ if (!authEnabled) return LOCAL_ORG;
29271
+ return tenant ? tenantOrgId(tenant) : void 0;
29272
+ }
29273
+ function registerCustomProvidersSource({
29274
+ storage,
29275
+ authEnabled
29276
+ }) {
29277
+ setCustomProvidersSource((tenant) => {
29278
+ const orgId = orgForTenant(tenant, authEnabled);
29279
+ if (!orgId) return [];
29280
+ return snapshotFor(orgId, storage).get();
29281
+ });
29282
+ }
29283
+ function invalidateCustomProvidersSnapshots(tenant) {
29284
+ orgSnapshots.delete(tenant.orgId);
29285
+ }
29286
+ function createCustomProvidersPrimer({
29287
+ auth,
29288
+ storage,
29289
+ authEnabled
29290
+ }) {
29291
+ return async (c, next) => {
29292
+ const orgId = orgForTenant(auth.tenant(c), authEnabled);
29293
+ if (orgId) {
29294
+ try {
29295
+ await snapshotFor(orgId, storage).ensureFresh();
29296
+ } catch {
29297
+ }
29298
+ }
29299
+ await next();
29300
+ };
29301
+ }
29302
+
29068
29303
  // src/routes/projects.ts
29069
29304
  import { registerApiRoute as registerApiRoute6 } from "@mastra/core/server";
29070
29305
 
@@ -29931,6 +30166,11 @@ var FactoryGithubEventService = class {
29931
30166
  return results[0] ?? { status: "ignored" };
29932
30167
  }
29933
30168
  async #ingestProject(parsed, event, installationId, repositoryId, repositoryName, login, project) {
30169
+ const factoryProject = await this.options.projects.get({
30170
+ orgId: project.orgId,
30171
+ id: project.factoryProjectId
30172
+ });
30173
+ if (!factoryProject) return { status: "missing" };
29934
30174
  const issue = object(parsed.payload.issue);
29935
30175
  const pullRequest = object(parsed.payload.pull_request);
29936
30176
  const issueNumber = number(issue?.number);
@@ -29975,13 +30215,22 @@ var FactoryGithubEventService = class {
29975
30215
  } : {},
29976
30216
  event,
29977
30217
  deliveryId: parsed.deliveryId,
30218
+ factory: { createdAt: factoryProject.createdAt.toISOString() },
29978
30219
  repository: { id: repositoryId, fullName: repositoryName },
29979
- ...issueNumber && string(issue?.title) && string(issue?.html_url) ? { issue: { number: issueNumber, title: string(issue?.title), url: string(issue?.html_url) } } : {},
30220
+ ...issueNumber && string(issue?.title) && string(issue?.html_url) ? {
30221
+ issue: {
30222
+ number: issueNumber,
30223
+ title: string(issue?.title),
30224
+ url: string(issue?.html_url),
30225
+ ...string(issue?.created_at) ? { createdAt: string(issue?.created_at) } : {}
30226
+ }
30227
+ } : {},
29980
30228
  ...pullRequestNumber && string(pullRequest?.title) && string(pullRequest?.html_url) ? {
29981
30229
  pullRequest: {
29982
30230
  number: pullRequestNumber,
29983
30231
  title: string(pullRequest?.title),
29984
30232
  url: string(pullRequest?.html_url),
30233
+ ...string(pullRequest?.created_at) ? { createdAt: string(pullRequest?.created_at) } : {},
29985
30234
  state: string(pullRequest?.state) === "closed" ? "closed" : "open",
29986
30235
  merged: boolean(pullRequest?.merged) ?? false,
29987
30236
  headBranch: string(object(pullRequest?.head)?.ref) ?? "",
@@ -30478,82 +30727,10 @@ var FactoryTransitionService = class {
30478
30727
  };
30479
30728
 
30480
30729
  // src/routes/config.ts
30481
- import { removeCustomPackFromSettings } from "@mastra/code-sdk/onboarding/custom-packs";
30482
- import {
30483
- removeCustomProviderFromSettings,
30484
- upsertCustomProviderInSettings
30485
- } from "@mastra/code-sdk/onboarding/custom-providers";
30486
- import { applyOmRoleOverride, persistOmObserveAttachments } from "@mastra/code-sdk/onboarding/om-settings";
30730
+ import { DEFAULT_OM_MODEL_ID } from "@mastra/code-sdk/constants";
30487
30731
  import { getAvailableModePacks } from "@mastra/code-sdk/onboarding/packs";
30488
- import {
30489
- getCustomProviderId,
30490
- loadSettings,
30491
- saveSettings,
30492
- THREAD_ACTIVE_MODEL_PACK_ID_KEY
30493
- } from "@mastra/code-sdk/onboarding/settings";
30732
+ import { getCustomProviderId, THREAD_ACTIVE_MODEL_PACK_ID_KEY } from "@mastra/code-sdk/onboarding/settings";
30494
30733
  import { registerApiRoute as registerApiRoute7 } from "@mastra/core/server";
30495
-
30496
- // src/routes/provider-credentials.ts
30497
- function getAuthProviderId(provider) {
30498
- return provider === "openai" ? "openai-codex" : provider;
30499
- }
30500
- var WEB_OAUTH_FLOW_KINDS = {
30501
- anthropic: "paste-code",
30502
- openai: "device-code",
30503
- "github-copilot": "device-code",
30504
- xai: "device-code"
30505
- };
30506
- async function getTenantCredentialsStorage(credentials) {
30507
- if (!credentials) return void 0;
30508
- try {
30509
- await credentials.ensureReady();
30510
- } catch {
30511
- return void 0;
30512
- }
30513
- return credentials;
30514
- }
30515
- function tenantOrgId(tenant) {
30516
- return tenant.orgId ?? `user:${tenant.userId}`;
30517
- }
30518
- async function resolveCredentialContext({
30519
- c,
30520
- auth,
30521
- credentials
30522
- }) {
30523
- await auth.ensureUser(c);
30524
- const tenant = auth.tenant(c);
30525
- if (!tenant) {
30526
- if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
30527
- return { mode: "local" };
30528
- }
30529
- const storage = await getTenantCredentialsStorage(credentials);
30530
- if (!storage) {
30531
- return {
30532
- response: c.json(
30533
- {
30534
- error: "credentials_unavailable",
30535
- message: "Tenant credential storage is unavailable \u2014 the app database is not configured or failed to start."
30536
- },
30537
- 503
30538
- )
30539
- };
30540
- }
30541
- return { mode: "tenant", storage, orgId: tenantOrgId(tenant), userId: tenant.userId };
30542
- }
30543
- async function listTenantCredentialsForRequest({
30544
- c,
30545
- auth,
30546
- credentials
30547
- }) {
30548
- await auth.ensureUser(c);
30549
- const tenant = auth.tenant(c);
30550
- if (!tenant) return auth.enabled() ? [] : void 0;
30551
- const storage = await getTenantCredentialsStorage(credentials);
30552
- if (!storage) return [];
30553
- return storage.listCredentials(tenantOrgId(tenant), tenant.userId);
30554
- }
30555
-
30556
- // src/routes/config.ts
30557
30734
  function loose6(c) {
30558
30735
  return c;
30559
30736
  }
@@ -30597,15 +30774,39 @@ async function listProviders({
30597
30774
  }
30598
30775
  return Array.from(seen.values()).sort((a, b) => a.provider.localeCompare(b.provider));
30599
30776
  }
30600
- function listCustomProviders() {
30601
- const settings = loadSettings();
30602
- return settings.customProviders.map((p) => ({
30603
- id: getCustomProviderId(p.name),
30604
- name: p.name,
30605
- url: p.url,
30606
- hasApiKey: Boolean(p.apiKey),
30607
- models: p.models
30608
- }));
30777
+ function toCustomProviderInfo(record) {
30778
+ return {
30779
+ id: record.providerId,
30780
+ name: record.name,
30781
+ url: record.url,
30782
+ hasApiKey: Boolean(record.apiKey),
30783
+ models: record.models
30784
+ };
30785
+ }
30786
+ async function resolveCustomProvidersContext({
30787
+ c,
30788
+ auth,
30789
+ customProviders
30790
+ }) {
30791
+ await auth.ensureUser(c);
30792
+ const tenant = auth.tenant(c);
30793
+ if (!tenant && auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
30794
+ if (customProviders) {
30795
+ try {
30796
+ await customProviders.ensureReady();
30797
+ return tenant ? { storage: customProviders, orgId: tenantOrgId(tenant), userId: tenant.userId } : { storage: customProviders, orgId: "local", userId: "local" };
30798
+ } catch {
30799
+ }
30800
+ }
30801
+ return {
30802
+ response: c.json(
30803
+ {
30804
+ error: "custom_providers_unavailable",
30805
+ message: "Custom provider storage is unavailable \u2014 the app database is not configured or failed to start."
30806
+ },
30807
+ 503
30808
+ )
30809
+ };
30609
30810
  }
30610
30811
  function parseCustomProviderBody(body) {
30611
30812
  if (!body || typeof body !== "object") return { error: "Invalid JSON body" };
@@ -30670,15 +30871,6 @@ async function buildProviderAccess({
30670
30871
  function canUseModelProvider(access, provider) {
30671
30872
  return Boolean(access[provider]);
30672
30873
  }
30673
- async function getTenantModelPacksStorage(modelPacks) {
30674
- if (!modelPacks) return void 0;
30675
- try {
30676
- await modelPacks.ensureReady();
30677
- } catch {
30678
- return void 0;
30679
- }
30680
- return modelPacks;
30681
- }
30682
30874
  async function resolvePackContext({
30683
30875
  c,
30684
30876
  auth,
@@ -30686,23 +30878,23 @@ async function resolvePackContext({
30686
30878
  }) {
30687
30879
  await auth.ensureUser(c);
30688
30880
  const tenant = auth.tenant(c);
30689
- if (!tenant) {
30690
- if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
30691
- return { mode: "local" };
30692
- }
30693
- const storage = await getTenantModelPacksStorage(modelPacks);
30694
- if (!storage) {
30695
- return {
30696
- response: c.json(
30697
- {
30698
- error: "model_packs_unavailable",
30699
- message: "Model pack storage is unavailable \u2014 the app database is not configured or failed to start."
30700
- },
30701
- 503
30702
- )
30703
- };
30881
+ if (!tenant && auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
30882
+ if (modelPacks) {
30883
+ try {
30884
+ await modelPacks.ensureReady();
30885
+ return tenant ? { storage: modelPacks, orgId: tenantOrgId(tenant), userId: tenant.userId } : { storage: modelPacks, orgId: "local", userId: "local" };
30886
+ } catch {
30887
+ }
30704
30888
  }
30705
- return { mode: "tenant", storage, orgId: tenantOrgId(tenant), userId: tenant.userId };
30889
+ return {
30890
+ response: c.json(
30891
+ {
30892
+ error: "model_packs_unavailable",
30893
+ message: "Model pack storage is unavailable \u2014 the app database is not configured or failed to start."
30894
+ },
30895
+ 503
30896
+ )
30897
+ };
30706
30898
  }
30707
30899
  function recordToModePack(record) {
30708
30900
  return { id: `custom:${record.id}`, name: record.name, description: "Saved custom pack", models: record.models };
@@ -30715,7 +30907,7 @@ async function listModelPacks({
30715
30907
  activePackId
30716
30908
  }) {
30717
30909
  const access = await buildProviderAccess({ controller, authStorage, tenantCredentials });
30718
- const packs = packContext.mode === "local" ? getAvailableModePacks(access, loadSettings().customModelPacks) : [
30910
+ const packs = [
30719
30911
  ...getAvailableModePacks(access),
30720
30912
  ...(await packContext.storage.list({ orgId: packContext.orgId })).map(recordToModePack)
30721
30913
  ];
@@ -30773,20 +30965,57 @@ function readOMConfig(session) {
30773
30965
  observeAttachments: observeAttachments === true || observeAttachments === false ? observeAttachments : "auto"
30774
30966
  };
30775
30967
  }
30776
- function persistOmThreshold({ role, value }) {
30777
- const settings = loadSettings();
30778
- if (role === "observation") settings.models.omObservationThreshold = value;
30779
- else settings.models.omReflectionThreshold = value;
30780
- saveSettings(settings);
30781
- }
30782
- function persistOmRoleOverride({
30783
- role,
30784
- modelId,
30785
- otherRoleCurrentModelId
30968
+ async function resolveMemorySettingsContext({
30969
+ c,
30970
+ auth,
30971
+ memorySettings
30786
30972
  }) {
30787
- const settings = loadSettings();
30788
- applyOmRoleOverride(settings, role, modelId, otherRoleCurrentModelId);
30789
- saveSettings(settings);
30973
+ await auth.ensureUser(c);
30974
+ const tenant = auth.tenant(c);
30975
+ if (!tenant && auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
30976
+ if (memorySettings) {
30977
+ try {
30978
+ await memorySettings.ensureReady();
30979
+ return tenant ? { storage: memorySettings, orgId: tenantOrgId(tenant), userId: tenant.userId } : { storage: memorySettings, orgId: "local", userId: "local" };
30980
+ } catch {
30981
+ }
30982
+ }
30983
+ return {
30984
+ response: c.json(
30985
+ {
30986
+ error: "memory_settings_unavailable",
30987
+ message: "Memory settings storage is unavailable \u2014 the app database is not configured or failed to start."
30988
+ },
30989
+ 503
30990
+ )
30991
+ };
30992
+ }
30993
+ async function persistMemorySettings(context, patch, fillIfUnset) {
30994
+ await context.storage.patch({ orgId: context.orgId, userId: context.userId, patch, fillIfUnset });
30995
+ }
30996
+ async function hydrateSessionMemorySettings(session, record) {
30997
+ for (const role of ["observer", "reflector"]) {
30998
+ const stored = role === "observer" ? record?.observerModelId : record?.reflectorModelId;
30999
+ const target = stored ?? DEFAULT_OM_MODEL_ID;
31000
+ if (session.om[role].modelId() !== target) {
31001
+ await session.om[role].switchModel({ modelId: target });
31002
+ }
31003
+ }
31004
+ const state = session.state.get() ?? {};
31005
+ const updates = {};
31006
+ const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;
31007
+ if (state.observationThreshold !== observationThreshold) {
31008
+ updates.observationThreshold = observationThreshold;
31009
+ }
31010
+ const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;
31011
+ if (state.reflectionThreshold !== reflectionThreshold) {
31012
+ updates.reflectionThreshold = reflectionThreshold;
31013
+ }
31014
+ const observeAttachments = record?.observeAttachments ?? "auto";
31015
+ if ((state.observeAttachments ?? "auto") !== observeAttachments) {
31016
+ updates.observeAttachments = observeAttachments;
31017
+ }
31018
+ if (Object.keys(updates).length > 0) await session.state.set(updates);
30790
31019
  }
30791
31020
  var ConfigRoutes = class extends Route {
30792
31021
  routes() {
@@ -30794,6 +31023,8 @@ var ConfigRoutes = class extends Route {
30794
31023
  const { controller, authStorage, auth } = options;
30795
31024
  const onCredentialsChanged = options.onCredentialsChanged ?? (() => {
30796
31025
  });
31026
+ const onCustomProvidersChanged = options.onCustomProvidersChanged ?? (() => {
31027
+ });
30797
31028
  return [
30798
31029
  registerApiRoute7("/web/config/providers", {
30799
31030
  method: "GET",
@@ -30885,14 +31116,22 @@ var ConfigRoutes = class extends Route {
30885
31116
  }
30886
31117
  }),
30887
31118
  // ── Custom providers (OpenAI-compatible endpoints) ──────────────────────
30888
- // Mirrors the TUI's /custom-providers command. Backed by GlobalSettings
30889
- // (settings.json), not session state these are user-global definitions.
31119
+ // Mirrors the TUI's /custom-providers command, but backed by the
31120
+ // `custom-providers` domain (org rows in tenant mode, a sentinel `local`
31121
+ // org in no-auth mode) — the server never reads settings.json for these.
30890
31122
  registerApiRoute7("/web/config/custom-providers", {
30891
31123
  method: "GET",
30892
31124
  requiresAuth: false,
30893
- handler: (c) => {
31125
+ handler: async (c) => {
31126
+ const ctx = await resolveCustomProvidersContext({
31127
+ c: loose6(c),
31128
+ auth,
31129
+ customProviders: options.customProviders
31130
+ });
31131
+ if ("response" in ctx) return ctx.response;
30894
31132
  try {
30895
- return c.json({ providers: listCustomProviders() });
31133
+ const records = await ctx.storage.list({ orgId: ctx.orgId });
31134
+ return c.json({ providers: records.map(toCustomProviderInfo) });
30896
31135
  } catch (error) {
30897
31136
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
30898
31137
  }
@@ -30902,6 +31141,12 @@ var ConfigRoutes = class extends Route {
30902
31141
  method: "POST",
30903
31142
  requiresAuth: false,
30904
31143
  handler: async (c) => {
31144
+ const ctx = await resolveCustomProvidersContext({
31145
+ c: loose6(c),
31146
+ auth,
31147
+ customProviders: options.customProviders
31148
+ });
31149
+ if ("response" in ctx) return ctx.response;
30905
31150
  let body;
30906
31151
  try {
30907
31152
  body = await c.req.json();
@@ -30912,11 +31157,20 @@ var ConfigRoutes = class extends Route {
30912
31157
  if ("error" in parsed) return c.json({ error: parsed.error }, 400);
30913
31158
  const previousId = body && typeof body === "object" && typeof body.previousId === "string" ? body.previousId : void 0;
30914
31159
  try {
30915
- const settings = loadSettings();
30916
- upsertCustomProviderInSettings(settings, parsed, previousId);
30917
- saveSettings(settings);
30918
- const id = getCustomProviderId(parsed.name);
30919
- return c.json({ ok: true, provider: listCustomProviders().find((p) => p.id === id) });
31160
+ const record = await ctx.storage.upsert({
31161
+ orgId: ctx.orgId,
31162
+ userId: ctx.userId,
31163
+ input: {
31164
+ providerId: getCustomProviderId(parsed.name),
31165
+ name: parsed.name,
31166
+ url: parsed.url,
31167
+ apiKey: parsed.apiKey,
31168
+ models: parsed.models
31169
+ },
31170
+ previousProviderId: previousId
31171
+ });
31172
+ onCustomProvidersChanged({ orgId: ctx.orgId });
31173
+ return c.json({ ok: true, provider: toCustomProviderInfo(record) });
30920
31174
  } catch (error) {
30921
31175
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
30922
31176
  }
@@ -30925,12 +31179,17 @@ var ConfigRoutes = class extends Route {
30925
31179
  registerApiRoute7("/web/config/custom-providers/:id", {
30926
31180
  method: "DELETE",
30927
31181
  requiresAuth: false,
30928
- handler: (c) => {
31182
+ handler: async (c) => {
31183
+ const ctx = await resolveCustomProvidersContext({
31184
+ c: loose6(c),
31185
+ auth,
31186
+ customProviders: options.customProviders
31187
+ });
31188
+ if ("response" in ctx) return ctx.response;
30929
31189
  const id = c.req.param("id");
30930
31190
  try {
30931
- const settings = loadSettings();
30932
- removeCustomProviderFromSettings(settings, id);
30933
- saveSettings(settings);
31191
+ await ctx.storage.delete({ orgId: ctx.orgId, providerId: id });
31192
+ onCustomProvidersChanged({ orgId: ctx.orgId });
30934
31193
  return c.json({ ok: true });
30935
31194
  } catch (error) {
30936
31195
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -30959,8 +31218,30 @@ var ConfigRoutes = class extends Route {
30959
31218
  tenantCredentials
30960
31219
  })
30961
31220
  ]);
31221
+ const catalog = models.filter((m) => canUseModelProvider(access, m.provider) && typeof m.id === "string").map((m) => ({ id: m.id, provider: m.provider, modelName: m.modelName, hasApiKey: true }));
31222
+ if (options.customProviders) {
31223
+ try {
31224
+ const ctx = await resolveCustomProvidersContext({
31225
+ c: loose6(c),
31226
+ auth,
31227
+ customProviders: options.customProviders
31228
+ });
31229
+ if (!("response" in ctx)) {
31230
+ const known = new Set(catalog.map((m) => m.id));
31231
+ for (const record of await ctx.storage.list({ orgId: ctx.orgId })) {
31232
+ for (const model of record.models) {
31233
+ const id = `${record.providerId}/${model}`;
31234
+ if (known.has(id)) continue;
31235
+ known.add(id);
31236
+ catalog.push({ id, provider: record.providerId, modelName: model, hasApiKey: true });
31237
+ }
31238
+ }
31239
+ }
31240
+ } catch {
31241
+ }
31242
+ }
30962
31243
  return c.json({
30963
- models: models.filter((m) => canUseModelProvider(access, m.provider) && typeof m.id === "string").map((m) => ({ id: m.id, provider: m.provider, modelName: m.modelName, hasApiKey: true })).sort(
31244
+ models: catalog.sort(
30964
31245
  (a, b) => a.provider === b.provider ? a.id.localeCompare(b.id) : a.provider.localeCompare(b.provider)
30965
31246
  )
30966
31247
  });
@@ -30970,10 +31251,10 @@ var ConfigRoutes = class extends Route {
30970
31251
  }
30971
31252
  }),
30972
31253
  // ── Model packs ─────────────────────────────────────────────────────────
30973
- // Mirrors the TUI's /models-pack command. Custom-pack CRUD is DB-backed in
30974
- // tenant mode (model-packs storage domain, org-scoped) and settings.json in
30975
- // local mode; activation is session-scoped and resolves the session from
30976
- // the controller registry by resourceId.
31254
+ // Mirrors the TUI's /models-pack command. Custom-pack CRUD lives in the
31255
+ // model-packs storage domain (org-scoped, sentinel `local` org in no-auth
31256
+ // mode — never settings.json); activation is session-scoped and resolves
31257
+ // the session from the controller registry by resourceId.
30977
31258
  registerApiRoute7("/web/config/model-packs", {
30978
31259
  method: "GET",
30979
31260
  requiresAuth: false,
@@ -31027,21 +31308,12 @@ var ConfigRoutes = class extends Route {
31027
31308
  return c.json({ error: "models.build, models.plan and models.fast are required" }, 400);
31028
31309
  }
31029
31310
  try {
31030
- if (packContext.mode === "tenant") {
31031
- const record = await packContext.storage.upsert({
31032
- orgId: packContext.orgId,
31033
- userId: packContext.userId,
31034
- input: { name, models: { build, plan, fast } }
31035
- });
31036
- return c.json({ ok: true, pack: recordToModePack(record) });
31037
- }
31038
- const settings = loadSettings();
31039
- const entry = { name, models: { build, plan, fast }, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
31040
- const idx = settings.customModelPacks.findIndex((p) => p.name === name);
31041
- if (idx >= 0) settings.customModelPacks[idx] = entry;
31042
- else settings.customModelPacks.push(entry);
31043
- saveSettings(settings);
31044
- return c.json({ ok: true, pack: { id: `custom:${name}`, name, models: { build, plan, fast } } });
31311
+ const record = await packContext.storage.upsert({
31312
+ orgId: packContext.orgId,
31313
+ userId: packContext.userId,
31314
+ input: { name, models: { build, plan, fast } }
31315
+ });
31316
+ return c.json({ ok: true, pack: recordToModePack(record) });
31045
31317
  } catch (error) {
31046
31318
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
31047
31319
  }
@@ -31055,15 +31327,9 @@ var ConfigRoutes = class extends Route {
31055
31327
  if ("response" in packContext) return packContext.response;
31056
31328
  const id = decodeURIComponent(c.req.param("id"));
31057
31329
  try {
31058
- if (packContext.mode === "tenant") {
31059
- const recordId = id.startsWith("custom:") ? id.slice("custom:".length) : id;
31060
- const deleted = await packContext.storage.delete({ orgId: packContext.orgId, id: recordId });
31061
- return deleted ? c.json({ ok: true }) : c.json({ error: `Unknown pack "${id}"` }, 404);
31062
- }
31063
- const settings = loadSettings();
31064
- removeCustomPackFromSettings(settings, id);
31065
- saveSettings(settings);
31066
- return c.json({ ok: true });
31330
+ const recordId = id.startsWith("custom:") ? id.slice("custom:".length) : id;
31331
+ const deleted = await packContext.storage.delete({ orgId: packContext.orgId, id: recordId });
31332
+ return deleted ? c.json({ ok: true }) : c.json({ error: `Unknown pack "${id}"` }, 404);
31067
31333
  } catch (error) {
31068
31334
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
31069
31335
  }
@@ -31110,8 +31376,10 @@ var ConfigRoutes = class extends Route {
31110
31376
  }),
31111
31377
  // ── Observational memory ──────────────────────────────────────────────────
31112
31378
  // Mirrors the TUI's /om command. All five knobs are session-scoped (resolved
31113
- // from the session, persisted to its state + thread setting) plus written to
31114
- // GlobalSettings so the choice survives restarts and stays in sync with the TUI.
31379
+ // from the session, persisted to its state + thread setting) and durably
31380
+ // stored in the per-(org, user) `memory-settings` app table never
31381
+ // settings.json. GET hydrates the session from the stored row first so the
31382
+ // DB, not the SDK's boot-time seed, is the source of truth.
31115
31383
  registerApiRoute7("/web/config/om", {
31116
31384
  method: "GET",
31117
31385
  requiresAuth: false,
@@ -31119,9 +31387,17 @@ var ConfigRoutes = class extends Route {
31119
31387
  const resourceId = c.req.query("resourceId");
31120
31388
  const scope = c.req.query("scope") || void 0;
31121
31389
  if (!resourceId) return c.json({ error: "Missing required query param: resourceId" }, 400);
31390
+ const context = await resolveMemorySettingsContext({
31391
+ c: loose6(c),
31392
+ auth,
31393
+ memorySettings: options.memorySettings
31394
+ });
31395
+ if ("response" in context) return context.response;
31122
31396
  try {
31123
31397
  const session = await controller.getSessionByResource?.(resourceId, scope);
31124
31398
  if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
31399
+ const record = await context.storage.get({ orgId: context.orgId, userId: context.userId });
31400
+ await hydrateSessionMemorySettings(session, record);
31125
31401
  return c.json({ config: readOMConfig(session) });
31126
31402
  } catch (error) {
31127
31403
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -31147,13 +31423,24 @@ var ConfigRoutes = class extends Route {
31147
31423
  const modelId = typeof body.modelId === "string" ? body.modelId.trim() : "";
31148
31424
  if (!resourceId) return c.json({ error: "Missing required field: resourceId" }, 400);
31149
31425
  if (!modelId) return c.json({ error: "Missing required field: modelId" }, 400);
31426
+ const context = await resolveMemorySettingsContext({
31427
+ c: loose6(c),
31428
+ auth,
31429
+ memorySettings: options.memorySettings
31430
+ });
31431
+ if ("response" in context) return context.response;
31150
31432
  try {
31151
31433
  const session = await controller.getSessionByResource?.(resourceId, scope);
31152
31434
  if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
31153
31435
  const otherRole = role === "observer" ? session.om.reflector : session.om.observer;
31154
31436
  const otherRoleCurrentModelId = otherRole.modelId() ?? null;
31155
31437
  await session.om[role].switchModel({ modelId });
31156
- persistOmRoleOverride({ role, modelId, otherRoleCurrentModelId });
31438
+ const otherKey = role === "observer" ? "reflectorModelId" : "observerModelId";
31439
+ await persistMemorySettings(
31440
+ context,
31441
+ { [role === "observer" ? "observerModelId" : "reflectorModelId"]: modelId },
31442
+ otherRoleCurrentModelId ? { [otherKey]: otherRoleCurrentModelId } : void 0
31443
+ );
31157
31444
  return c.json({ ok: true, config: readOMConfig(session) });
31158
31445
  } catch (error) {
31159
31446
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -31178,19 +31465,27 @@ var ConfigRoutes = class extends Route {
31178
31465
  if (observation === void 0 && reflection === void 0) {
31179
31466
  return c.json({ error: "Provide observationThreshold and/or reflectionThreshold (positive numbers)" }, 400);
31180
31467
  }
31468
+ const context = await resolveMemorySettingsContext({
31469
+ c: loose6(c),
31470
+ auth,
31471
+ memorySettings: options.memorySettings
31472
+ });
31473
+ if ("response" in context) return context.response;
31181
31474
  try {
31182
31475
  const session = await controller.getSessionByResource?.(resourceId, scope);
31183
31476
  if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
31184
31477
  if (observation !== void 0) {
31185
31478
  await session.state.set({ observationThreshold: observation });
31186
31479
  await session.thread.setSetting({ key: "observationThreshold", value: observation });
31187
- persistOmThreshold({ role: "observation", value: observation });
31188
31480
  }
31189
31481
  if (reflection !== void 0) {
31190
31482
  await session.state.set({ reflectionThreshold: reflection });
31191
31483
  await session.thread.setSetting({ key: "reflectionThreshold", value: reflection });
31192
- persistOmThreshold({ role: "reflection", value: reflection });
31193
31484
  }
31485
+ await persistMemorySettings(context, {
31486
+ ...observation !== void 0 ? { observationThreshold: observation } : {},
31487
+ ...reflection !== void 0 ? { reflectionThreshold: reflection } : {}
31488
+ });
31194
31489
  return c.json({ ok: true, config: readOMConfig(session) });
31195
31490
  } catch (error) {
31196
31491
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -31215,12 +31510,18 @@ var ConfigRoutes = class extends Route {
31215
31510
  if (raw !== "auto" && raw !== true && raw !== false) {
31216
31511
  return c.json({ error: "value must be 'auto', true, or false" }, 400);
31217
31512
  }
31513
+ const context = await resolveMemorySettingsContext({
31514
+ c: loose6(c),
31515
+ auth,
31516
+ memorySettings: options.memorySettings
31517
+ });
31518
+ if ("response" in context) return context.response;
31218
31519
  try {
31219
31520
  const session = await controller.getSessionByResource?.(resourceId, scope);
31220
31521
  if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
31221
31522
  await session.state.set({ observeAttachments: value });
31222
31523
  await session.thread.setSetting({ key: "observeAttachments", value });
31223
- persistOmObserveAttachments(value);
31524
+ await persistMemorySettings(context, { observeAttachments: value });
31224
31525
  return c.json({ ok: true, config: readOMConfig(session) });
31225
31526
  } catch (error) {
31226
31527
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -32438,7 +32739,7 @@ var SkillRoutes = class extends Route {
32438
32739
  // src/routes/tenant-credentials.ts
32439
32740
  import { setCredentialStoreProvider } from "@mastra/code-sdk/agents/credential-resolver";
32440
32741
  import { getOAuthProvider } from "@mastra/code-sdk/auth/storage";
32441
- var SNAPSHOT_TTL_MS = 15e3;
32742
+ var SNAPSHOT_TTL_MS2 = 15e3;
32442
32743
  var MAX_CACHED_TENANTS = 1e3;
32443
32744
  var TenantCredentialStore = class {
32444
32745
  allowEnvironmentFallback = false;
@@ -32455,7 +32756,7 @@ var TenantCredentialStore = class {
32455
32756
  }
32456
32757
  /** Hydrate the snapshot when stale; coalesces concurrent callers. */
32457
32758
  async ensureFresh(now = Date.now()) {
32458
- if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;
32759
+ if (now - this.#fetchedAt < SNAPSHOT_TTL_MS2) return;
32459
32760
  this.#hydrating ??= this.#hydrate().finally(() => {
32460
32761
  this.#hydrating = void 0;
32461
32762
  });
@@ -32477,7 +32778,7 @@ var TenantCredentialStore = class {
32477
32778
  }
32478
32779
  /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */
32479
32780
  reload() {
32480
- if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {
32781
+ if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS2) {
32481
32782
  void this.ensureFresh().catch(() => {
32482
32783
  });
32483
32784
  }
@@ -34813,6 +35114,7 @@ function assembleFactoryApiRoutes(deps) {
34813
35114
  github: githubIntegration,
34814
35115
  sourceControl: githubStorage,
34815
35116
  integrationStorage: deps.integrationStorage.forIntegration("github"),
35117
+ projects: deps.domains.projects,
34816
35118
  storage: workItems,
34817
35119
  rules: deps.rules
34818
35120
  }) : void 0;
@@ -34860,7 +35162,10 @@ function assembleFactoryApiRoutes(deps) {
34860
35162
  authStorage: deps.authStorage,
34861
35163
  modelCredentials: deps.domains.modelCredentials,
34862
35164
  modelPacks: deps.domains.modelPacks,
34863
- onCredentialsChanged: invalidateTenantCredentialSnapshots
35165
+ memorySettings: deps.domains.memorySettings,
35166
+ customProviders: deps.domains.customProviders,
35167
+ onCredentialsChanged: invalidateTenantCredentialSnapshots,
35168
+ onCustomProvidersChanged: invalidateCustomProvidersSnapshots
34864
35169
  }).routes(),
34865
35170
  ...new OAuthRoutes({
34866
35171
  auth: deps.auth,
@@ -34950,6 +35255,12 @@ function advanceApprovedPlan(context) {
34950
35255
  stage: "execute"
34951
35256
  };
34952
35257
  }
35258
+ function createdAfterFactory(createdAt, factoryCreatedAt) {
35259
+ if (!createdAt) return false;
35260
+ const sourceCreatedAt = Date.parse(createdAt);
35261
+ const projectCreatedAt = Date.parse(factoryCreatedAt);
35262
+ return Number.isFinite(sourceCreatedAt) && Number.isFinite(projectCreatedAt) && sourceCreatedAt > projectCreatedAt;
35263
+ }
34953
35264
  function issueOpened(context) {
34954
35265
  if (!context.issue) return;
34955
35266
  return {
@@ -34960,7 +35271,7 @@ function issueOpened(context) {
34960
35271
  sourceKey: `github-issue:${context.issue.number}`,
34961
35272
  title: context.issue.title,
34962
35273
  url: context.issue.url,
34963
- stage: trustedGithubActor(context) ? "triage" : "intake",
35274
+ stage: trustedGithubActor(context) && createdAfterFactory(context.issue.createdAt, context.factory.createdAt) ? "triage" : "intake",
34964
35275
  metadata: {
34965
35276
  githubRepositoryId: context.repository.id,
34966
35277
  githubIssueNumber: context.issue.number
@@ -34977,7 +35288,7 @@ function pullRequestOpened(context) {
34977
35288
  sourceKey: `github-pr:${context.pullRequest.number}`,
34978
35289
  title: context.pullRequest.title,
34979
35290
  url: context.pullRequest.url,
34980
- stage: trustedGithubActor(context) ? "review" : "intake",
35291
+ stage: trustedGithubActor(context) && createdAfterFactory(context.pullRequest.createdAt, context.factory.createdAt) ? "review" : "intake",
34981
35292
  metadata: {
34982
35293
  githubRepositoryId: context.repository.id,
34983
35294
  githubPullRequestNumber: context.pullRequest.number,
@@ -36019,10 +36330,13 @@ var MIME = {
36019
36330
  };
36020
36331
  var SERVER_PREFIXES = ["/api", "/web", "/auth"];
36021
36332
  function resolveUiDistDir() {
36333
+ const projectRoot = process.env.MASTRA_PROJECT_ROOT?.trim();
36022
36334
  const candidates = [
36023
36335
  process.env.MASTRACODE_UI_DIST,
36024
- join2(dirname(fileURLToPath(import.meta.url)), "ui"),
36025
- resolve2(process.cwd(), "src/mastra/public/ui")
36336
+ resolve2(process.cwd(), "factory"),
36337
+ join2(dirname(fileURLToPath(import.meta.url)), "factory"),
36338
+ projectRoot && resolve2(projectRoot, "src/mastra/public/factory"),
36339
+ resolve2(process.cwd(), "src/mastra/public/factory")
36026
36340
  ];
36027
36341
  for (const candidate of candidates) {
36028
36342
  if (candidate && existsSync(join2(candidate, "index.html"))) return resolve2(candidate);
@@ -36448,8 +36762,154 @@ var AuditDomain = class {
36448
36762
  }
36449
36763
  };
36450
36764
 
36451
- // src/storage/domains/intake/base.ts
36765
+ // src/storage/domains/custom-providers/base.ts
36452
36766
  import { FactoryStorageDomain as FactoryStorageDomain5, UniqueViolationError as UniqueViolationError5 } from "@mastra/core/storage";
36767
+ var CUSTOM_PROVIDERS_SCHEMA = {
36768
+ name: "custom_providers",
36769
+ columns: {
36770
+ id: { type: "uuid-pk" },
36771
+ org_id: { type: "text" },
36772
+ created_by: { type: "text" },
36773
+ provider_id: { type: "text" },
36774
+ name: { type: "text" },
36775
+ url: { type: "text" },
36776
+ api_key: { type: "text", nullable: true },
36777
+ models: { type: "json" },
36778
+ created_at: { type: "timestamp" },
36779
+ updated_at: { type: "timestamp" }
36780
+ },
36781
+ uniqueIndexes: [{ name: "custom_providers_org_provider_key", columns: ["org_id", "provider_id"] }]
36782
+ };
36783
+ function toRecord(row) {
36784
+ return {
36785
+ id: row.id,
36786
+ orgId: row.org_id,
36787
+ createdBy: row.created_by,
36788
+ providerId: row.provider_id,
36789
+ name: row.name,
36790
+ url: row.url,
36791
+ apiKey: row.api_key,
36792
+ models: Array.isArray(row.models) ? row.models : [],
36793
+ createdAt: row.created_at,
36794
+ updatedAt: row.updated_at
36795
+ };
36796
+ }
36797
+ var CustomProvidersStorage = class extends FactoryStorageDomain5 {
36798
+ constructor() {
36799
+ super("custom-providers");
36800
+ }
36801
+ async init() {
36802
+ await this.ensureCollections([CUSTOM_PROVIDERS_SCHEMA]);
36803
+ }
36804
+ async dangerouslyClearAll() {
36805
+ await this.ops.deleteMany("custom_providers", {});
36806
+ }
36807
+ get #db() {
36808
+ return this.ops;
36809
+ }
36810
+ /**
36811
+ * Create or wholesale-replace a provider by `(orgId, providerId)` — mirrors
36812
+ * the settings.json upsert semantics (no key retention: an absent `apiKey`
36813
+ * clears the stored key). `previousProviderId` renames the provider: the
36814
+ * common path is a single atomic in-place update of the old row; renaming
36815
+ * onto an id that already exists overwrites the target first and removes the
36816
+ * old row last, so no interleaving or failure can lose the provider.
36817
+ */
36818
+ async upsert({
36819
+ orgId,
36820
+ userId,
36821
+ input,
36822
+ previousProviderId
36823
+ }) {
36824
+ const now = /* @__PURE__ */ new Date();
36825
+ const renameFrom = previousProviderId && previousProviderId !== input.providerId ? previousProviderId : void 0;
36826
+ if (renameFrom) {
36827
+ const target = await this.#db.findOne("custom_providers", {
36828
+ org_id: orgId,
36829
+ provider_id: input.providerId
36830
+ });
36831
+ if (!target) {
36832
+ const renamed = await this.#db.updateAtomic(
36833
+ "custom_providers",
36834
+ { org_id: orgId, provider_id: renameFrom },
36835
+ () => ({
36836
+ provider_id: input.providerId,
36837
+ name: input.name,
36838
+ url: input.url,
36839
+ api_key: input.apiKey ?? null,
36840
+ models: input.models,
36841
+ updated_at: now
36842
+ })
36843
+ );
36844
+ if (renamed) return toRecord(renamed);
36845
+ }
36846
+ }
36847
+ const record = await this.#write({ orgId, userId, input, now });
36848
+ if (renameFrom) {
36849
+ await this.#db.deleteMany("custom_providers", { org_id: orgId, provider_id: renameFrom });
36850
+ }
36851
+ return record;
36852
+ }
36853
+ /**
36854
+ * Update-first, then insert-and-catch-unique-violation (see `queue_health`):
36855
+ * concurrent creates of the same provider both succeed (last write wins on
36856
+ * the single row) instead of one failing on the unique index, and a row
36857
+ * deleted mid-flight is recreated rather than reported as a stale success.
36858
+ */
36859
+ async #write({
36860
+ orgId,
36861
+ userId,
36862
+ input,
36863
+ now
36864
+ }) {
36865
+ const updateExisting = () => this.#db.updateAtomic(
36866
+ "custom_providers",
36867
+ { org_id: orgId, provider_id: input.providerId },
36868
+ () => ({
36869
+ name: input.name,
36870
+ url: input.url,
36871
+ api_key: input.apiKey ?? null,
36872
+ models: input.models,
36873
+ updated_at: now
36874
+ })
36875
+ );
36876
+ const updated = await updateExisting();
36877
+ if (updated) return toRecord(updated);
36878
+ try {
36879
+ const row = await this.#db.insertOne("custom_providers", {
36880
+ org_id: orgId,
36881
+ created_by: userId,
36882
+ provider_id: input.providerId,
36883
+ name: input.name,
36884
+ url: input.url,
36885
+ api_key: input.apiKey ?? null,
36886
+ models: input.models,
36887
+ created_at: now,
36888
+ updated_at: now
36889
+ });
36890
+ return toRecord(row);
36891
+ } catch (error) {
36892
+ if (!(error instanceof UniqueViolationError5)) throw error;
36893
+ const row = await updateExisting();
36894
+ if (!row) throw error;
36895
+ return toRecord(row);
36896
+ }
36897
+ }
36898
+ async list({ orgId }) {
36899
+ const rows = await this.#db.findMany(
36900
+ "custom_providers",
36901
+ { org_id: orgId },
36902
+ { orderBy: [["name", "asc"]] }
36903
+ );
36904
+ return rows.map(toRecord);
36905
+ }
36906
+ async delete({ orgId, providerId }) {
36907
+ return await this.#db.deleteMany("custom_providers", { org_id: orgId, provider_id: providerId }) > 0;
36908
+ }
36909
+ };
36910
+
36911
+ // src/storage/domains/intake/base.ts
36912
+ import { FactoryStorageDomain as FactoryStorageDomain6, UniqueViolationError as UniqueViolationError6 } from "@mastra/core/storage";
36453
36913
  var DEFAULT_INTAKE_CONFIG = {};
36454
36914
  var INTAKE_SETTINGS_SCHEMA = {
36455
36915
  name: "intake_settings",
@@ -36463,7 +36923,7 @@ var INTAKE_SETTINGS_SCHEMA = {
36463
36923
  },
36464
36924
  uniqueIndexes: [{ name: "intake_settings_org_user_unique", columns: ["org_id", "user_id"] }]
36465
36925
  };
36466
- var IntakeStorage = class extends FactoryStorageDomain5 {
36926
+ var IntakeStorage = class extends FactoryStorageDomain6 {
36467
36927
  constructor() {
36468
36928
  super("intake");
36469
36929
  }
@@ -36499,14 +36959,14 @@ var IntakeStorage = class extends FactoryStorageDomain5 {
36499
36959
  try {
36500
36960
  await this.#db.insertOne("intake_settings", { ...where, config, created_at: now, updated_at: now });
36501
36961
  } catch (error) {
36502
- if (!(error instanceof UniqueViolationError5)) throw error;
36962
+ if (!(error instanceof UniqueViolationError6)) throw error;
36503
36963
  await this.#db.updateMany("intake_settings", where, { config, updated_at: now });
36504
36964
  }
36505
36965
  }
36506
36966
  };
36507
36967
 
36508
36968
  // src/storage/domains/integrations/base.ts
36509
- import { FactoryStorageDomain as FactoryStorageDomain6, UniqueViolationError as UniqueViolationError6 } from "@mastra/core/storage";
36969
+ import { FactoryStorageDomain as FactoryStorageDomain7, UniqueViolationError as UniqueViolationError7 } from "@mastra/core/storage";
36510
36970
  var INTEGRATION_CONNECTIONS_SCHEMA = {
36511
36971
  name: "integration_connections",
36512
36972
  columns: {
@@ -36559,7 +37019,7 @@ var INTEGRATION_SETTINGS_SCHEMA = {
36559
37019
  { name: "integration_settings_integration_org_user_unique", columns: ["integration_id", "org_id", "user_id"] }
36560
37020
  ]
36561
37021
  };
36562
- var IntegrationStorage = class extends FactoryStorageDomain6 {
37022
+ var IntegrationStorage = class extends FactoryStorageDomain7 {
36563
37023
  constructor() {
36564
37024
  super("integrations");
36565
37025
  }
@@ -36635,7 +37095,7 @@ var IntegrationStorage = class extends FactoryStorageDomain6 {
36635
37095
  });
36636
37096
  return;
36637
37097
  } catch (error) {
36638
- if (!(error instanceof UniqueViolationError6)) throw error;
37098
+ if (!(error instanceof UniqueViolationError7)) throw error;
36639
37099
  lastError = error;
36640
37100
  }
36641
37101
  }
@@ -36740,7 +37200,7 @@ var IntegrationStorage = class extends FactoryStorageDomain6 {
36740
37200
  });
36741
37201
  return;
36742
37202
  } catch (error) {
36743
- if (!(error instanceof UniqueViolationError6)) throw error;
37203
+ if (!(error instanceof UniqueViolationError7)) throw error;
36744
37204
  lastError = error;
36745
37205
  }
36746
37206
  }
@@ -36751,8 +37211,115 @@ var IntegrationStorage = class extends FactoryStorageDomain6 {
36751
37211
  }
36752
37212
  };
36753
37213
 
37214
+ // src/storage/domains/memory-settings/base.ts
37215
+ import { FactoryStorageDomain as FactoryStorageDomain8, UniqueViolationError as UniqueViolationError8 } from "@mastra/core/storage";
37216
+ var MEMORY_SETTINGS_SCHEMA = {
37217
+ name: "memory_settings",
37218
+ columns: {
37219
+ id: { type: "uuid-pk" },
37220
+ org_id: { type: "text" },
37221
+ user_id: { type: "text" },
37222
+ observer_model_id: { type: "text", nullable: true },
37223
+ reflector_model_id: { type: "text", nullable: true },
37224
+ observation_threshold: { type: "integer", nullable: true },
37225
+ reflection_threshold: { type: "integer", nullable: true },
37226
+ // 'auto' | true | false — json keeps the tri-state without string encoding.
37227
+ observe_attachments: { type: "json", nullable: true },
37228
+ created_at: { type: "timestamp" },
37229
+ updated_at: { type: "timestamp" }
37230
+ },
37231
+ uniqueIndexes: [{ name: "memory_settings_org_user_key", columns: ["org_id", "user_id"] }]
37232
+ };
37233
+ function toRecord2(row) {
37234
+ return {
37235
+ orgId: row.org_id,
37236
+ userId: row.user_id,
37237
+ observerModelId: row.observer_model_id,
37238
+ reflectorModelId: row.reflector_model_id,
37239
+ observationThreshold: row.observation_threshold,
37240
+ reflectionThreshold: row.reflection_threshold,
37241
+ observeAttachments: row.observe_attachments,
37242
+ createdAt: row.created_at,
37243
+ updatedAt: row.updated_at
37244
+ };
37245
+ }
37246
+ function patchToColumns(patch) {
37247
+ const columns = {};
37248
+ if (patch.observerModelId !== void 0) columns.observer_model_id = patch.observerModelId;
37249
+ if (patch.reflectorModelId !== void 0) columns.reflector_model_id = patch.reflectorModelId;
37250
+ if (patch.observationThreshold !== void 0) columns.observation_threshold = patch.observationThreshold;
37251
+ if (patch.reflectionThreshold !== void 0) columns.reflection_threshold = patch.reflectionThreshold;
37252
+ if (patch.observeAttachments !== void 0) columns.observe_attachments = patch.observeAttachments;
37253
+ return columns;
37254
+ }
37255
+ var MemorySettingsStorage = class extends FactoryStorageDomain8 {
37256
+ constructor() {
37257
+ super("memory-settings");
37258
+ }
37259
+ async init() {
37260
+ await this.ensureCollections([MEMORY_SETTINGS_SCHEMA]);
37261
+ }
37262
+ async dangerouslyClearAll() {
37263
+ await this.ops.deleteMany("memory_settings", {});
37264
+ }
37265
+ get #db() {
37266
+ return this.ops;
37267
+ }
37268
+ async get({ orgId, userId }) {
37269
+ const row = await this.#db.findOne("memory_settings", { org_id: orgId, user_id: userId });
37270
+ return row ? toRecord2(row) : null;
37271
+ }
37272
+ /**
37273
+ * Upsert the user's row, writing only the knobs present in `patch`.
37274
+ * `fillIfUnset` knobs are written only where the stored value is still
37275
+ * `NULL`, decided inside the atomic update so concurrent explicit writes
37276
+ * win over fills. Concurrent first writes are resolved via the shared
37277
+ * insert-then-catch-unique-violation pattern (see `queue_health`).
37278
+ */
37279
+ async patch({
37280
+ orgId,
37281
+ userId,
37282
+ patch,
37283
+ fillIfUnset
37284
+ }) {
37285
+ const now = /* @__PURE__ */ new Date();
37286
+ const updateExisting = () => this.#db.updateAtomic("memory_settings", { org_id: orgId, user_id: userId }, (row) => {
37287
+ const columns = { ...patchToColumns(patch), updated_at: now };
37288
+ if (fillIfUnset?.observerModelId !== void 0 && row.observer_model_id == null) {
37289
+ columns.observer_model_id = patch.observerModelId ?? fillIfUnset.observerModelId;
37290
+ }
37291
+ if (fillIfUnset?.reflectorModelId !== void 0 && row.reflector_model_id == null) {
37292
+ columns.reflector_model_id = patch.reflectorModelId ?? fillIfUnset.reflectorModelId;
37293
+ }
37294
+ return columns;
37295
+ });
37296
+ const updated = await updateExisting();
37297
+ if (updated) return toRecord2(updated);
37298
+ try {
37299
+ const row = await this.#db.insertOne("memory_settings", {
37300
+ org_id: orgId,
37301
+ user_id: userId,
37302
+ observer_model_id: fillIfUnset?.observerModelId ?? null,
37303
+ reflector_model_id: fillIfUnset?.reflectorModelId ?? null,
37304
+ observation_threshold: null,
37305
+ reflection_threshold: null,
37306
+ observe_attachments: null,
37307
+ ...patchToColumns(patch),
37308
+ created_at: now,
37309
+ updated_at: now
37310
+ });
37311
+ return toRecord2(row);
37312
+ } catch (error) {
37313
+ if (!(error instanceof UniqueViolationError8)) throw error;
37314
+ const row = await updateExisting();
37315
+ if (!row) throw error;
37316
+ return toRecord2(row);
37317
+ }
37318
+ }
37319
+ };
37320
+
36754
37321
  // src/storage/domains/model-packs/base.ts
36755
- import { FactoryStorageDomain as FactoryStorageDomain7 } from "@mastra/core/storage";
37322
+ import { FactoryStorageDomain as FactoryStorageDomain9 } from "@mastra/core/storage";
36756
37323
  var MODEL_PACKS_SCHEMA = {
36757
37324
  name: "model_packs",
36758
37325
  columns: {
@@ -36779,7 +37346,7 @@ function toModelPack(row) {
36779
37346
  updatedAt: row.updated_at
36780
37347
  };
36781
37348
  }
36782
- var ModelPacksStorage = class extends FactoryStorageDomain7 {
37349
+ var ModelPacksStorage = class extends FactoryStorageDomain9 {
36783
37350
  constructor() {
36784
37351
  super("model-packs");
36785
37352
  }
@@ -36843,7 +37410,7 @@ var ModelPacksStorage = class extends FactoryStorageDomain7 {
36843
37410
  };
36844
37411
 
36845
37412
  // src/storage/domains/projects/base.ts
36846
- import { FactoryStorageDomain as FactoryStorageDomain8 } from "@mastra/core/storage";
37413
+ import { FactoryStorageDomain as FactoryStorageDomain10 } from "@mastra/core/storage";
36847
37414
  var FACTORY_PROJECTS_SCHEMA = {
36848
37415
  name: "factory_projects",
36849
37416
  columns: {
@@ -36870,7 +37437,7 @@ function toFactoryProject(row) {
36870
37437
  updatedAt: row.updated_at
36871
37438
  };
36872
37439
  }
36873
- var FactoryProjectsStorage = class extends FactoryStorageDomain8 {
37440
+ var FactoryProjectsStorage = class extends FactoryStorageDomain10 {
36874
37441
  constructor() {
36875
37442
  super("projects");
36876
37443
  }
@@ -36938,7 +37505,7 @@ var FactoryProjectsStorage = class extends FactoryStorageDomain8 {
36938
37505
  };
36939
37506
 
36940
37507
  // src/storage/domains/source-control/base.ts
36941
- import { FactoryStorageDomain as FactoryStorageDomain9, UniqueViolationError as UniqueViolationError7 } from "@mastra/core/storage";
37508
+ import { FactoryStorageDomain as FactoryStorageDomain11, UniqueViolationError as UniqueViolationError9 } from "@mastra/core/storage";
36942
37509
  var FACTORY_PROJECTS = "factory_projects";
36943
37510
  var INSTALLATIONS = "source_control_installations";
36944
37511
  var REPOSITORIES = "source_control_repositories";
@@ -37191,7 +37758,7 @@ function toSession(row) {
37191
37758
  updatedAt: row.updated_at
37192
37759
  };
37193
37760
  }
37194
- var SourceControlStorage = class extends FactoryStorageDomain9 {
37761
+ var SourceControlStorage = class extends FactoryStorageDomain11 {
37195
37762
  constructor() {
37196
37763
  super("source-control");
37197
37764
  }
@@ -37385,7 +37952,7 @@ var SourceControlStorage = class extends FactoryStorageDomain9 {
37385
37952
  });
37386
37953
  return toConnection(row);
37387
37954
  } catch (error) {
37388
- if (!(error instanceof UniqueViolationError7)) throw error;
37955
+ if (!(error instanceof UniqueViolationError9)) throw error;
37389
37956
  const row = await db().findOne(CONNECTIONS, {
37390
37957
  factory_project_id: input.factoryProjectId,
37391
37958
  integration_id: integrationId,
@@ -37514,7 +38081,7 @@ var SourceControlStorage = class extends FactoryStorageDomain9 {
37514
38081
  });
37515
38082
  return toSandbox(row);
37516
38083
  } catch (error) {
37517
- if (!(error instanceof UniqueViolationError7)) throw error;
38084
+ if (!(error instanceof UniqueViolationError9)) throw error;
37518
38085
  const row = await db().findOne(SANDBOXES, where);
37519
38086
  if (!row) throw error;
37520
38087
  return toSandbox(row);
@@ -37627,7 +38194,7 @@ var SourceControlStorage = class extends FactoryStorageDomain9 {
37627
38194
  });
37628
38195
  return toSession(row);
37629
38196
  } catch (error) {
37630
- if (!(error instanceof UniqueViolationError7)) throw error;
38197
+ if (!(error instanceof UniqueViolationError9)) throw error;
37631
38198
  const row = await db().findOne(SESSIONS, {
37632
38199
  project_repository_id: input.projectRepositoryId,
37633
38200
  user_id: input.userId,
@@ -37730,6 +38297,7 @@ var factorySkillExtension = {
37730
38297
  function createWorkspaceFactory(options = {}) {
37731
38298
  const { sandbox: sandboxConfig, github, fleet } = options;
37732
38299
  const isLocalSandbox = sandboxConfig?.machine instanceof LocalSandbox;
38300
+ const githubTokenInjectors = /* @__PURE__ */ new Map();
37733
38301
  return async ({ requestContext, mastra, skillExtension }) => {
37734
38302
  const effectiveSkillExtension = skillExtension ?? factorySkillExtension;
37735
38303
  const ctx = requestContext.get("controller");
@@ -37781,6 +38349,8 @@ function createWorkspaceFactory(options = {}) {
37781
38349
  const existing = mastra?.getWorkspaceById(workspaceId);
37782
38350
  if (existing) {
37783
38351
  existing.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);
38352
+ const injectGithubToken3 = githubTokenInjectors.get(workspaceId);
38353
+ if (injectGithubToken3) registerGithubTokenInjector(requestContext, injectGithubToken3);
37784
38354
  return existing;
37785
38355
  }
37786
38356
  } catch {
@@ -37811,6 +38381,14 @@ function createWorkspaceFactory(options = {}) {
37811
38381
  repoFullName
37812
38382
  });
37813
38383
  if (projectRepository.setupCommand) await runWorktreeSetup(sandbox, workdir, projectRepository.setupCommand);
38384
+ const injectGithubToken2 = (freshToken) => {
38385
+ if (!sandbox.setEnvironmentVariable) {
38386
+ throw new Error("The active sandbox provider does not support runtime GitHub token refresh.");
38387
+ }
38388
+ sandbox.setEnvironmentVariable("GH_TOKEN", freshToken);
38389
+ };
38390
+ githubTokenInjectors.set(workspaceId, injectGithubToken2);
38391
+ registerGithubTokenInjector(requestContext, injectGithubToken2);
37814
38392
  const filesystem = new SandboxFilesystem({ sandbox, workdir });
37815
38393
  const projectSkillPaths = [path2.join(configDir, "skills"), ".claude/skills", ".agents/skills"];
37816
38394
  const skillPaths = [...effectiveSkillExtension?.paths ?? [], ...projectSkillPaths];
@@ -37918,6 +38496,8 @@ var MastraFactory = class {
37918
38496
  const workItemsStorage = storage.registerDomain(new WorkItemsStorage());
37919
38497
  const modelCredentialsStorage = storage.registerDomain(new ModelCredentialsStorage());
37920
38498
  const modelPacksStorage = storage.registerDomain(new ModelPacksStorage());
38499
+ const memorySettingsStorage = storage.registerDomain(new MemorySettingsStorage());
38500
+ const customProvidersStorage = storage.registerDomain(new CustomProvidersStorage());
37921
38501
  const queueHealthStorage = storage.registerDomain(new QueueHealthStorage());
37922
38502
  const integrationStorage = storage.registerDomain(new IntegrationStorage());
37923
38503
  const factoryProjectsStorage = storage.registerDomain(new FactoryProjectsStorage());
@@ -37926,6 +38506,8 @@ var MastraFactory = class {
37926
38506
  intake: intakeStorage,
37927
38507
  modelCredentials: modelCredentialsStorage,
37928
38508
  modelPacks: modelPacksStorage,
38509
+ memorySettings: memorySettingsStorage,
38510
+ customProviders: customProvidersStorage,
37929
38511
  projects: factoryProjectsStorage,
37930
38512
  queueHealth: queueHealthStorage,
37931
38513
  workItems: workItemsStorage
@@ -37974,6 +38556,7 @@ var MastraFactory = class {
37974
38556
  if (auth) {
37975
38557
  registerTenantCredentialResolver(modelCredentialsStorage);
37976
38558
  }
38559
+ registerCustomProvidersSource({ storage: customProvidersStorage, authEnabled: Boolean(auth) });
37977
38560
  for (const integration of integrations) {
37978
38561
  integration.initialize?.({
37979
38562
  storage: integrationStorage.forIntegration(integration.id),
@@ -38038,6 +38621,9 @@ var MastraFactory = class {
38038
38621
  fleet
38039
38622
  }),
38040
38623
  disableGithubSignals: true,
38624
+ // Memory settings live in the factory's `memory-settings` app table (per
38625
+ // org/user), so the host machine's TUI settings.json must not seed them.
38626
+ disableSettingsOmSeed: true,
38041
38627
  storage: storage.getMastraStorage(),
38042
38628
  ...factoryProcessor ? { inputProcessors: [factoryProcessor] } : {},
38043
38629
  ...vector ? { vector } : {},
@@ -38146,12 +38732,25 @@ var MastraFactory = class {
38146
38732
  const uiDist = resolveUiDistDir();
38147
38733
  const spa = uiDist ? [createSpaStaticMiddleware(uiDist)] : [];
38148
38734
  if (!auth) {
38149
- return { ...spa.length ? { middleware: spa } : {}, ...cors, ...onError };
38735
+ return {
38736
+ middleware: [
38737
+ createCustomProvidersPrimer({ auth: routeAuth, storage: customProvidersStorage, authEnabled: false }),
38738
+ ...spa
38739
+ ],
38740
+ ...cors,
38741
+ ...onError
38742
+ };
38150
38743
  }
38151
38744
  return {
38745
+ auth,
38152
38746
  middleware: [
38153
38747
  createFactoryAuthGate(auth),
38154
38748
  createTenantCredentialPrimer({ auth: routeAuth, credentials: modelCredentialsStorage }),
38749
+ createCustomProvidersPrimer({
38750
+ auth: routeAuth,
38751
+ storage: customProvidersStorage,
38752
+ authEnabled: Boolean(auth)
38753
+ }),
38155
38754
  ...spa
38156
38755
  ],
38157
38756
  ...cors,
@@ -38181,6 +38780,10 @@ var MastraFactory = class {
38181
38780
  );
38182
38781
  return {
38183
38782
  ...prepared.mastraArgs,
38783
+ // Same provider on `studio.auth` as on `server.auth` (buildServerConfig):
38784
+ // deployed factories must authenticate BOTH plain API callers and Studio
38785
+ // requests (`x-mastra-client-type: studio` routes to `studio.auth`).
38786
+ ...auth ? { studio: { auth } } : {},
38184
38787
  ...integrationWorkers.length > 0 ? { workers: integrationWorkers } : {}
38185
38788
  };
38186
38789
  }