@mastra/factory 0.1.0-alpha.3 → 0.1.0-alpha.5

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 (72) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/capabilities/intake.d.ts +2 -0
  3. package/dist/capabilities/intake.d.ts.map +1 -1
  4. package/dist/capabilities/version-control.d.ts +6 -0
  5. package/dist/capabilities/version-control.d.ts.map +1 -1
  6. package/dist/factory.d.ts.map +1 -1
  7. package/dist/factory.js +912 -242
  8. package/dist/factory.js.map +1 -1
  9. package/dist/index.js +912 -242
  10. package/dist/index.js.map +1 -1
  11. package/dist/integrations/github/integration.d.ts +2 -2
  12. package/dist/integrations/github/integration.js +45 -12
  13. package/dist/integrations/github/integration.js.map +1 -1
  14. package/dist/integrations/github/provenance.js.map +1 -1
  15. package/dist/integrations/github/routes.js +12 -10
  16. package/dist/integrations/github/routes.js.map +1 -1
  17. package/dist/integrations/github/sandbox.d.ts +1 -0
  18. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  19. package/dist/integrations/github/sandbox.js +2 -2
  20. package/dist/integrations/github/sandbox.js.map +1 -1
  21. package/dist/integrations/github/session-subscriptions.d.ts +3 -0
  22. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  23. package/dist/integrations/github/session-subscriptions.js +30 -0
  24. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  25. package/dist/integrations/github/token-refresh.d.ts +6 -0
  26. package/dist/integrations/github/token-refresh.d.ts.map +1 -0
  27. package/dist/integrations/github/token-refresh.js +17 -0
  28. package/dist/integrations/github/token-refresh.js.map +1 -0
  29. package/dist/integrations/platform/api-client.d.ts +2 -0
  30. package/dist/integrations/platform/api-client.d.ts.map +1 -1
  31. package/dist/integrations/platform/api-client.js +7 -0
  32. package/dist/integrations/platform/api-client.js.map +1 -1
  33. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  34. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  35. package/dist/integrations/platform/github/integration.js +173 -45
  36. package/dist/integrations/platform/github/integration.js.map +1 -1
  37. package/dist/integrations/platform/linear/integration.js +3 -0
  38. package/dist/integrations/platform/linear/integration.js.map +1 -1
  39. package/dist/routes/config.d.ts +16 -11
  40. package/dist/routes/config.d.ts.map +1 -1
  41. package/dist/routes/config.js +222 -109
  42. package/dist/routes/config.js.map +1 -1
  43. package/dist/routes/custom-provider-source.d.ts +52 -0
  44. package/dist/routes/custom-provider-source.d.ts.map +1 -0
  45. package/dist/routes/custom-provider-source.js +107 -0
  46. package/dist/routes/custom-provider-source.js.map +1 -0
  47. package/dist/routes/oauth.js +33 -1
  48. package/dist/routes/oauth.js.map +1 -1
  49. package/dist/routes/surface.d.ts +4 -0
  50. package/dist/routes/surface.d.ts.map +1 -1
  51. package/dist/routes/surface.js +266 -110
  52. package/dist/routes/surface.js.map +1 -1
  53. package/dist/sandbox/fleet.d.ts +3 -0
  54. package/dist/sandbox/fleet.d.ts.map +1 -1
  55. package/dist/sandbox/fleet.js +10 -3
  56. package/dist/sandbox/fleet.js.map +1 -1
  57. package/dist/spa-static.d.ts +4 -5
  58. package/dist/spa-static.d.ts.map +1 -1
  59. package/dist/spa-static.js +5 -2
  60. package/dist/spa-static.js.map +1 -1
  61. package/dist/storage/domains/custom-providers/base.d.ts +57 -0
  62. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
  63. package/dist/storage/domains/custom-providers/base.js +150 -0
  64. package/dist/storage/domains/custom-providers/base.js.map +1 -0
  65. package/dist/storage/domains/memory-settings/base.d.ts +62 -0
  66. package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
  67. package/dist/storage/domains/memory-settings/base.js +111 -0
  68. package/dist/storage/domains/memory-settings/base.js.map +1 -0
  69. package/dist/workspace.d.ts.map +1 -1
  70. package/dist/workspace.js +17 -0
  71. package/dist/workspace.js.map +1 -1
  72. package/package.json +8 -7
package/dist/index.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;
@@ -26623,13 +26694,6 @@ async function loadOrCreateSandboxRow(github, project, userId) {
26623
26694
  async function prepareProject(options) {
26624
26695
  const { github, fleet, project, userId, onProgress } = options;
26625
26696
  const sandboxRow = await loadOrCreateSandboxRow(github, project, userId);
26626
- const sandbox = await ensureProjectSandbox({
26627
- fleet,
26628
- row: sandboxRow,
26629
- storage: github.sourceControlStorage.sandboxes,
26630
- onProgress
26631
- });
26632
- const fresh = await github.sourceControlStorage.sandboxes.getById({ id: sandboxRow.id });
26633
26697
  const access = await github.versionControl.getRepositoryAccess({
26634
26698
  orgId: project.installation.orgId,
26635
26699
  repositoryId: project.repository.id
@@ -26637,6 +26701,14 @@ async function prepareProject(options) {
26637
26701
  if (!access.authorization) {
26638
26702
  throw new MaterializeError("Repository access did not include a bearer token.", "clone-failed");
26639
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 });
26640
26712
  const finalRow = fresh ?? sandboxRow;
26641
26713
  await materializeRepo({
26642
26714
  row: finalRow,
@@ -26956,7 +27028,8 @@ function buildProjectGitRoutes({
26956
27028
  baseBranch: base,
26957
27029
  headBranch: head,
26958
27030
  title,
26959
- body: prBody
27031
+ body: prBody,
27032
+ actingUserId: userId
26960
27033
  });
26961
27034
  await emitAudit?.({
26962
27035
  context: loose(c),
@@ -27142,6 +27215,9 @@ var PlatformApiClient = class {
27142
27215
  accept: "application/json",
27143
27216
  authorization: `Bearer ${this.#accessToken}`
27144
27217
  };
27218
+ if (options?.actingUserId) {
27219
+ headers["x-acting-user-id"] = options.actingUserId;
27220
+ }
27145
27221
  const timeoutSignal = AbortSignal.timeout(15e3);
27146
27222
  const init = {
27147
27223
  method,
@@ -27193,6 +27269,9 @@ function redact(message, accessToken) {
27193
27269
  function logPlatformInfo(message, fields) {
27194
27270
  writePlatformLog("info", message, fields);
27195
27271
  }
27272
+ function logPlatformWarn(message, fields) {
27273
+ writePlatformLog("warn", message, fields);
27274
+ }
27196
27275
  function logPlatformError(message, fields) {
27197
27276
  writePlatformLog("error", message, fields);
27198
27277
  }
@@ -27511,6 +27590,11 @@ function retryDelay(error, fallbackMs) {
27511
27590
  // src/integrations/platform/github/integration.ts
27512
27591
  var PAGE_SIZE = 30;
27513
27592
  var API_PREFIX2 = "/v1/server";
27593
+ var REPOSITORY_TOKEN_PERMISSIONS = {
27594
+ contents: "write",
27595
+ issues: "write",
27596
+ pull_requests: "write"
27597
+ };
27514
27598
  function loose2(c) {
27515
27599
  return c;
27516
27600
  }
@@ -27624,7 +27708,7 @@ var PlatformGithubIntegration = class {
27624
27708
  return this.#listIssues(input.connection, sourceId, parsePositiveCursor(input.cursor), input.labels);
27625
27709
  },
27626
27710
  getIssue: (input) => this.#getIssue(input.connection, input.sourceId, input.issueId),
27627
- createComment: (input) => this.#createIssueComment(input.connection, input.sourceId, input.issueId, input.body)
27711
+ createComment: (input) => this.#createIssueComment(input)
27628
27712
  };
27629
27713
  versionControl = {
27630
27714
  initialize: ({ storage }) => {
@@ -27663,7 +27747,7 @@ var PlatformGithubIntegration = class {
27663
27747
  const token = await this.#client.request(
27664
27748
  "POST",
27665
27749
  `${API_PREFIX2}/github-app/installations/${installationId}/token`,
27666
- { repositories: [repositoryName], permissions: { contents: "write" } }
27750
+ { repositories: [repositoryName], permissions: REPOSITORY_TOKEN_PERMISSIONS }
27667
27751
  );
27668
27752
  return {
27669
27753
  cloneUrl: `https://github.com/${repository.slug}.git`,
@@ -27727,6 +27811,7 @@ var PlatformGithubIntegration = class {
27727
27811
  return [
27728
27812
  this.#statusRoute(ctx),
27729
27813
  this.#connectRoute(ctx),
27814
+ this.#connectUserRoute(ctx),
27730
27815
  ...buildGithubRoutes({
27731
27816
  auth: ctx.auth,
27732
27817
  fleet: ctx.fleet,
@@ -27758,11 +27843,16 @@ var PlatformGithubIntegration = class {
27758
27843
  organizationRequired: true,
27759
27844
  connected: false,
27760
27845
  installations: [],
27846
+ userConnected: false,
27847
+ userGithubUsername: null,
27761
27848
  reason: "organization_required",
27762
27849
  diagnostics: this.diagnostics()
27763
27850
  });
27764
27851
  }
27765
- const installations = await this.#syncInstallations(tenant.orgId, tenant.userId);
27852
+ const [installations, userConnection] = await Promise.all([
27853
+ this.#syncInstallations(tenant.orgId, tenant.userId),
27854
+ this.#fetchUserConnection(tenant.userId)
27855
+ ]);
27766
27856
  return c.json({
27767
27857
  enabled: true,
27768
27858
  sandboxEnabled: ctx.fleet.enabled,
@@ -27772,6 +27862,8 @@ var PlatformGithubIntegration = class {
27772
27862
  accountLogin: installation.accountName,
27773
27863
  accountType: installation.accountType
27774
27864
  })),
27865
+ userConnected: userConnection.connected,
27866
+ userGithubUsername: userConnection.githubUsername,
27775
27867
  reason: installations.length > 0 ? "ready" : "not_connected",
27776
27868
  diagnostics: this.diagnostics()
27777
27869
  });
@@ -27806,6 +27898,55 @@ var PlatformGithubIntegration = class {
27806
27898
  }
27807
27899
  });
27808
27900
  }
27901
+ #connectUserRoute(ctx) {
27902
+ return registerApiRoute3("/auth/github/connect-user", {
27903
+ method: "GET",
27904
+ requiresAuth: false,
27905
+ handler: async (c) => {
27906
+ await ctx.auth.ensureUser(loose2(c));
27907
+ const tenant = ctx.auth.tenant(loose2(c));
27908
+ if (!tenant?.orgId) return c.json({ error: "unauthorized" }, 401);
27909
+ const redirectTo = c.req.query("redirectTo") || c.req.query("return_to") || "/";
27910
+ const originator = routeBaseUrl(ctx, c.req.url);
27911
+ logPlatformInfo("Starting Platform GitHub user authorization flow", {
27912
+ orgId: tenant.orgId,
27913
+ redirectTo,
27914
+ originator
27915
+ });
27916
+ const query = new URLSearchParams({
27917
+ userId: tenant.userId,
27918
+ redirectTo,
27919
+ originator
27920
+ });
27921
+ const { url } = await this.#client.request(
27922
+ "GET",
27923
+ `${API_PREFIX2}/github-app/authenticate?${query}`
27924
+ );
27925
+ return c.redirect(url);
27926
+ }
27927
+ });
27928
+ }
27929
+ /**
27930
+ * Personal GitHub connection status for the acting user. Returns
27931
+ * not-connected when the platform predates the user-connection endpoint.
27932
+ */
27933
+ async #fetchUserConnection(userId) {
27934
+ try {
27935
+ const connection = await this.#client.request(
27936
+ "GET",
27937
+ `${API_PREFIX2}/github-app/user-connection?${new URLSearchParams({ userId })}`
27938
+ );
27939
+ if (!connection.connected && connection.reason) {
27940
+ logPlatformWarn("Platform GitHub user connection verification failed", {
27941
+ userId,
27942
+ reason: connection.reason
27943
+ });
27944
+ }
27945
+ return connection;
27946
+ } catch {
27947
+ return { connected: false, githubUsername: null };
27948
+ }
27949
+ }
27809
27950
  async #syncInstallations(orgId, userId) {
27810
27951
  const result = await this.#client.request(
27811
27952
  "GET",
@@ -27901,7 +28042,7 @@ var PlatformGithubIntegration = class {
27901
28042
  const result = await this.#client.request(
27902
28043
  "POST",
27903
28044
  `${API_PREFIX2}/github-app/installations/${installationId}/token`,
27904
- { repositories: repositories.map((repository) => repository.name), permissions: { contents: "write" } }
28045
+ { repositories: repositories.map((repository) => repository.name), permissions: REPOSITORY_TOKEN_PERMISSIONS }
27905
28046
  );
27906
28047
  return result.token;
27907
28048
  }
@@ -27956,15 +28097,16 @@ var PlatformGithubIntegration = class {
27956
28097
  throw error;
27957
28098
  }
27958
28099
  }
27959
- async #createIssueComment(connection, sourceId, issueId, body) {
27960
- requireGithubConnection(connection);
27961
- const repository = requireSource(sourceId, "GitHub Intake requires a repository source.");
27962
- const issueNumber = requirePositiveId(issueId, "issue");
28100
+ async #createIssueComment(input) {
28101
+ requireGithubConnection(input.connection);
28102
+ const repository = requireSource(input.sourceId, "GitHub Intake requires a repository source.");
28103
+ const issueNumber = requirePositiveId(input.issueId, "issue");
27963
28104
  try {
27964
28105
  const comment = await this.#client.request(
27965
28106
  "POST",
27966
28107
  repositoryPath(repository, `issues/${issueNumber}/comments`),
27967
- { body }
28108
+ { body: input.body },
28109
+ { actingUserId: input.actingUserId }
27968
28110
  );
27969
28111
  return { id: String(comment.id), url: comment.htmlUrl };
27970
28112
  } catch (error) {
@@ -28001,29 +28143,40 @@ var PlatformGithubIntegration = class {
28001
28143
  }
28002
28144
  async #createPullRequest(input) {
28003
28145
  requireGithubConnection(input.connection);
28004
- const result = await this.#client.request("POST", repositoryPath(input.sourceId, "pulls"), {
28005
- head: input.headBranch,
28006
- base: input.baseBranch,
28007
- title: input.title,
28008
- body: input.body,
28009
- draft: input.draft
28010
- });
28146
+ const result = await this.#client.request(
28147
+ "POST",
28148
+ repositoryPath(input.sourceId, "pulls"),
28149
+ {
28150
+ head: input.headBranch,
28151
+ base: input.baseBranch,
28152
+ title: input.title,
28153
+ body: input.body,
28154
+ draft: input.draft
28155
+ },
28156
+ { actingUserId: input.actingUserId }
28157
+ );
28011
28158
  return parsePullRequest2(result);
28012
28159
  }
28013
28160
  async #updatePullRequest(input) {
28014
- const result = await this.#client.request("PATCH", pullRequestPath(input, input.pullRequestId), {
28015
- title: input.title,
28016
- body: input.body === null ? "" : input.body,
28017
- base: input.baseBranch,
28018
- state: input.state
28019
- });
28161
+ const result = await this.#client.request(
28162
+ "PATCH",
28163
+ pullRequestPath(input, input.pullRequestId),
28164
+ {
28165
+ title: input.title,
28166
+ body: input.body === null ? "" : input.body,
28167
+ base: input.baseBranch,
28168
+ state: input.state
28169
+ },
28170
+ { actingUserId: input.actingUserId }
28171
+ );
28020
28172
  return parsePullRequest2(result);
28021
28173
  }
28022
28174
  #mergePullRequest(input) {
28023
28175
  return this.#client.request(
28024
28176
  "PUT",
28025
28177
  `${pullRequestPath(input, input.pullRequestId)}/merge`,
28026
- { commitTitle: input.commitTitle, commitMessage: input.commitMessage, method: input.method }
28178
+ { commitTitle: input.commitTitle, commitMessage: input.commitMessage, method: input.method },
28179
+ { actingUserId: input.actingUserId }
28027
28180
  );
28028
28181
  }
28029
28182
  async #listComments(input) {
@@ -28041,7 +28194,8 @@ var PlatformGithubIntegration = class {
28041
28194
  const comment = await this.#client.request(
28042
28195
  "POST",
28043
28196
  repositoryPath(input.sourceId, `issues/${requirePositiveId(input.pullRequestId, "pull request")}/comments`),
28044
- { body: input.body }
28197
+ { body: input.body },
28198
+ { actingUserId: input.actingUserId }
28045
28199
  );
28046
28200
  return parseComment(comment);
28047
28201
  }
@@ -28050,7 +28204,8 @@ var PlatformGithubIntegration = class {
28050
28204
  const comment = await this.#client.request(
28051
28205
  "PATCH",
28052
28206
  repositoryPath(input.sourceId, `issues/comments/${requirePositiveId(input.commentId, "comment")}`),
28053
- { body: input.body }
28207
+ { body: input.body },
28208
+ { actingUserId: input.actingUserId }
28054
28209
  );
28055
28210
  return parseComment(comment);
28056
28211
  }
@@ -28058,7 +28213,9 @@ var PlatformGithubIntegration = class {
28058
28213
  requireGithubConnection(input.connection);
28059
28214
  await this.#client.request(
28060
28215
  "DELETE",
28061
- repositoryPath(input.sourceId, `issues/comments/${requirePositiveId(input.commentId, "comment")}`)
28216
+ repositoryPath(input.sourceId, `issues/comments/${requirePositiveId(input.commentId, "comment")}`),
28217
+ void 0,
28218
+ { actingUserId: input.actingUserId }
28062
28219
  );
28063
28220
  }
28064
28221
  async #listReviews(input) {
@@ -28089,7 +28246,8 @@ var PlatformGithubIntegration = class {
28089
28246
  const review = await this.#client.request(
28090
28247
  "POST",
28091
28248
  `${pullRequestPath(input, input.pullRequestId)}/reviews`,
28092
- { body: input.body, commitId: input.commitId, event: input.event ? reviewEvent(input.event) : void 0 }
28249
+ { body: input.body, commitId: input.commitId, event: input.event ? reviewEvent(input.event) : void 0 },
28250
+ { actingUserId: input.actingUserId }
28093
28251
  );
28094
28252
  return parseReview(review);
28095
28253
  }
@@ -28097,7 +28255,8 @@ var PlatformGithubIntegration = class {
28097
28255
  const review = await this.#client.request(
28098
28256
  "PUT",
28099
28257
  `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}`,
28100
- { body: input.body }
28258
+ { body: input.body },
28259
+ { actingUserId: input.actingUserId }
28101
28260
  );
28102
28261
  return parseReview(review);
28103
28262
  }
@@ -28105,7 +28264,8 @@ var PlatformGithubIntegration = class {
28105
28264
  const review = await this.#client.request(
28106
28265
  "POST",
28107
28266
  `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}/events`,
28108
- { body: input.body, event: reviewEvent(input.event) }
28267
+ { body: input.body, event: reviewEvent(input.event) },
28268
+ { actingUserId: input.actingUserId }
28109
28269
  );
28110
28270
  return parseReview(review);
28111
28271
  }
@@ -28113,14 +28273,17 @@ var PlatformGithubIntegration = class {
28113
28273
  const review = await this.#client.request(
28114
28274
  "PUT",
28115
28275
  `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}/dismissals`,
28116
- { message: input.message }
28276
+ { message: input.message },
28277
+ { actingUserId: input.actingUserId }
28117
28278
  );
28118
28279
  return parseReview(review);
28119
28280
  }
28120
28281
  async #deletePendingReview(input) {
28121
28282
  await this.#client.request(
28122
28283
  "DELETE",
28123
- `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}`
28284
+ `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}`,
28285
+ void 0,
28286
+ { actingUserId: input.actingUserId }
28124
28287
  );
28125
28288
  }
28126
28289
  async #listReviewComments(input) {
@@ -28156,7 +28319,8 @@ var PlatformGithubIntegration = class {
28156
28319
  await this.#client.request(
28157
28320
  "POST",
28158
28321
  `${pullRequestPath(input, input.pullRequestId)}/comments`,
28159
- body
28322
+ body,
28323
+ { actingUserId: input.actingUserId }
28160
28324
  )
28161
28325
  );
28162
28326
  }
@@ -28166,7 +28330,8 @@ var PlatformGithubIntegration = class {
28166
28330
  await this.#client.request(
28167
28331
  "PATCH",
28168
28332
  repositoryPath(input.sourceId, `pulls/comments/${requirePositiveId(input.commentId, "review comment")}`),
28169
- { body: input.body }
28333
+ { body: input.body },
28334
+ { actingUserId: input.actingUserId }
28170
28335
  )
28171
28336
  );
28172
28337
  }
@@ -28174,7 +28339,9 @@ var PlatformGithubIntegration = class {
28174
28339
  requireGithubConnection(input.connection);
28175
28340
  await this.#client.request(
28176
28341
  "DELETE",
28177
- repositoryPath(input.sourceId, `pulls/comments/${requirePositiveId(input.commentId, "review comment")}`)
28342
+ repositoryPath(input.sourceId, `pulls/comments/${requirePositiveId(input.commentId, "review comment")}`),
28343
+ void 0,
28344
+ { actingUserId: input.actingUserId }
28178
28345
  );
28179
28346
  }
28180
28347
  #requestedReviewers(method, input) {
@@ -28182,7 +28349,8 @@ var PlatformGithubIntegration = class {
28182
28349
  return this.#client.request(
28183
28350
  method,
28184
28351
  `${pullRequestPath(input, input.pullRequestId)}/requested-reviewers`,
28185
- method === "GET" ? void 0 : { users: input.users, teams: input.teams }
28352
+ method === "GET" ? void 0 : { users: input.users, teams: input.teams },
28353
+ method === "GET" ? void 0 : { actingUserId: input.actingUserId }
28186
28354
  );
28187
28355
  }
28188
28356
  };
@@ -29076,6 +29244,158 @@ function isNotFound2(error) {
29076
29244
  return error instanceof PlatformApiError && error.status === 404;
29077
29245
  }
29078
29246
 
29247
+ // src/routes/custom-provider-source.ts
29248
+ import { setCustomProvidersSource } from "@mastra/code-sdk/agents/custom-provider-source";
29249
+
29250
+ // src/routes/provider-credentials.ts
29251
+ function getAuthProviderId(provider) {
29252
+ return provider === "openai" ? "openai-codex" : provider;
29253
+ }
29254
+ var WEB_OAUTH_FLOW_KINDS = {
29255
+ anthropic: "paste-code",
29256
+ openai: "device-code",
29257
+ "github-copilot": "device-code",
29258
+ xai: "device-code"
29259
+ };
29260
+ async function getTenantCredentialsStorage(credentials) {
29261
+ if (!credentials) return void 0;
29262
+ try {
29263
+ await credentials.ensureReady();
29264
+ } catch {
29265
+ return void 0;
29266
+ }
29267
+ return credentials;
29268
+ }
29269
+ function tenantOrgId(tenant) {
29270
+ return tenant.orgId ?? `user:${tenant.userId}`;
29271
+ }
29272
+ async function resolveCredentialContext({
29273
+ c,
29274
+ auth,
29275
+ credentials
29276
+ }) {
29277
+ await auth.ensureUser(c);
29278
+ const tenant = auth.tenant(c);
29279
+ if (!tenant) {
29280
+ if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
29281
+ return { mode: "local" };
29282
+ }
29283
+ const storage = await getTenantCredentialsStorage(credentials);
29284
+ if (!storage) {
29285
+ return {
29286
+ response: c.json(
29287
+ {
29288
+ error: "credentials_unavailable",
29289
+ message: "Tenant credential storage is unavailable \u2014 the app database is not configured or failed to start."
29290
+ },
29291
+ 503
29292
+ )
29293
+ };
29294
+ }
29295
+ return { mode: "tenant", storage, orgId: tenantOrgId(tenant), userId: tenant.userId };
29296
+ }
29297
+ async function listTenantCredentialsForRequest({
29298
+ c,
29299
+ auth,
29300
+ credentials
29301
+ }) {
29302
+ await auth.ensureUser(c);
29303
+ const tenant = auth.tenant(c);
29304
+ if (!tenant) return auth.enabled() ? [] : void 0;
29305
+ const storage = await getTenantCredentialsStorage(credentials);
29306
+ if (!storage) return [];
29307
+ return storage.listCredentials(tenantOrgId(tenant), tenant.userId);
29308
+ }
29309
+
29310
+ // src/routes/custom-provider-source.ts
29311
+ var SNAPSHOT_TTL_MS = 15e3;
29312
+ var MAX_CACHED_ORGS = 1e3;
29313
+ var LOCAL_ORG = "local";
29314
+ var OrgCustomProvidersSnapshot = class {
29315
+ #orgId;
29316
+ #storage;
29317
+ #snapshot = [];
29318
+ #fetchedAt = 0;
29319
+ #hydrating;
29320
+ constructor(orgId, storage) {
29321
+ this.#orgId = orgId;
29322
+ this.#storage = storage;
29323
+ }
29324
+ /** Hydrate the snapshot when stale; coalesces concurrent callers. */
29325
+ async ensureFresh(now = Date.now()) {
29326
+ if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;
29327
+ this.#hydrating ??= this.#hydrate().finally(() => {
29328
+ this.#hydrating = void 0;
29329
+ });
29330
+ await this.#hydrating;
29331
+ }
29332
+ async #hydrate() {
29333
+ await this.#storage.ensureReady();
29334
+ const records = await this.#storage.list({ orgId: this.#orgId });
29335
+ this.#snapshot = records.map((record) => ({
29336
+ name: record.name,
29337
+ url: record.url,
29338
+ apiKey: record.apiKey ?? void 0,
29339
+ models: record.models
29340
+ }));
29341
+ this.#fetchedAt = Date.now();
29342
+ }
29343
+ /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */
29344
+ get() {
29345
+ if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {
29346
+ void this.ensureFresh().catch(() => {
29347
+ });
29348
+ }
29349
+ return this.#snapshot;
29350
+ }
29351
+ };
29352
+ var orgSnapshots = /* @__PURE__ */ new Map();
29353
+ function snapshotFor(orgId, storage) {
29354
+ let snapshot = orgSnapshots.get(orgId);
29355
+ if (!snapshot) {
29356
+ if (orgSnapshots.size >= MAX_CACHED_ORGS) {
29357
+ const oldest = orgSnapshots.keys().next().value;
29358
+ if (oldest !== void 0) orgSnapshots.delete(oldest);
29359
+ }
29360
+ snapshot = new OrgCustomProvidersSnapshot(orgId, storage);
29361
+ orgSnapshots.set(orgId, snapshot);
29362
+ }
29363
+ return snapshot;
29364
+ }
29365
+ function orgForTenant(tenant, authEnabled) {
29366
+ if (!authEnabled) return LOCAL_ORG;
29367
+ return tenant ? tenantOrgId(tenant) : void 0;
29368
+ }
29369
+ function registerCustomProvidersSource({
29370
+ storage,
29371
+ authEnabled
29372
+ }) {
29373
+ setCustomProvidersSource((tenant) => {
29374
+ const orgId = orgForTenant(tenant, authEnabled);
29375
+ if (!orgId) return [];
29376
+ return snapshotFor(orgId, storage).get();
29377
+ });
29378
+ }
29379
+ function invalidateCustomProvidersSnapshots(tenant) {
29380
+ orgSnapshots.delete(tenant.orgId);
29381
+ }
29382
+ function createCustomProvidersPrimer({
29383
+ auth,
29384
+ storage,
29385
+ authEnabled
29386
+ }) {
29387
+ return async (c, next) => {
29388
+ const orgId = orgForTenant(auth.tenant(c), authEnabled);
29389
+ if (orgId) {
29390
+ try {
29391
+ await snapshotFor(orgId, storage).ensureFresh();
29392
+ } catch {
29393
+ }
29394
+ }
29395
+ await next();
29396
+ };
29397
+ }
29398
+
29079
29399
  // src/routes/projects.ts
29080
29400
  import { registerApiRoute as registerApiRoute6 } from "@mastra/core/server";
29081
29401
 
@@ -30503,82 +30823,10 @@ var FactoryTransitionService = class {
30503
30823
  };
30504
30824
 
30505
30825
  // src/routes/config.ts
30506
- import { removeCustomPackFromSettings } from "@mastra/code-sdk/onboarding/custom-packs";
30507
- import {
30508
- removeCustomProviderFromSettings,
30509
- upsertCustomProviderInSettings
30510
- } from "@mastra/code-sdk/onboarding/custom-providers";
30511
- import { applyOmRoleOverride, persistOmObserveAttachments } from "@mastra/code-sdk/onboarding/om-settings";
30826
+ import { DEFAULT_OM_MODEL_ID } from "@mastra/code-sdk/constants";
30512
30827
  import { getAvailableModePacks } from "@mastra/code-sdk/onboarding/packs";
30513
- import {
30514
- getCustomProviderId,
30515
- loadSettings,
30516
- saveSettings,
30517
- THREAD_ACTIVE_MODEL_PACK_ID_KEY
30518
- } from "@mastra/code-sdk/onboarding/settings";
30828
+ import { getCustomProviderId, THREAD_ACTIVE_MODEL_PACK_ID_KEY } from "@mastra/code-sdk/onboarding/settings";
30519
30829
  import { registerApiRoute as registerApiRoute7 } from "@mastra/core/server";
30520
-
30521
- // src/routes/provider-credentials.ts
30522
- function getAuthProviderId(provider) {
30523
- return provider === "openai" ? "openai-codex" : provider;
30524
- }
30525
- var WEB_OAUTH_FLOW_KINDS = {
30526
- anthropic: "paste-code",
30527
- openai: "device-code",
30528
- "github-copilot": "device-code",
30529
- xai: "device-code"
30530
- };
30531
- async function getTenantCredentialsStorage(credentials) {
30532
- if (!credentials) return void 0;
30533
- try {
30534
- await credentials.ensureReady();
30535
- } catch {
30536
- return void 0;
30537
- }
30538
- return credentials;
30539
- }
30540
- function tenantOrgId(tenant) {
30541
- return tenant.orgId ?? `user:${tenant.userId}`;
30542
- }
30543
- async function resolveCredentialContext({
30544
- c,
30545
- auth,
30546
- credentials
30547
- }) {
30548
- await auth.ensureUser(c);
30549
- const tenant = auth.tenant(c);
30550
- if (!tenant) {
30551
- if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
30552
- return { mode: "local" };
30553
- }
30554
- const storage = await getTenantCredentialsStorage(credentials);
30555
- if (!storage) {
30556
- return {
30557
- response: c.json(
30558
- {
30559
- error: "credentials_unavailable",
30560
- message: "Tenant credential storage is unavailable \u2014 the app database is not configured or failed to start."
30561
- },
30562
- 503
30563
- )
30564
- };
30565
- }
30566
- return { mode: "tenant", storage, orgId: tenantOrgId(tenant), userId: tenant.userId };
30567
- }
30568
- async function listTenantCredentialsForRequest({
30569
- c,
30570
- auth,
30571
- credentials
30572
- }) {
30573
- await auth.ensureUser(c);
30574
- const tenant = auth.tenant(c);
30575
- if (!tenant) return auth.enabled() ? [] : void 0;
30576
- const storage = await getTenantCredentialsStorage(credentials);
30577
- if (!storage) return [];
30578
- return storage.listCredentials(tenantOrgId(tenant), tenant.userId);
30579
- }
30580
-
30581
- // src/routes/config.ts
30582
30830
  function loose6(c) {
30583
30831
  return c;
30584
30832
  }
@@ -30622,15 +30870,39 @@ async function listProviders({
30622
30870
  }
30623
30871
  return Array.from(seen.values()).sort((a, b) => a.provider.localeCompare(b.provider));
30624
30872
  }
30625
- function listCustomProviders() {
30626
- const settings = loadSettings();
30627
- return settings.customProviders.map((p) => ({
30628
- id: getCustomProviderId(p.name),
30629
- name: p.name,
30630
- url: p.url,
30631
- hasApiKey: Boolean(p.apiKey),
30632
- models: p.models
30633
- }));
30873
+ function toCustomProviderInfo(record) {
30874
+ return {
30875
+ id: record.providerId,
30876
+ name: record.name,
30877
+ url: record.url,
30878
+ hasApiKey: Boolean(record.apiKey),
30879
+ models: record.models
30880
+ };
30881
+ }
30882
+ async function resolveCustomProvidersContext({
30883
+ c,
30884
+ auth,
30885
+ customProviders
30886
+ }) {
30887
+ await auth.ensureUser(c);
30888
+ const tenant = auth.tenant(c);
30889
+ if (!tenant && auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
30890
+ if (customProviders) {
30891
+ try {
30892
+ await customProviders.ensureReady();
30893
+ return tenant ? { storage: customProviders, orgId: tenantOrgId(tenant), userId: tenant.userId } : { storage: customProviders, orgId: "local", userId: "local" };
30894
+ } catch {
30895
+ }
30896
+ }
30897
+ return {
30898
+ response: c.json(
30899
+ {
30900
+ error: "custom_providers_unavailable",
30901
+ message: "Custom provider storage is unavailable \u2014 the app database is not configured or failed to start."
30902
+ },
30903
+ 503
30904
+ )
30905
+ };
30634
30906
  }
30635
30907
  function parseCustomProviderBody(body) {
30636
30908
  if (!body || typeof body !== "object") return { error: "Invalid JSON body" };
@@ -30695,15 +30967,6 @@ async function buildProviderAccess({
30695
30967
  function canUseModelProvider(access, provider) {
30696
30968
  return Boolean(access[provider]);
30697
30969
  }
30698
- async function getTenantModelPacksStorage(modelPacks) {
30699
- if (!modelPacks) return void 0;
30700
- try {
30701
- await modelPacks.ensureReady();
30702
- } catch {
30703
- return void 0;
30704
- }
30705
- return modelPacks;
30706
- }
30707
30970
  async function resolvePackContext({
30708
30971
  c,
30709
30972
  auth,
@@ -30711,23 +30974,23 @@ async function resolvePackContext({
30711
30974
  }) {
30712
30975
  await auth.ensureUser(c);
30713
30976
  const tenant = auth.tenant(c);
30714
- if (!tenant) {
30715
- if (auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
30716
- return { mode: "local" };
30717
- }
30718
- const storage = await getTenantModelPacksStorage(modelPacks);
30719
- if (!storage) {
30720
- return {
30721
- response: c.json(
30722
- {
30723
- error: "model_packs_unavailable",
30724
- message: "Model pack storage is unavailable \u2014 the app database is not configured or failed to start."
30725
- },
30726
- 503
30727
- )
30728
- };
30977
+ if (!tenant && auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
30978
+ if (modelPacks) {
30979
+ try {
30980
+ await modelPacks.ensureReady();
30981
+ return tenant ? { storage: modelPacks, orgId: tenantOrgId(tenant), userId: tenant.userId } : { storage: modelPacks, orgId: "local", userId: "local" };
30982
+ } catch {
30983
+ }
30729
30984
  }
30730
- return { mode: "tenant", storage, orgId: tenantOrgId(tenant), userId: tenant.userId };
30985
+ return {
30986
+ response: c.json(
30987
+ {
30988
+ error: "model_packs_unavailable",
30989
+ message: "Model pack storage is unavailable \u2014 the app database is not configured or failed to start."
30990
+ },
30991
+ 503
30992
+ )
30993
+ };
30731
30994
  }
30732
30995
  function recordToModePack(record) {
30733
30996
  return { id: `custom:${record.id}`, name: record.name, description: "Saved custom pack", models: record.models };
@@ -30740,7 +31003,7 @@ async function listModelPacks({
30740
31003
  activePackId
30741
31004
  }) {
30742
31005
  const access = await buildProviderAccess({ controller, authStorage, tenantCredentials });
30743
- const packs = packContext.mode === "local" ? getAvailableModePacks(access, loadSettings().customModelPacks) : [
31006
+ const packs = [
30744
31007
  ...getAvailableModePacks(access),
30745
31008
  ...(await packContext.storage.list({ orgId: packContext.orgId })).map(recordToModePack)
30746
31009
  ];
@@ -30798,20 +31061,57 @@ function readOMConfig(session) {
30798
31061
  observeAttachments: observeAttachments === true || observeAttachments === false ? observeAttachments : "auto"
30799
31062
  };
30800
31063
  }
30801
- function persistOmThreshold({ role, value }) {
30802
- const settings = loadSettings();
30803
- if (role === "observation") settings.models.omObservationThreshold = value;
30804
- else settings.models.omReflectionThreshold = value;
30805
- saveSettings(settings);
30806
- }
30807
- function persistOmRoleOverride({
30808
- role,
30809
- modelId,
30810
- otherRoleCurrentModelId
31064
+ async function resolveMemorySettingsContext({
31065
+ c,
31066
+ auth,
31067
+ memorySettings
30811
31068
  }) {
30812
- const settings = loadSettings();
30813
- applyOmRoleOverride(settings, role, modelId, otherRoleCurrentModelId);
30814
- saveSettings(settings);
31069
+ await auth.ensureUser(c);
31070
+ const tenant = auth.tenant(c);
31071
+ if (!tenant && auth.enabled()) return { response: c.json({ error: "unauthorized" }, 401) };
31072
+ if (memorySettings) {
31073
+ try {
31074
+ await memorySettings.ensureReady();
31075
+ return tenant ? { storage: memorySettings, orgId: tenantOrgId(tenant), userId: tenant.userId } : { storage: memorySettings, orgId: "local", userId: "local" };
31076
+ } catch {
31077
+ }
31078
+ }
31079
+ return {
31080
+ response: c.json(
31081
+ {
31082
+ error: "memory_settings_unavailable",
31083
+ message: "Memory settings storage is unavailable \u2014 the app database is not configured or failed to start."
31084
+ },
31085
+ 503
31086
+ )
31087
+ };
31088
+ }
31089
+ async function persistMemorySettings(context, patch, fillIfUnset) {
31090
+ await context.storage.patch({ orgId: context.orgId, userId: context.userId, patch, fillIfUnset });
31091
+ }
31092
+ async function hydrateSessionMemorySettings(session, record) {
31093
+ for (const role of ["observer", "reflector"]) {
31094
+ const stored = role === "observer" ? record?.observerModelId : record?.reflectorModelId;
31095
+ const target = stored ?? DEFAULT_OM_MODEL_ID;
31096
+ if (session.om[role].modelId() !== target) {
31097
+ await session.om[role].switchModel({ modelId: target });
31098
+ }
31099
+ }
31100
+ const state = session.state.get() ?? {};
31101
+ const updates = {};
31102
+ const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;
31103
+ if (state.observationThreshold !== observationThreshold) {
31104
+ updates.observationThreshold = observationThreshold;
31105
+ }
31106
+ const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;
31107
+ if (state.reflectionThreshold !== reflectionThreshold) {
31108
+ updates.reflectionThreshold = reflectionThreshold;
31109
+ }
31110
+ const observeAttachments = record?.observeAttachments ?? "auto";
31111
+ if ((state.observeAttachments ?? "auto") !== observeAttachments) {
31112
+ updates.observeAttachments = observeAttachments;
31113
+ }
31114
+ if (Object.keys(updates).length > 0) await session.state.set(updates);
30815
31115
  }
30816
31116
  var ConfigRoutes = class extends Route {
30817
31117
  routes() {
@@ -30819,6 +31119,8 @@ var ConfigRoutes = class extends Route {
30819
31119
  const { controller, authStorage, auth } = options;
30820
31120
  const onCredentialsChanged = options.onCredentialsChanged ?? (() => {
30821
31121
  });
31122
+ const onCustomProvidersChanged = options.onCustomProvidersChanged ?? (() => {
31123
+ });
30822
31124
  return [
30823
31125
  registerApiRoute7("/web/config/providers", {
30824
31126
  method: "GET",
@@ -30910,14 +31212,22 @@ var ConfigRoutes = class extends Route {
30910
31212
  }
30911
31213
  }),
30912
31214
  // ── Custom providers (OpenAI-compatible endpoints) ──────────────────────
30913
- // Mirrors the TUI's /custom-providers command. Backed by GlobalSettings
30914
- // (settings.json), not session state these are user-global definitions.
31215
+ // Mirrors the TUI's /custom-providers command, but backed by the
31216
+ // `custom-providers` domain (org rows in tenant mode, a sentinel `local`
31217
+ // org in no-auth mode) — the server never reads settings.json for these.
30915
31218
  registerApiRoute7("/web/config/custom-providers", {
30916
31219
  method: "GET",
30917
31220
  requiresAuth: false,
30918
- handler: (c) => {
31221
+ handler: async (c) => {
31222
+ const ctx = await resolveCustomProvidersContext({
31223
+ c: loose6(c),
31224
+ auth,
31225
+ customProviders: options.customProviders
31226
+ });
31227
+ if ("response" in ctx) return ctx.response;
30919
31228
  try {
30920
- return c.json({ providers: listCustomProviders() });
31229
+ const records = await ctx.storage.list({ orgId: ctx.orgId });
31230
+ return c.json({ providers: records.map(toCustomProviderInfo) });
30921
31231
  } catch (error) {
30922
31232
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
30923
31233
  }
@@ -30927,6 +31237,12 @@ var ConfigRoutes = class extends Route {
30927
31237
  method: "POST",
30928
31238
  requiresAuth: false,
30929
31239
  handler: async (c) => {
31240
+ const ctx = await resolveCustomProvidersContext({
31241
+ c: loose6(c),
31242
+ auth,
31243
+ customProviders: options.customProviders
31244
+ });
31245
+ if ("response" in ctx) return ctx.response;
30930
31246
  let body;
30931
31247
  try {
30932
31248
  body = await c.req.json();
@@ -30937,11 +31253,20 @@ var ConfigRoutes = class extends Route {
30937
31253
  if ("error" in parsed) return c.json({ error: parsed.error }, 400);
30938
31254
  const previousId = body && typeof body === "object" && typeof body.previousId === "string" ? body.previousId : void 0;
30939
31255
  try {
30940
- const settings = loadSettings();
30941
- upsertCustomProviderInSettings(settings, parsed, previousId);
30942
- saveSettings(settings);
30943
- const id = getCustomProviderId(parsed.name);
30944
- return c.json({ ok: true, provider: listCustomProviders().find((p) => p.id === id) });
31256
+ const record = await ctx.storage.upsert({
31257
+ orgId: ctx.orgId,
31258
+ userId: ctx.userId,
31259
+ input: {
31260
+ providerId: getCustomProviderId(parsed.name),
31261
+ name: parsed.name,
31262
+ url: parsed.url,
31263
+ apiKey: parsed.apiKey,
31264
+ models: parsed.models
31265
+ },
31266
+ previousProviderId: previousId
31267
+ });
31268
+ onCustomProvidersChanged({ orgId: ctx.orgId });
31269
+ return c.json({ ok: true, provider: toCustomProviderInfo(record) });
30945
31270
  } catch (error) {
30946
31271
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
30947
31272
  }
@@ -30950,12 +31275,17 @@ var ConfigRoutes = class extends Route {
30950
31275
  registerApiRoute7("/web/config/custom-providers/:id", {
30951
31276
  method: "DELETE",
30952
31277
  requiresAuth: false,
30953
- handler: (c) => {
31278
+ handler: async (c) => {
31279
+ const ctx = await resolveCustomProvidersContext({
31280
+ c: loose6(c),
31281
+ auth,
31282
+ customProviders: options.customProviders
31283
+ });
31284
+ if ("response" in ctx) return ctx.response;
30954
31285
  const id = c.req.param("id");
30955
31286
  try {
30956
- const settings = loadSettings();
30957
- removeCustomProviderFromSettings(settings, id);
30958
- saveSettings(settings);
31287
+ await ctx.storage.delete({ orgId: ctx.orgId, providerId: id });
31288
+ onCustomProvidersChanged({ orgId: ctx.orgId });
30959
31289
  return c.json({ ok: true });
30960
31290
  } catch (error) {
30961
31291
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -30984,8 +31314,30 @@ var ConfigRoutes = class extends Route {
30984
31314
  tenantCredentials
30985
31315
  })
30986
31316
  ]);
31317
+ 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 }));
31318
+ if (options.customProviders) {
31319
+ try {
31320
+ const ctx = await resolveCustomProvidersContext({
31321
+ c: loose6(c),
31322
+ auth,
31323
+ customProviders: options.customProviders
31324
+ });
31325
+ if (!("response" in ctx)) {
31326
+ const known = new Set(catalog.map((m) => m.id));
31327
+ for (const record of await ctx.storage.list({ orgId: ctx.orgId })) {
31328
+ for (const model of record.models) {
31329
+ const id = `${record.providerId}/${model}`;
31330
+ if (known.has(id)) continue;
31331
+ known.add(id);
31332
+ catalog.push({ id, provider: record.providerId, modelName: model, hasApiKey: true });
31333
+ }
31334
+ }
31335
+ }
31336
+ } catch {
31337
+ }
31338
+ }
30987
31339
  return c.json({
30988
- 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(
31340
+ models: catalog.sort(
30989
31341
  (a, b) => a.provider === b.provider ? a.id.localeCompare(b.id) : a.provider.localeCompare(b.provider)
30990
31342
  )
30991
31343
  });
@@ -30995,10 +31347,10 @@ var ConfigRoutes = class extends Route {
30995
31347
  }
30996
31348
  }),
30997
31349
  // ── Model packs ─────────────────────────────────────────────────────────
30998
- // Mirrors the TUI's /models-pack command. Custom-pack CRUD is DB-backed in
30999
- // tenant mode (model-packs storage domain, org-scoped) and settings.json in
31000
- // local mode; activation is session-scoped and resolves the session from
31001
- // the controller registry by resourceId.
31350
+ // Mirrors the TUI's /models-pack command. Custom-pack CRUD lives in the
31351
+ // model-packs storage domain (org-scoped, sentinel `local` org in no-auth
31352
+ // mode — never settings.json); activation is session-scoped and resolves
31353
+ // the session from the controller registry by resourceId.
31002
31354
  registerApiRoute7("/web/config/model-packs", {
31003
31355
  method: "GET",
31004
31356
  requiresAuth: false,
@@ -31052,21 +31404,12 @@ var ConfigRoutes = class extends Route {
31052
31404
  return c.json({ error: "models.build, models.plan and models.fast are required" }, 400);
31053
31405
  }
31054
31406
  try {
31055
- if (packContext.mode === "tenant") {
31056
- const record = await packContext.storage.upsert({
31057
- orgId: packContext.orgId,
31058
- userId: packContext.userId,
31059
- input: { name, models: { build, plan, fast } }
31060
- });
31061
- return c.json({ ok: true, pack: recordToModePack(record) });
31062
- }
31063
- const settings = loadSettings();
31064
- const entry = { name, models: { build, plan, fast }, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
31065
- const idx = settings.customModelPacks.findIndex((p) => p.name === name);
31066
- if (idx >= 0) settings.customModelPacks[idx] = entry;
31067
- else settings.customModelPacks.push(entry);
31068
- saveSettings(settings);
31069
- return c.json({ ok: true, pack: { id: `custom:${name}`, name, models: { build, plan, fast } } });
31407
+ const record = await packContext.storage.upsert({
31408
+ orgId: packContext.orgId,
31409
+ userId: packContext.userId,
31410
+ input: { name, models: { build, plan, fast } }
31411
+ });
31412
+ return c.json({ ok: true, pack: recordToModePack(record) });
31070
31413
  } catch (error) {
31071
31414
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
31072
31415
  }
@@ -31080,15 +31423,9 @@ var ConfigRoutes = class extends Route {
31080
31423
  if ("response" in packContext) return packContext.response;
31081
31424
  const id = decodeURIComponent(c.req.param("id"));
31082
31425
  try {
31083
- if (packContext.mode === "tenant") {
31084
- const recordId = id.startsWith("custom:") ? id.slice("custom:".length) : id;
31085
- const deleted = await packContext.storage.delete({ orgId: packContext.orgId, id: recordId });
31086
- return deleted ? c.json({ ok: true }) : c.json({ error: `Unknown pack "${id}"` }, 404);
31087
- }
31088
- const settings = loadSettings();
31089
- removeCustomPackFromSettings(settings, id);
31090
- saveSettings(settings);
31091
- return c.json({ ok: true });
31426
+ const recordId = id.startsWith("custom:") ? id.slice("custom:".length) : id;
31427
+ const deleted = await packContext.storage.delete({ orgId: packContext.orgId, id: recordId });
31428
+ return deleted ? c.json({ ok: true }) : c.json({ error: `Unknown pack "${id}"` }, 404);
31092
31429
  } catch (error) {
31093
31430
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
31094
31431
  }
@@ -31135,8 +31472,10 @@ var ConfigRoutes = class extends Route {
31135
31472
  }),
31136
31473
  // ── Observational memory ──────────────────────────────────────────────────
31137
31474
  // Mirrors the TUI's /om command. All five knobs are session-scoped (resolved
31138
- // from the session, persisted to its state + thread setting) plus written to
31139
- // GlobalSettings so the choice survives restarts and stays in sync with the TUI.
31475
+ // from the session, persisted to its state + thread setting) and durably
31476
+ // stored in the per-(org, user) `memory-settings` app table never
31477
+ // settings.json. GET hydrates the session from the stored row first so the
31478
+ // DB, not the SDK's boot-time seed, is the source of truth.
31140
31479
  registerApiRoute7("/web/config/om", {
31141
31480
  method: "GET",
31142
31481
  requiresAuth: false,
@@ -31144,9 +31483,17 @@ var ConfigRoutes = class extends Route {
31144
31483
  const resourceId = c.req.query("resourceId");
31145
31484
  const scope = c.req.query("scope") || void 0;
31146
31485
  if (!resourceId) return c.json({ error: "Missing required query param: resourceId" }, 400);
31486
+ const context = await resolveMemorySettingsContext({
31487
+ c: loose6(c),
31488
+ auth,
31489
+ memorySettings: options.memorySettings
31490
+ });
31491
+ if ("response" in context) return context.response;
31147
31492
  try {
31148
31493
  const session = await controller.getSessionByResource?.(resourceId, scope);
31149
31494
  if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
31495
+ const record = await context.storage.get({ orgId: context.orgId, userId: context.userId });
31496
+ await hydrateSessionMemorySettings(session, record);
31150
31497
  return c.json({ config: readOMConfig(session) });
31151
31498
  } catch (error) {
31152
31499
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -31172,13 +31519,24 @@ var ConfigRoutes = class extends Route {
31172
31519
  const modelId = typeof body.modelId === "string" ? body.modelId.trim() : "";
31173
31520
  if (!resourceId) return c.json({ error: "Missing required field: resourceId" }, 400);
31174
31521
  if (!modelId) return c.json({ error: "Missing required field: modelId" }, 400);
31522
+ const context = await resolveMemorySettingsContext({
31523
+ c: loose6(c),
31524
+ auth,
31525
+ memorySettings: options.memorySettings
31526
+ });
31527
+ if ("response" in context) return context.response;
31175
31528
  try {
31176
31529
  const session = await controller.getSessionByResource?.(resourceId, scope);
31177
31530
  if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
31178
31531
  const otherRole = role === "observer" ? session.om.reflector : session.om.observer;
31179
31532
  const otherRoleCurrentModelId = otherRole.modelId() ?? null;
31180
31533
  await session.om[role].switchModel({ modelId });
31181
- persistOmRoleOverride({ role, modelId, otherRoleCurrentModelId });
31534
+ const otherKey = role === "observer" ? "reflectorModelId" : "observerModelId";
31535
+ await persistMemorySettings(
31536
+ context,
31537
+ { [role === "observer" ? "observerModelId" : "reflectorModelId"]: modelId },
31538
+ otherRoleCurrentModelId ? { [otherKey]: otherRoleCurrentModelId } : void 0
31539
+ );
31182
31540
  return c.json({ ok: true, config: readOMConfig(session) });
31183
31541
  } catch (error) {
31184
31542
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -31203,19 +31561,27 @@ var ConfigRoutes = class extends Route {
31203
31561
  if (observation === void 0 && reflection === void 0) {
31204
31562
  return c.json({ error: "Provide observationThreshold and/or reflectionThreshold (positive numbers)" }, 400);
31205
31563
  }
31564
+ const context = await resolveMemorySettingsContext({
31565
+ c: loose6(c),
31566
+ auth,
31567
+ memorySettings: options.memorySettings
31568
+ });
31569
+ if ("response" in context) return context.response;
31206
31570
  try {
31207
31571
  const session = await controller.getSessionByResource?.(resourceId, scope);
31208
31572
  if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
31209
31573
  if (observation !== void 0) {
31210
31574
  await session.state.set({ observationThreshold: observation });
31211
31575
  await session.thread.setSetting({ key: "observationThreshold", value: observation });
31212
- persistOmThreshold({ role: "observation", value: observation });
31213
31576
  }
31214
31577
  if (reflection !== void 0) {
31215
31578
  await session.state.set({ reflectionThreshold: reflection });
31216
31579
  await session.thread.setSetting({ key: "reflectionThreshold", value: reflection });
31217
- persistOmThreshold({ role: "reflection", value: reflection });
31218
31580
  }
31581
+ await persistMemorySettings(context, {
31582
+ ...observation !== void 0 ? { observationThreshold: observation } : {},
31583
+ ...reflection !== void 0 ? { reflectionThreshold: reflection } : {}
31584
+ });
31219
31585
  return c.json({ ok: true, config: readOMConfig(session) });
31220
31586
  } catch (error) {
31221
31587
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -31240,12 +31606,18 @@ var ConfigRoutes = class extends Route {
31240
31606
  if (raw !== "auto" && raw !== true && raw !== false) {
31241
31607
  return c.json({ error: "value must be 'auto', true, or false" }, 400);
31242
31608
  }
31609
+ const context = await resolveMemorySettingsContext({
31610
+ c: loose6(c),
31611
+ auth,
31612
+ memorySettings: options.memorySettings
31613
+ });
31614
+ if ("response" in context) return context.response;
31243
31615
  try {
31244
31616
  const session = await controller.getSessionByResource?.(resourceId, scope);
31245
31617
  if (!session) return c.json({ error: `No session for resourceId "${resourceId}"` }, 404);
31246
31618
  await session.state.set({ observeAttachments: value });
31247
31619
  await session.thread.setSetting({ key: "observeAttachments", value });
31248
- persistOmObserveAttachments(value);
31620
+ await persistMemorySettings(context, { observeAttachments: value });
31249
31621
  return c.json({ ok: true, config: readOMConfig(session) });
31250
31622
  } catch (error) {
31251
31623
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -32463,7 +32835,7 @@ var SkillRoutes = class extends Route {
32463
32835
  // src/routes/tenant-credentials.ts
32464
32836
  import { setCredentialStoreProvider } from "@mastra/code-sdk/agents/credential-resolver";
32465
32837
  import { getOAuthProvider } from "@mastra/code-sdk/auth/storage";
32466
- var SNAPSHOT_TTL_MS = 15e3;
32838
+ var SNAPSHOT_TTL_MS2 = 15e3;
32467
32839
  var MAX_CACHED_TENANTS = 1e3;
32468
32840
  var TenantCredentialStore = class {
32469
32841
  allowEnvironmentFallback = false;
@@ -32480,7 +32852,7 @@ var TenantCredentialStore = class {
32480
32852
  }
32481
32853
  /** Hydrate the snapshot when stale; coalesces concurrent callers. */
32482
32854
  async ensureFresh(now = Date.now()) {
32483
- if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;
32855
+ if (now - this.#fetchedAt < SNAPSHOT_TTL_MS2) return;
32484
32856
  this.#hydrating ??= this.#hydrate().finally(() => {
32485
32857
  this.#hydrating = void 0;
32486
32858
  });
@@ -32502,7 +32874,7 @@ var TenantCredentialStore = class {
32502
32874
  }
32503
32875
  /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */
32504
32876
  reload() {
32505
- if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {
32877
+ if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS2) {
32506
32878
  void this.ensureFresh().catch(() => {
32507
32879
  });
32508
32880
  }
@@ -34886,7 +35258,10 @@ function assembleFactoryApiRoutes(deps) {
34886
35258
  authStorage: deps.authStorage,
34887
35259
  modelCredentials: deps.domains.modelCredentials,
34888
35260
  modelPacks: deps.domains.modelPacks,
34889
- onCredentialsChanged: invalidateTenantCredentialSnapshots
35261
+ memorySettings: deps.domains.memorySettings,
35262
+ customProviders: deps.domains.customProviders,
35263
+ onCredentialsChanged: invalidateTenantCredentialSnapshots,
35264
+ onCustomProvidersChanged: invalidateCustomProvidersSnapshots
34890
35265
  }).routes(),
34891
35266
  ...new OAuthRoutes({
34892
35267
  auth: deps.auth,
@@ -36051,10 +36426,13 @@ var MIME = {
36051
36426
  };
36052
36427
  var SERVER_PREFIXES = ["/api", "/web", "/auth"];
36053
36428
  function resolveUiDistDir() {
36429
+ const projectRoot = process.env.MASTRA_PROJECT_ROOT?.trim();
36054
36430
  const candidates = [
36055
36431
  process.env.MASTRACODE_UI_DIST,
36056
- join2(dirname(fileURLToPath(import.meta.url)), "ui"),
36057
- resolve2(process.cwd(), "src/mastra/public/ui")
36432
+ resolve2(process.cwd(), "factory"),
36433
+ join2(dirname(fileURLToPath(import.meta.url)), "factory"),
36434
+ projectRoot && resolve2(projectRoot, "src/mastra/public/factory"),
36435
+ resolve2(process.cwd(), "src/mastra/public/factory")
36058
36436
  ];
36059
36437
  for (const candidate of candidates) {
36060
36438
  if (candidate && existsSync(join2(candidate, "index.html"))) return resolve2(candidate);
@@ -36480,8 +36858,154 @@ var AuditDomain = class {
36480
36858
  }
36481
36859
  };
36482
36860
 
36483
- // src/storage/domains/intake/base.ts
36861
+ // src/storage/domains/custom-providers/base.ts
36484
36862
  import { FactoryStorageDomain as FactoryStorageDomain5, UniqueViolationError as UniqueViolationError5 } from "@mastra/core/storage";
36863
+ var CUSTOM_PROVIDERS_SCHEMA = {
36864
+ name: "custom_providers",
36865
+ columns: {
36866
+ id: { type: "uuid-pk" },
36867
+ org_id: { type: "text" },
36868
+ created_by: { type: "text" },
36869
+ provider_id: { type: "text" },
36870
+ name: { type: "text" },
36871
+ url: { type: "text" },
36872
+ api_key: { type: "text", nullable: true },
36873
+ models: { type: "json" },
36874
+ created_at: { type: "timestamp" },
36875
+ updated_at: { type: "timestamp" }
36876
+ },
36877
+ uniqueIndexes: [{ name: "custom_providers_org_provider_key", columns: ["org_id", "provider_id"] }]
36878
+ };
36879
+ function toRecord(row) {
36880
+ return {
36881
+ id: row.id,
36882
+ orgId: row.org_id,
36883
+ createdBy: row.created_by,
36884
+ providerId: row.provider_id,
36885
+ name: row.name,
36886
+ url: row.url,
36887
+ apiKey: row.api_key,
36888
+ models: Array.isArray(row.models) ? row.models : [],
36889
+ createdAt: row.created_at,
36890
+ updatedAt: row.updated_at
36891
+ };
36892
+ }
36893
+ var CustomProvidersStorage = class extends FactoryStorageDomain5 {
36894
+ constructor() {
36895
+ super("custom-providers");
36896
+ }
36897
+ async init() {
36898
+ await this.ensureCollections([CUSTOM_PROVIDERS_SCHEMA]);
36899
+ }
36900
+ async dangerouslyClearAll() {
36901
+ await this.ops.deleteMany("custom_providers", {});
36902
+ }
36903
+ get #db() {
36904
+ return this.ops;
36905
+ }
36906
+ /**
36907
+ * Create or wholesale-replace a provider by `(orgId, providerId)` — mirrors
36908
+ * the settings.json upsert semantics (no key retention: an absent `apiKey`
36909
+ * clears the stored key). `previousProviderId` renames the provider: the
36910
+ * common path is a single atomic in-place update of the old row; renaming
36911
+ * onto an id that already exists overwrites the target first and removes the
36912
+ * old row last, so no interleaving or failure can lose the provider.
36913
+ */
36914
+ async upsert({
36915
+ orgId,
36916
+ userId,
36917
+ input,
36918
+ previousProviderId
36919
+ }) {
36920
+ const now = /* @__PURE__ */ new Date();
36921
+ const renameFrom = previousProviderId && previousProviderId !== input.providerId ? previousProviderId : void 0;
36922
+ if (renameFrom) {
36923
+ const target = await this.#db.findOne("custom_providers", {
36924
+ org_id: orgId,
36925
+ provider_id: input.providerId
36926
+ });
36927
+ if (!target) {
36928
+ const renamed = await this.#db.updateAtomic(
36929
+ "custom_providers",
36930
+ { org_id: orgId, provider_id: renameFrom },
36931
+ () => ({
36932
+ provider_id: input.providerId,
36933
+ name: input.name,
36934
+ url: input.url,
36935
+ api_key: input.apiKey ?? null,
36936
+ models: input.models,
36937
+ updated_at: now
36938
+ })
36939
+ );
36940
+ if (renamed) return toRecord(renamed);
36941
+ }
36942
+ }
36943
+ const record = await this.#write({ orgId, userId, input, now });
36944
+ if (renameFrom) {
36945
+ await this.#db.deleteMany("custom_providers", { org_id: orgId, provider_id: renameFrom });
36946
+ }
36947
+ return record;
36948
+ }
36949
+ /**
36950
+ * Update-first, then insert-and-catch-unique-violation (see `queue_health`):
36951
+ * concurrent creates of the same provider both succeed (last write wins on
36952
+ * the single row) instead of one failing on the unique index, and a row
36953
+ * deleted mid-flight is recreated rather than reported as a stale success.
36954
+ */
36955
+ async #write({
36956
+ orgId,
36957
+ userId,
36958
+ input,
36959
+ now
36960
+ }) {
36961
+ const updateExisting = () => this.#db.updateAtomic(
36962
+ "custom_providers",
36963
+ { org_id: orgId, provider_id: input.providerId },
36964
+ () => ({
36965
+ name: input.name,
36966
+ url: input.url,
36967
+ api_key: input.apiKey ?? null,
36968
+ models: input.models,
36969
+ updated_at: now
36970
+ })
36971
+ );
36972
+ const updated = await updateExisting();
36973
+ if (updated) return toRecord(updated);
36974
+ try {
36975
+ const row = await this.#db.insertOne("custom_providers", {
36976
+ org_id: orgId,
36977
+ created_by: userId,
36978
+ provider_id: input.providerId,
36979
+ name: input.name,
36980
+ url: input.url,
36981
+ api_key: input.apiKey ?? null,
36982
+ models: input.models,
36983
+ created_at: now,
36984
+ updated_at: now
36985
+ });
36986
+ return toRecord(row);
36987
+ } catch (error) {
36988
+ if (!(error instanceof UniqueViolationError5)) throw error;
36989
+ const row = await updateExisting();
36990
+ if (!row) throw error;
36991
+ return toRecord(row);
36992
+ }
36993
+ }
36994
+ async list({ orgId }) {
36995
+ const rows = await this.#db.findMany(
36996
+ "custom_providers",
36997
+ { org_id: orgId },
36998
+ { orderBy: [["name", "asc"]] }
36999
+ );
37000
+ return rows.map(toRecord);
37001
+ }
37002
+ async delete({ orgId, providerId }) {
37003
+ return await this.#db.deleteMany("custom_providers", { org_id: orgId, provider_id: providerId }) > 0;
37004
+ }
37005
+ };
37006
+
37007
+ // src/storage/domains/intake/base.ts
37008
+ import { FactoryStorageDomain as FactoryStorageDomain6, UniqueViolationError as UniqueViolationError6 } from "@mastra/core/storage";
36485
37009
  var DEFAULT_INTAKE_CONFIG = {};
36486
37010
  var INTAKE_SETTINGS_SCHEMA = {
36487
37011
  name: "intake_settings",
@@ -36495,7 +37019,7 @@ var INTAKE_SETTINGS_SCHEMA = {
36495
37019
  },
36496
37020
  uniqueIndexes: [{ name: "intake_settings_org_user_unique", columns: ["org_id", "user_id"] }]
36497
37021
  };
36498
- var IntakeStorage = class extends FactoryStorageDomain5 {
37022
+ var IntakeStorage = class extends FactoryStorageDomain6 {
36499
37023
  constructor() {
36500
37024
  super("intake");
36501
37025
  }
@@ -36531,14 +37055,14 @@ var IntakeStorage = class extends FactoryStorageDomain5 {
36531
37055
  try {
36532
37056
  await this.#db.insertOne("intake_settings", { ...where, config, created_at: now, updated_at: now });
36533
37057
  } catch (error) {
36534
- if (!(error instanceof UniqueViolationError5)) throw error;
37058
+ if (!(error instanceof UniqueViolationError6)) throw error;
36535
37059
  await this.#db.updateMany("intake_settings", where, { config, updated_at: now });
36536
37060
  }
36537
37061
  }
36538
37062
  };
36539
37063
 
36540
37064
  // src/storage/domains/integrations/base.ts
36541
- import { FactoryStorageDomain as FactoryStorageDomain6, UniqueViolationError as UniqueViolationError6 } from "@mastra/core/storage";
37065
+ import { FactoryStorageDomain as FactoryStorageDomain7, UniqueViolationError as UniqueViolationError7 } from "@mastra/core/storage";
36542
37066
  var INTEGRATION_CONNECTIONS_SCHEMA = {
36543
37067
  name: "integration_connections",
36544
37068
  columns: {
@@ -36591,7 +37115,7 @@ var INTEGRATION_SETTINGS_SCHEMA = {
36591
37115
  { name: "integration_settings_integration_org_user_unique", columns: ["integration_id", "org_id", "user_id"] }
36592
37116
  ]
36593
37117
  };
36594
- var IntegrationStorage = class extends FactoryStorageDomain6 {
37118
+ var IntegrationStorage = class extends FactoryStorageDomain7 {
36595
37119
  constructor() {
36596
37120
  super("integrations");
36597
37121
  }
@@ -36667,7 +37191,7 @@ var IntegrationStorage = class extends FactoryStorageDomain6 {
36667
37191
  });
36668
37192
  return;
36669
37193
  } catch (error) {
36670
- if (!(error instanceof UniqueViolationError6)) throw error;
37194
+ if (!(error instanceof UniqueViolationError7)) throw error;
36671
37195
  lastError = error;
36672
37196
  }
36673
37197
  }
@@ -36772,7 +37296,7 @@ var IntegrationStorage = class extends FactoryStorageDomain6 {
36772
37296
  });
36773
37297
  return;
36774
37298
  } catch (error) {
36775
- if (!(error instanceof UniqueViolationError6)) throw error;
37299
+ if (!(error instanceof UniqueViolationError7)) throw error;
36776
37300
  lastError = error;
36777
37301
  }
36778
37302
  }
@@ -36783,8 +37307,115 @@ var IntegrationStorage = class extends FactoryStorageDomain6 {
36783
37307
  }
36784
37308
  };
36785
37309
 
37310
+ // src/storage/domains/memory-settings/base.ts
37311
+ import { FactoryStorageDomain as FactoryStorageDomain8, UniqueViolationError as UniqueViolationError8 } from "@mastra/core/storage";
37312
+ var MEMORY_SETTINGS_SCHEMA = {
37313
+ name: "memory_settings",
37314
+ columns: {
37315
+ id: { type: "uuid-pk" },
37316
+ org_id: { type: "text" },
37317
+ user_id: { type: "text" },
37318
+ observer_model_id: { type: "text", nullable: true },
37319
+ reflector_model_id: { type: "text", nullable: true },
37320
+ observation_threshold: { type: "integer", nullable: true },
37321
+ reflection_threshold: { type: "integer", nullable: true },
37322
+ // 'auto' | true | false — json keeps the tri-state without string encoding.
37323
+ observe_attachments: { type: "json", nullable: true },
37324
+ created_at: { type: "timestamp" },
37325
+ updated_at: { type: "timestamp" }
37326
+ },
37327
+ uniqueIndexes: [{ name: "memory_settings_org_user_key", columns: ["org_id", "user_id"] }]
37328
+ };
37329
+ function toRecord2(row) {
37330
+ return {
37331
+ orgId: row.org_id,
37332
+ userId: row.user_id,
37333
+ observerModelId: row.observer_model_id,
37334
+ reflectorModelId: row.reflector_model_id,
37335
+ observationThreshold: row.observation_threshold,
37336
+ reflectionThreshold: row.reflection_threshold,
37337
+ observeAttachments: row.observe_attachments,
37338
+ createdAt: row.created_at,
37339
+ updatedAt: row.updated_at
37340
+ };
37341
+ }
37342
+ function patchToColumns(patch) {
37343
+ const columns = {};
37344
+ if (patch.observerModelId !== void 0) columns.observer_model_id = patch.observerModelId;
37345
+ if (patch.reflectorModelId !== void 0) columns.reflector_model_id = patch.reflectorModelId;
37346
+ if (patch.observationThreshold !== void 0) columns.observation_threshold = patch.observationThreshold;
37347
+ if (patch.reflectionThreshold !== void 0) columns.reflection_threshold = patch.reflectionThreshold;
37348
+ if (patch.observeAttachments !== void 0) columns.observe_attachments = patch.observeAttachments;
37349
+ return columns;
37350
+ }
37351
+ var MemorySettingsStorage = class extends FactoryStorageDomain8 {
37352
+ constructor() {
37353
+ super("memory-settings");
37354
+ }
37355
+ async init() {
37356
+ await this.ensureCollections([MEMORY_SETTINGS_SCHEMA]);
37357
+ }
37358
+ async dangerouslyClearAll() {
37359
+ await this.ops.deleteMany("memory_settings", {});
37360
+ }
37361
+ get #db() {
37362
+ return this.ops;
37363
+ }
37364
+ async get({ orgId, userId }) {
37365
+ const row = await this.#db.findOne("memory_settings", { org_id: orgId, user_id: userId });
37366
+ return row ? toRecord2(row) : null;
37367
+ }
37368
+ /**
37369
+ * Upsert the user's row, writing only the knobs present in `patch`.
37370
+ * `fillIfUnset` knobs are written only where the stored value is still
37371
+ * `NULL`, decided inside the atomic update so concurrent explicit writes
37372
+ * win over fills. Concurrent first writes are resolved via the shared
37373
+ * insert-then-catch-unique-violation pattern (see `queue_health`).
37374
+ */
37375
+ async patch({
37376
+ orgId,
37377
+ userId,
37378
+ patch,
37379
+ fillIfUnset
37380
+ }) {
37381
+ const now = /* @__PURE__ */ new Date();
37382
+ const updateExisting = () => this.#db.updateAtomic("memory_settings", { org_id: orgId, user_id: userId }, (row) => {
37383
+ const columns = { ...patchToColumns(patch), updated_at: now };
37384
+ if (fillIfUnset?.observerModelId !== void 0 && row.observer_model_id == null) {
37385
+ columns.observer_model_id = patch.observerModelId ?? fillIfUnset.observerModelId;
37386
+ }
37387
+ if (fillIfUnset?.reflectorModelId !== void 0 && row.reflector_model_id == null) {
37388
+ columns.reflector_model_id = patch.reflectorModelId ?? fillIfUnset.reflectorModelId;
37389
+ }
37390
+ return columns;
37391
+ });
37392
+ const updated = await updateExisting();
37393
+ if (updated) return toRecord2(updated);
37394
+ try {
37395
+ const row = await this.#db.insertOne("memory_settings", {
37396
+ org_id: orgId,
37397
+ user_id: userId,
37398
+ observer_model_id: fillIfUnset?.observerModelId ?? null,
37399
+ reflector_model_id: fillIfUnset?.reflectorModelId ?? null,
37400
+ observation_threshold: null,
37401
+ reflection_threshold: null,
37402
+ observe_attachments: null,
37403
+ ...patchToColumns(patch),
37404
+ created_at: now,
37405
+ updated_at: now
37406
+ });
37407
+ return toRecord2(row);
37408
+ } catch (error) {
37409
+ if (!(error instanceof UniqueViolationError8)) throw error;
37410
+ const row = await updateExisting();
37411
+ if (!row) throw error;
37412
+ return toRecord2(row);
37413
+ }
37414
+ }
37415
+ };
37416
+
36786
37417
  // src/storage/domains/model-packs/base.ts
36787
- import { FactoryStorageDomain as FactoryStorageDomain7 } from "@mastra/core/storage";
37418
+ import { FactoryStorageDomain as FactoryStorageDomain9 } from "@mastra/core/storage";
36788
37419
  var MODEL_PACKS_SCHEMA = {
36789
37420
  name: "model_packs",
36790
37421
  columns: {
@@ -36811,7 +37442,7 @@ function toModelPack(row) {
36811
37442
  updatedAt: row.updated_at
36812
37443
  };
36813
37444
  }
36814
- var ModelPacksStorage = class extends FactoryStorageDomain7 {
37445
+ var ModelPacksStorage = class extends FactoryStorageDomain9 {
36815
37446
  constructor() {
36816
37447
  super("model-packs");
36817
37448
  }
@@ -36875,7 +37506,7 @@ var ModelPacksStorage = class extends FactoryStorageDomain7 {
36875
37506
  };
36876
37507
 
36877
37508
  // src/storage/domains/projects/base.ts
36878
- import { FactoryStorageDomain as FactoryStorageDomain8 } from "@mastra/core/storage";
37509
+ import { FactoryStorageDomain as FactoryStorageDomain10 } from "@mastra/core/storage";
36879
37510
  var FACTORY_PROJECTS_SCHEMA = {
36880
37511
  name: "factory_projects",
36881
37512
  columns: {
@@ -36902,7 +37533,7 @@ function toFactoryProject(row) {
36902
37533
  updatedAt: row.updated_at
36903
37534
  };
36904
37535
  }
36905
- var FactoryProjectsStorage = class extends FactoryStorageDomain8 {
37536
+ var FactoryProjectsStorage = class extends FactoryStorageDomain10 {
36906
37537
  constructor() {
36907
37538
  super("projects");
36908
37539
  }
@@ -36970,7 +37601,7 @@ var FactoryProjectsStorage = class extends FactoryStorageDomain8 {
36970
37601
  };
36971
37602
 
36972
37603
  // src/storage/domains/source-control/base.ts
36973
- import { FactoryStorageDomain as FactoryStorageDomain9, UniqueViolationError as UniqueViolationError7 } from "@mastra/core/storage";
37604
+ import { FactoryStorageDomain as FactoryStorageDomain11, UniqueViolationError as UniqueViolationError9 } from "@mastra/core/storage";
36974
37605
  var FACTORY_PROJECTS = "factory_projects";
36975
37606
  var INSTALLATIONS = "source_control_installations";
36976
37607
  var REPOSITORIES = "source_control_repositories";
@@ -37223,7 +37854,7 @@ function toSession(row) {
37223
37854
  updatedAt: row.updated_at
37224
37855
  };
37225
37856
  }
37226
- var SourceControlStorage = class extends FactoryStorageDomain9 {
37857
+ var SourceControlStorage = class extends FactoryStorageDomain11 {
37227
37858
  constructor() {
37228
37859
  super("source-control");
37229
37860
  }
@@ -37417,7 +38048,7 @@ var SourceControlStorage = class extends FactoryStorageDomain9 {
37417
38048
  });
37418
38049
  return toConnection(row);
37419
38050
  } catch (error) {
37420
- if (!(error instanceof UniqueViolationError7)) throw error;
38051
+ if (!(error instanceof UniqueViolationError9)) throw error;
37421
38052
  const row = await db().findOne(CONNECTIONS, {
37422
38053
  factory_project_id: input.factoryProjectId,
37423
38054
  integration_id: integrationId,
@@ -37546,7 +38177,7 @@ var SourceControlStorage = class extends FactoryStorageDomain9 {
37546
38177
  });
37547
38178
  return toSandbox(row);
37548
38179
  } catch (error) {
37549
- if (!(error instanceof UniqueViolationError7)) throw error;
38180
+ if (!(error instanceof UniqueViolationError9)) throw error;
37550
38181
  const row = await db().findOne(SANDBOXES, where);
37551
38182
  if (!row) throw error;
37552
38183
  return toSandbox(row);
@@ -37659,7 +38290,7 @@ var SourceControlStorage = class extends FactoryStorageDomain9 {
37659
38290
  });
37660
38291
  return toSession(row);
37661
38292
  } catch (error) {
37662
- if (!(error instanceof UniqueViolationError7)) throw error;
38293
+ if (!(error instanceof UniqueViolationError9)) throw error;
37663
38294
  const row = await db().findOne(SESSIONS, {
37664
38295
  project_repository_id: input.projectRepositoryId,
37665
38296
  user_id: input.userId,
@@ -37762,6 +38393,7 @@ var factorySkillExtension = {
37762
38393
  function createWorkspaceFactory(options = {}) {
37763
38394
  const { sandbox: sandboxConfig, github, fleet } = options;
37764
38395
  const isLocalSandbox = sandboxConfig?.machine instanceof LocalSandbox;
38396
+ const githubTokenInjectors = /* @__PURE__ */ new Map();
37765
38397
  return async ({ requestContext, mastra, skillExtension }) => {
37766
38398
  const effectiveSkillExtension = skillExtension ?? factorySkillExtension;
37767
38399
  const ctx = requestContext.get("controller");
@@ -37813,6 +38445,8 @@ function createWorkspaceFactory(options = {}) {
37813
38445
  const existing = mastra?.getWorkspaceById(workspaceId);
37814
38446
  if (existing) {
37815
38447
  existing.setToolsConfig(MASTRACODE_WORKSPACE_TOOLS);
38448
+ const injectGithubToken3 = githubTokenInjectors.get(workspaceId);
38449
+ if (injectGithubToken3) registerGithubTokenInjector(requestContext, injectGithubToken3);
37816
38450
  return existing;
37817
38451
  }
37818
38452
  } catch {
@@ -37843,6 +38477,14 @@ function createWorkspaceFactory(options = {}) {
37843
38477
  repoFullName
37844
38478
  });
37845
38479
  if (projectRepository.setupCommand) await runWorktreeSetup(sandbox, workdir, projectRepository.setupCommand);
38480
+ const injectGithubToken2 = (freshToken) => {
38481
+ if (!sandbox.setEnvironmentVariable) {
38482
+ throw new Error("The active sandbox provider does not support runtime GitHub token refresh.");
38483
+ }
38484
+ sandbox.setEnvironmentVariable("GH_TOKEN", freshToken);
38485
+ };
38486
+ githubTokenInjectors.set(workspaceId, injectGithubToken2);
38487
+ registerGithubTokenInjector(requestContext, injectGithubToken2);
37846
38488
  const filesystem = new SandboxFilesystem({ sandbox, workdir });
37847
38489
  const projectSkillPaths = [path2.join(configDir, "skills"), ".claude/skills", ".agents/skills"];
37848
38490
  const skillPaths = [...effectiveSkillExtension?.paths ?? [], ...projectSkillPaths];
@@ -37950,6 +38592,8 @@ var MastraFactory = class {
37950
38592
  const workItemsStorage = storage.registerDomain(new WorkItemsStorage());
37951
38593
  const modelCredentialsStorage = storage.registerDomain(new ModelCredentialsStorage());
37952
38594
  const modelPacksStorage = storage.registerDomain(new ModelPacksStorage());
38595
+ const memorySettingsStorage = storage.registerDomain(new MemorySettingsStorage());
38596
+ const customProvidersStorage = storage.registerDomain(new CustomProvidersStorage());
37953
38597
  const queueHealthStorage = storage.registerDomain(new QueueHealthStorage());
37954
38598
  const integrationStorage = storage.registerDomain(new IntegrationStorage());
37955
38599
  const factoryProjectsStorage = storage.registerDomain(new FactoryProjectsStorage());
@@ -37958,6 +38602,8 @@ var MastraFactory = class {
37958
38602
  intake: intakeStorage,
37959
38603
  modelCredentials: modelCredentialsStorage,
37960
38604
  modelPacks: modelPacksStorage,
38605
+ memorySettings: memorySettingsStorage,
38606
+ customProviders: customProvidersStorage,
37961
38607
  projects: factoryProjectsStorage,
37962
38608
  queueHealth: queueHealthStorage,
37963
38609
  workItems: workItemsStorage
@@ -38006,6 +38652,7 @@ var MastraFactory = class {
38006
38652
  if (auth) {
38007
38653
  registerTenantCredentialResolver(modelCredentialsStorage);
38008
38654
  }
38655
+ registerCustomProvidersSource({ storage: customProvidersStorage, authEnabled: Boolean(auth) });
38009
38656
  for (const integration of integrations) {
38010
38657
  integration.initialize?.({
38011
38658
  storage: integrationStorage.forIntegration(integration.id),
@@ -38059,6 +38706,13 @@ var MastraFactory = class {
38059
38706
  );
38060
38707
  }
38061
38708
  }
38709
+ const mastraStorageBackend = (() => {
38710
+ for (let proto = Object.getPrototypeOf(storage); proto; proto = Object.getPrototypeOf(proto)) {
38711
+ if (proto.constructor?.name === "PgFactoryStorage") return "pg";
38712
+ if (proto.constructor?.name === "LibSQLFactoryStorage") return "libsql";
38713
+ }
38714
+ return void 0;
38715
+ })();
38062
38716
  const toolIntegrations = integrationRegistrations.filter(
38063
38717
  ({ integration }) => integration.agentTools || integration.sessionTools
38064
38718
  );
@@ -38070,7 +38724,11 @@ var MastraFactory = class {
38070
38724
  fleet
38071
38725
  }),
38072
38726
  disableGithubSignals: true,
38727
+ // Memory settings live in the factory's `memory-settings` app table (per
38728
+ // org/user), so the host machine's TUI settings.json must not seed them.
38729
+ disableSettingsOmSeed: true,
38073
38730
  storage: storage.getMastraStorage(),
38731
+ ...mastraStorageBackend ? { storageBackend: mastraStorageBackend } : {},
38074
38732
  ...factoryProcessor ? { inputProcessors: [factoryProcessor] } : {},
38075
38733
  ...vector ? { vector } : {},
38076
38734
  ...toolIntegrations.length > 0 || workItemsStorage && transitionService ? {
@@ -38178,13 +38836,25 @@ var MastraFactory = class {
38178
38836
  const uiDist = resolveUiDistDir();
38179
38837
  const spa = uiDist ? [createSpaStaticMiddleware(uiDist)] : [];
38180
38838
  if (!auth) {
38181
- return { ...spa.length ? { middleware: spa } : {}, ...cors, ...onError };
38839
+ return {
38840
+ middleware: [
38841
+ createCustomProvidersPrimer({ auth: routeAuth, storage: customProvidersStorage, authEnabled: false }),
38842
+ ...spa
38843
+ ],
38844
+ ...cors,
38845
+ ...onError
38846
+ };
38182
38847
  }
38183
38848
  return {
38184
38849
  auth,
38185
38850
  middleware: [
38186
38851
  createFactoryAuthGate(auth),
38187
38852
  createTenantCredentialPrimer({ auth: routeAuth, credentials: modelCredentialsStorage }),
38853
+ createCustomProvidersPrimer({
38854
+ auth: routeAuth,
38855
+ storage: customProvidersStorage,
38856
+ authEnabled: Boolean(auth)
38857
+ }),
38188
38858
  ...spa
38189
38859
  ],
38190
38860
  ...cors,