@mastra/factory 0.17.0 → 0.17.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/integrations/base.d.ts +7 -0
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +1 -0
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +29 -5
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +85 -11
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/routes/surface.d.ts +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +2 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/session/memory-settings-hydration.d.ts +22 -0
- package/dist/session/memory-settings-hydration.d.ts.map +1 -1
- package/dist/session/memory-settings-hydration.js +42 -1
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/storage/domains/comments/routes.d.ts.map +1 -1
- package/dist/storage/domains/comments/routes.js +14 -6
- package/dist/storage/domains/comments/routes.js.map +1 -1
- package/package.json +6 -6
|
@@ -37,6 +37,7 @@ import type { WorkItemFeedPublisher } from '../storage/domains/comments/feed-syn
|
|
|
37
37
|
import type { IntakeStorage } from '../storage/domains/intake/base.js';
|
|
38
38
|
import type { IntegrationStorageHandle } from '../storage/domains/integrations/base.js';
|
|
39
39
|
import type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';
|
|
40
|
+
import type { ModelPacksStorage } from '../storage/domains/model-packs/base.js';
|
|
40
41
|
import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
|
|
41
42
|
import type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
|
|
42
43
|
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
@@ -113,6 +114,12 @@ export interface IntegrationContext {
|
|
|
113
114
|
* adopts the same memory configuration the web kickoff applies.
|
|
114
115
|
*/
|
|
115
116
|
memorySettings: MemorySettingsStorage;
|
|
117
|
+
/**
|
|
118
|
+
* Saved model packs and their per-user active selection. A channel
|
|
119
|
+
* integration reads the linked sender's active pack to start a session on
|
|
120
|
+
* the model that user chose rather than the factory's shared default.
|
|
121
|
+
*/
|
|
122
|
+
modelPacks: ModelPacksStorage;
|
|
116
123
|
/** Cross-integration intake selection (which sources are synced). */
|
|
117
124
|
intake: IntakeStorage;
|
|
118
125
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/integrations/base.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/integrations/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMhG;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,yBAAyB,CAAC;CAC5C;AAyBD,qBAAa,gBAAiB,YAAW,kBAAkB;;IACzD,QAAQ,CAAC,EAAE,WAAW;IACtB;;;;OAIG;IACH,QAAQ,CAAC,yBAAyB,QAAQ;gBAU9B,MAAM,EAAE,sBAAsB;IAS1C,QAAQ,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMhG;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,yBAAyB,CAAC;CAC5C;AAyBD,qBAAa,gBAAiB,YAAW,kBAAkB;;IACzD,QAAQ,CAAC,EAAE,WAAW;IACtB;;;;OAIG;IACH,QAAQ,CAAC,yBAAyB,QAAQ;gBAU9B,MAAM,EAAE,sBAAsB;IAS1C,QAAQ,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IA4BxD,aAAa,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IAI7D,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAc3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASvC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","names":["#config","#repoBackedSessions"],"sources":["../../../src/integrations/slack/integration.ts"],"sourcesContent":["/**\n * `SlackIntegration` — Slack as a `FactoryIntegration`.\n *\n * Slack contributes two things to a factory: the chat channels that carry\n * inbound messages into agent runs (`channels()`), and the browser-facing\n * account-link routes that bind a Slack sender to a Mastra tenant (`routes()`).\n * Both used to be assembled by hand in the deploy entry, which had to reach\n * into the prepared agent controller by string key, resolve storage domains\n * itself, mint its own state signer, and splice routes onto the factory's\n * assembled server config. Implementing the integration interface instead means\n * the factory does all of that the same way it already does for GitHub and\n * Linear, and the entry's job shrinks to reading Slack's env vars once.\n *\n * Slack ships as a factory built-in alongside GitHub and Linear (it originally\n * lived in `mastracode/web` to keep `@mastra/slack`/`chat` out of this package;\n * the team reversed that so `create-factory` consumers get Slack channels out\n * of the box). The integration interface remains open — third parties still add\n * capabilities by implementing `FactoryIntegration` from outside the package.\n */\n\nimport type { TypingStatusFn } from '@mastra/core/channels';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\n\nimport type { WorkItemFeedPublisher } from '../../storage/domains/comments/feed-sync.js';\nimport type { FactoryChannelsConfig, FactoryIntegration, IntegrationContext } from '../base.js';\n\nimport { createSlackConnectRoutes } from './connect-route.js';\nimport { SlackFeedPublisher } from './feed-publisher.js';\nimport { createSlackChannelsConfig } from './slack.js';\n\n/**\n * Slack app credentials, read from env ONCE by the deploy entry. `signingSecret`\n * is required because the Slack adapter validates it at construction — an\n * integration constructed without it would throw during `prepare()` rather than\n * reporting itself unconfigured.\n */\nexport interface SlackIntegrationConfig {\n /** Verifies inbound Slack request signatures. Required. */\n signingSecret: string;\n /** Bot token used to post replies and ephemeral cards. */\n botToken?: string;\n /**\n * OAuth client credentials. Required for account linking: \"Sign in with\n * Slack\" (OIDC) is the only flow that writes a link, so without these the\n * settings surface reports `canConnect: false` and no sender can link.\n */\n clientId?: string;\n clientSecret?: string;\n /**\n * HTTPS origin Slack redirects back to. Slack requires HTTPS, so locally this\n * is the tunnel origin rather than the app's own public URL.\n */\n oidcRedirectBaseUrl?: string;\n /** SPA origin the post-connect redirect returns to. */\n uiOrigin?: string;\n /**\n * Overrides for the Slack channel adapter entry.\n *\n * @example\n * ```ts\n * new SlackIntegration({\n * signingSecret,\n * adapterOptions: {\n * streaming: true,\n * toolDisplay: 'grouped',\n * },\n * });\n * ```\n */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nconst factoryTypingStatus: TypingStatusFn = chunk => {\n switch (chunk.type) {\n case 'reasoning-delta':\n return 'is thinking...';\n case 'text-delta':\n return 'is typing...';\n case 'tool-call':\n return 'is working...';\n default:\n return undefined;\n }\n};\n\n/**\n * Drops keys the caller set to `undefined` so spreading the overrides cannot\n * erase the Factory defaults they sit on top of.\n */\nfunction adapterOverrides(options: SlackAdapterChannelConfig | undefined): SlackAdapterChannelConfig {\n return Object.fromEntries(\n Object.entries(options ?? {}).filter(([, value]) => value !== undefined),\n ) as SlackAdapterChannelConfig;\n}\n\nexport class SlackIntegration implements FactoryIntegration {\n readonly id = 'slack';\n /**\n * The OIDC connect flow round-trips a signed `state` through Slack, so the\n * replica handling the callback must be able to verify a state a different\n * replica signed.\n */\n readonly requiresStableStateSigner = true;\n\n readonly #config: SlackIntegrationConfig;\n /**\n * Whether `channels()` found a source-control owner on the context and wired\n * repo-backed sessions. Set at the channels() attach path, which runs once at\n * boot before diagnostics are served.\n */\n #repoBackedSessions = false;\n\n constructor(config: SlackIntegrationConfig) {\n if (!config.signingSecret) {\n throw new Error(\n \"SlackIntegration: 'signingSecret' is required — Slack cannot verify inbound requests without it.\",\n );\n }\n this.#config = config;\n }\n\n channels(ctx: IntegrationContext): FactoryChannelsConfig {\n // Repo-backed sessions select the source-control provider from the linked\n // repository on each Factory project.\n const sourceControls =\n ctx.storage.sourceControls ?? (ctx.storage.sourceControlOwner ? [ctx.storage.sourceControlOwner] : []);\n this.#repoBackedSessions = sourceControls.length > 0;\n return createSlackChannelsConfig({\n slack: {\n clientId: this.#config.clientId,\n clientSecret: this.#config.clientSecret,\n signingSecret: this.#config.signingSecret,\n botToken: this.#config.botToken,\n },\n accountLinks: ctx.storage.channelIdentity,\n projects: ctx.storage.projects,\n sourceControls,\n memorySettings: ctx.storage.memorySettings,\n workItems: ctx.runtime?.workItems,\n feed: ctx.feed,\n adapterOptions: {\n toolDisplay: 'hidden',\n typingStatus: factoryTypingStatus,\n ...adapterOverrides(this.#config.adapterOptions),\n },\n });\n }\n\n feedPublisher(ctx: IntegrationContext): WorkItemFeedPublisher {\n return new SlackFeedPublisher({ controller: ctx.controller });\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n const { clientId, clientSecret, oidcRedirectBaseUrl, uiOrigin } = this.#config;\n return createSlackConnectRoutes({\n auth: ctx.auth,\n accountLinks: ctx.storage.channelIdentity,\n tenantStateSigner: ctx.stateSigner,\n oidc:\n clientId && clientSecret && oidcRedirectBaseUrl\n ? { clientId, clientSecret, redirectBaseUrl: oidcRedirectBaseUrl, uiOrigin }\n : undefined,\n projects: ctx.storage.projects,\n });\n }\n\n diagnostics(): Record<string, unknown> {\n const { clientId, clientSecret, botToken, oidcRedirectBaseUrl } = this.#config;\n return {\n configured: true,\n botTokenConfigured: Boolean(botToken),\n oidcConfigured: Boolean(clientId && clientSecret && oidcRedirectBaseUrl),\n repoBackedSessions: this.#repoBackedSessions,\n };\n }\n}\n"],"mappings":";;;;AAyEA,MAAM,uBAAsC,UAAS;CACnD,QAAQ,MAAM,MAAd;EACE,KAAK,mBACH,OAAO;EACT,KAAK,cACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,SACE;CACJ;AACF;;;;;AAMA,SAAS,iBAAiB,SAA2E;CACnG,OAAO,OAAO,YACZ,OAAO,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CACzE;AACF;AAEA,IAAa,mBAAb,MAA4D;CAC1D,KAAc;;;;;;CAMd,4BAAqC;CAErC;;;;;;CAMA,sBAAsB;CAEtB,YAAY,QAAgC;EAC1C,IAAI,CAAC,OAAO,eACV,MAAM,IAAI,MACR,kGACF;EAEF,KAAKA,UAAU;CACjB;CAEA,SAAS,KAAgD;EAGvD,MAAM,iBACJ,IAAI,QAAQ,mBAAmB,IAAI,QAAQ,qBAAqB,CAAC,IAAI,QAAQ,kBAAkB,IAAI,CAAC;EACtG,KAAKC,sBAAsB,eAAe,SAAS;EACnD,OAAO,0BAA0B;GAC/B,OAAO;IACL,UAAU,KAAKD,QAAQ;IACvB,cAAc,KAAKA,QAAQ;IAC3B,eAAe,KAAKA,QAAQ;IAC5B,UAAU,KAAKA,QAAQ;GACzB;GACA,cAAc,IAAI,QAAQ;GAC1B,UAAU,IAAI,QAAQ;GACtB;GACA,gBAAgB,IAAI,QAAQ;GAC5B,WAAW,IAAI,SAAS;GACxB,MAAM,IAAI;GACV,gBAAgB;IACd,aAAa;IACb,cAAc;IACd,GAAG,iBAAiB,KAAKA,QAAQ,cAAc;GACjD;EACF,CAAC;CACH;CAEA,cAAc,KAAgD;EAC5D,OAAO,IAAI,mBAAmB,EAAE,YAAY,IAAI,WAAW,CAAC;CAC9D;CAEA,OAAO,KAAqC;EAC1C,MAAM,EAAE,UAAU,cAAc,qBAAqB,aAAa,KAAKA;EACvE,OAAO,yBAAyB;GAC9B,MAAM,IAAI;GACV,cAAc,IAAI,QAAQ;GAC1B,mBAAmB,IAAI;GACvB,MACE,YAAY,gBAAgB,sBACxB;IAAE;IAAU;IAAc,iBAAiB;IAAqB;GAAS,IACzE,KAAA;GACN,UAAU,IAAI,QAAQ;EACxB,CAAC;CACH;CAEA,cAAuC;EACrC,MAAM,EAAE,UAAU,cAAc,UAAU,wBAAwB,KAAKA;EACvE,OAAO;GACL,YAAY;GACZ,oBAAoB,QAAQ,QAAQ;GACpC,gBAAgB,QAAQ,YAAY,gBAAgB,mBAAmB;GACvE,oBAAoB,KAAKC;EAC3B;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"integration.js","names":["#config","#repoBackedSessions"],"sources":["../../../src/integrations/slack/integration.ts"],"sourcesContent":["/**\n * `SlackIntegration` — Slack as a `FactoryIntegration`.\n *\n * Slack contributes two things to a factory: the chat channels that carry\n * inbound messages into agent runs (`channels()`), and the browser-facing\n * account-link routes that bind a Slack sender to a Mastra tenant (`routes()`).\n * Both used to be assembled by hand in the deploy entry, which had to reach\n * into the prepared agent controller by string key, resolve storage domains\n * itself, mint its own state signer, and splice routes onto the factory's\n * assembled server config. Implementing the integration interface instead means\n * the factory does all of that the same way it already does for GitHub and\n * Linear, and the entry's job shrinks to reading Slack's env vars once.\n *\n * Slack ships as a factory built-in alongside GitHub and Linear (it originally\n * lived in `mastracode/web` to keep `@mastra/slack`/`chat` out of this package;\n * the team reversed that so `create-factory` consumers get Slack channels out\n * of the box). The integration interface remains open — third parties still add\n * capabilities by implementing `FactoryIntegration` from outside the package.\n */\n\nimport type { TypingStatusFn } from '@mastra/core/channels';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\n\nimport type { WorkItemFeedPublisher } from '../../storage/domains/comments/feed-sync.js';\nimport type { FactoryChannelsConfig, FactoryIntegration, IntegrationContext } from '../base.js';\n\nimport { createSlackConnectRoutes } from './connect-route.js';\nimport { SlackFeedPublisher } from './feed-publisher.js';\nimport { createSlackChannelsConfig } from './slack.js';\n\n/**\n * Slack app credentials, read from env ONCE by the deploy entry. `signingSecret`\n * is required because the Slack adapter validates it at construction — an\n * integration constructed without it would throw during `prepare()` rather than\n * reporting itself unconfigured.\n */\nexport interface SlackIntegrationConfig {\n /** Verifies inbound Slack request signatures. Required. */\n signingSecret: string;\n /** Bot token used to post replies and ephemeral cards. */\n botToken?: string;\n /**\n * OAuth client credentials. Required for account linking: \"Sign in with\n * Slack\" (OIDC) is the only flow that writes a link, so without these the\n * settings surface reports `canConnect: false` and no sender can link.\n */\n clientId?: string;\n clientSecret?: string;\n /**\n * HTTPS origin Slack redirects back to. Slack requires HTTPS, so locally this\n * is the tunnel origin rather than the app's own public URL.\n */\n oidcRedirectBaseUrl?: string;\n /** SPA origin the post-connect redirect returns to. */\n uiOrigin?: string;\n /**\n * Overrides for the Slack channel adapter entry.\n *\n * @example\n * ```ts\n * new SlackIntegration({\n * signingSecret,\n * adapterOptions: {\n * streaming: true,\n * toolDisplay: 'grouped',\n * },\n * });\n * ```\n */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nconst factoryTypingStatus: TypingStatusFn = chunk => {\n switch (chunk.type) {\n case 'reasoning-delta':\n return 'is thinking...';\n case 'text-delta':\n return 'is typing...';\n case 'tool-call':\n return 'is working...';\n default:\n return undefined;\n }\n};\n\n/**\n * Drops keys the caller set to `undefined` so spreading the overrides cannot\n * erase the Factory defaults they sit on top of.\n */\nfunction adapterOverrides(options: SlackAdapterChannelConfig | undefined): SlackAdapterChannelConfig {\n return Object.fromEntries(\n Object.entries(options ?? {}).filter(([, value]) => value !== undefined),\n ) as SlackAdapterChannelConfig;\n}\n\nexport class SlackIntegration implements FactoryIntegration {\n readonly id = 'slack';\n /**\n * The OIDC connect flow round-trips a signed `state` through Slack, so the\n * replica handling the callback must be able to verify a state a different\n * replica signed.\n */\n readonly requiresStableStateSigner = true;\n\n readonly #config: SlackIntegrationConfig;\n /**\n * Whether `channels()` found a source-control owner on the context and wired\n * repo-backed sessions. Set at the channels() attach path, which runs once at\n * boot before diagnostics are served.\n */\n #repoBackedSessions = false;\n\n constructor(config: SlackIntegrationConfig) {\n if (!config.signingSecret) {\n throw new Error(\n \"SlackIntegration: 'signingSecret' is required — Slack cannot verify inbound requests without it.\",\n );\n }\n this.#config = config;\n }\n\n channels(ctx: IntegrationContext): FactoryChannelsConfig {\n // Repo-backed sessions select the source-control provider from the linked\n // repository on each Factory project.\n const sourceControls =\n ctx.storage.sourceControls ?? (ctx.storage.sourceControlOwner ? [ctx.storage.sourceControlOwner] : []);\n this.#repoBackedSessions = sourceControls.length > 0;\n return createSlackChannelsConfig({\n slack: {\n clientId: this.#config.clientId,\n clientSecret: this.#config.clientSecret,\n signingSecret: this.#config.signingSecret,\n botToken: this.#config.botToken,\n },\n accountLinks: ctx.storage.channelIdentity,\n projects: ctx.storage.projects,\n sourceControls,\n memorySettings: ctx.storage.memorySettings,\n modelPacks: ctx.storage.modelPacks,\n workItems: ctx.runtime?.workItems,\n feed: ctx.feed,\n adapterOptions: {\n toolDisplay: 'hidden',\n typingStatus: factoryTypingStatus,\n ...adapterOverrides(this.#config.adapterOptions),\n },\n });\n }\n\n feedPublisher(ctx: IntegrationContext): WorkItemFeedPublisher {\n return new SlackFeedPublisher({ controller: ctx.controller });\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n const { clientId, clientSecret, oidcRedirectBaseUrl, uiOrigin } = this.#config;\n return createSlackConnectRoutes({\n auth: ctx.auth,\n accountLinks: ctx.storage.channelIdentity,\n tenantStateSigner: ctx.stateSigner,\n oidc:\n clientId && clientSecret && oidcRedirectBaseUrl\n ? { clientId, clientSecret, redirectBaseUrl: oidcRedirectBaseUrl, uiOrigin }\n : undefined,\n projects: ctx.storage.projects,\n });\n }\n\n diagnostics(): Record<string, unknown> {\n const { clientId, clientSecret, botToken, oidcRedirectBaseUrl } = this.#config;\n return {\n configured: true,\n botTokenConfigured: Boolean(botToken),\n oidcConfigured: Boolean(clientId && clientSecret && oidcRedirectBaseUrl),\n repoBackedSessions: this.#repoBackedSessions,\n };\n }\n}\n"],"mappings":";;;;AAyEA,MAAM,uBAAsC,UAAS;CACnD,QAAQ,MAAM,MAAd;EACE,KAAK,mBACH,OAAO;EACT,KAAK,cACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,SACE;CACJ;AACF;;;;;AAMA,SAAS,iBAAiB,SAA2E;CACnG,OAAO,OAAO,YACZ,OAAO,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CACzE;AACF;AAEA,IAAa,mBAAb,MAA4D;CAC1D,KAAc;;;;;;CAMd,4BAAqC;CAErC;;;;;;CAMA,sBAAsB;CAEtB,YAAY,QAAgC;EAC1C,IAAI,CAAC,OAAO,eACV,MAAM,IAAI,MACR,kGACF;EAEF,KAAKA,UAAU;CACjB;CAEA,SAAS,KAAgD;EAGvD,MAAM,iBACJ,IAAI,QAAQ,mBAAmB,IAAI,QAAQ,qBAAqB,CAAC,IAAI,QAAQ,kBAAkB,IAAI,CAAC;EACtG,KAAKC,sBAAsB,eAAe,SAAS;EACnD,OAAO,0BAA0B;GAC/B,OAAO;IACL,UAAU,KAAKD,QAAQ;IACvB,cAAc,KAAKA,QAAQ;IAC3B,eAAe,KAAKA,QAAQ;IAC5B,UAAU,KAAKA,QAAQ;GACzB;GACA,cAAc,IAAI,QAAQ;GAC1B,UAAU,IAAI,QAAQ;GACtB;GACA,gBAAgB,IAAI,QAAQ;GAC5B,YAAY,IAAI,QAAQ;GACxB,WAAW,IAAI,SAAS;GACxB,MAAM,IAAI;GACV,gBAAgB;IACd,aAAa;IACb,cAAc;IACd,GAAG,iBAAiB,KAAKA,QAAQ,cAAc;GACjD;EACF,CAAC;CACH;CAEA,cAAc,KAAgD;EAC5D,OAAO,IAAI,mBAAmB,EAAE,YAAY,IAAI,WAAW,CAAC;CAC9D;CAEA,OAAO,KAAqC;EAC1C,MAAM,EAAE,UAAU,cAAc,qBAAqB,aAAa,KAAKA;EACvE,OAAO,yBAAyB;GAC9B,MAAM,IAAI;GACV,cAAc,IAAI,QAAQ;GAC1B,mBAAmB,IAAI;GACvB,MACE,YAAY,gBAAgB,sBACxB;IAAE;IAAU;IAAc,iBAAiB;IAAqB;GAAS,IACzE,KAAA;GACN,UAAU,IAAI,QAAQ;EACxB,CAAC;CACH;CAEA,cAAuC;EACrC,MAAM,EAAE,UAAU,cAAc,UAAU,wBAAwB,KAAKA;EACvE,OAAO;GACL,YAAY;GACZ,oBAAoB,QAAQ,QAAQ;GACpC,gBAAgB,QAAQ,YAAY,gBAAgB,mBAAmB;GACvE,oBAAoB,KAAKC;EAC3B;CACF;AACF"}
|
|
@@ -3,6 +3,7 @@ import type { SlackAdapterChannelConfig } from '@mastra/slack';
|
|
|
3
3
|
import type { ChannelAccountLink, ChannelAccountLinkKey, ChannelIdentityStorage } from '../../storage/domains/channel-identity/base.js';
|
|
4
4
|
import type { CommentsDomain } from '../../storage/domains/comments/domain.js';
|
|
5
5
|
import type { MemorySettingsStorage } from '../../storage/domains/memory-settings/base.js';
|
|
6
|
+
import type { ModelPacksStorage } from '../../storage/domains/model-packs/base.js';
|
|
6
7
|
import type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';
|
|
7
8
|
import type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';
|
|
8
9
|
import type { ExternalWorkItemSource, WorkItemsStorage } from '../../storage/domains/work-items/base.js';
|
|
@@ -45,9 +46,18 @@ interface SlackChannelDeps {
|
|
|
45
46
|
/**
|
|
46
47
|
* Observational-memory settings domain. When provided, a repo-backed session
|
|
47
48
|
* adopts its factory project's shared memory settings on start, matching the
|
|
48
|
-
* web kickoff
|
|
49
|
+
* web kickoff — and the linked sender's own settings win over them for every
|
|
50
|
+
* knob the sender has saved.
|
|
49
51
|
*/
|
|
50
52
|
memorySettings?: MemorySettingsStorage;
|
|
53
|
+
/**
|
|
54
|
+
* Model-packs domain. When provided, a new repo-backed session starts on the
|
|
55
|
+
* linked sender's active pack build model — the model that user picked for
|
|
56
|
+
* themselves — before the factory's shared default. Read once, when the
|
|
57
|
+
* thread's build model is first chosen; the choice is persisted on the thread
|
|
58
|
+
* and never re-resolved. Unset → the factory default, as before.
|
|
59
|
+
*/
|
|
60
|
+
modelPacks?: ModelPacksStorage;
|
|
51
61
|
/**
|
|
52
62
|
* Factory work-items domain. When provided, a dispatched new-session thread
|
|
53
63
|
* (DM or mention) upserts a Work-board card in Building (`execute`) carrying
|
|
@@ -161,10 +171,24 @@ export declare function createChannelSessionResolver(deps: SlackChannelDeps): Ch
|
|
|
161
171
|
* session id, which the source-control rows turn back into a project — a
|
|
162
172
|
* chat-only `channel:...` id names no project, so there is nothing to read.
|
|
163
173
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
174
|
+
* The model this session starts on is picked here, once, in the order of who
|
|
175
|
+
* chose it: the linked sender's active model pack, else the factory project's
|
|
176
|
+
* default, else the SDK's built-in mode default. The choice is persisted on the
|
|
177
|
+
* thread as `modeModelId_<mode>`, so it outlives the process that made it.
|
|
178
|
+
*
|
|
179
|
+
* Observational memory is configured here too, in the same order of who chose
|
|
180
|
+
* it: the project's shared settings first, then the linked sender's own row,
|
|
181
|
+
* which wins for every knob they have saved. The pair is re-applied on every
|
|
182
|
+
* start rather than once — memory settings are stored preference, not a choice
|
|
183
|
+
* made on this thread, and a restarted process re-resolves the project's row
|
|
184
|
+
* before this hook runs.
|
|
185
|
+
*
|
|
186
|
+
* The model resolution is skipped on a session whose mode already has a model
|
|
187
|
+
* persisted on the thread. That is the durable record of a deliberate choice —
|
|
188
|
+
* either an earlier start or a user's own switch — and re-applying a preference
|
|
189
|
+
* over it would undo the user's selection every time the process restarts.
|
|
190
|
+
* Memory settings have no such per-thread record, so they are re-applied on
|
|
191
|
+
* every start.
|
|
168
192
|
*/
|
|
169
193
|
export declare function createChannelSessionStartHook(deps: SlackChannelDeps): ChannelSessionStart;
|
|
170
194
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/slack.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EAEd,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EAChB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/slack.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { actorFromChannelAuthor } from "../../storage/domains/comments/actor.js";
|
|
2
2
|
import { readRequestContextOrgId, seedSessionOrg } from "../../session/org-seed.js";
|
|
3
|
+
import { applyPersonalMemorySettings } from "../../session/memory-settings-hydration.js";
|
|
3
4
|
import { createSourceControlSessionLookup, hydrateFactorySession, resolveFactoryDefaultModelId, resolveFactoryProjectForSession, resolveFactorySourceControl, resolveFactorySourceRepository } from "../../session/factory-session.js";
|
|
4
5
|
import { resolveEmojiShortcodes } from "./emoji.js";
|
|
5
6
|
import { slackCommentSource } from "./feed-publisher.js";
|
|
@@ -269,13 +270,27 @@ function createChannelSessionResolver(deps) {
|
|
|
269
270
|
* session id, which the source-control rows turn back into a project — a
|
|
270
271
|
* chat-only `channel:...` id names no project, so there is nothing to read.
|
|
271
272
|
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
273
|
+
* The model this session starts on is picked here, once, in the order of who
|
|
274
|
+
* chose it: the linked sender's active model pack, else the factory project's
|
|
275
|
+
* default, else the SDK's built-in mode default. The choice is persisted on the
|
|
276
|
+
* thread as `modeModelId_<mode>`, so it outlives the process that made it.
|
|
277
|
+
*
|
|
278
|
+
* Observational memory is configured here too, in the same order of who chose
|
|
279
|
+
* it: the project's shared settings first, then the linked sender's own row,
|
|
280
|
+
* which wins for every knob they have saved. The pair is re-applied on every
|
|
281
|
+
* start rather than once — memory settings are stored preference, not a choice
|
|
282
|
+
* made on this thread, and a restarted process re-resolves the project's row
|
|
283
|
+
* before this hook runs.
|
|
284
|
+
*
|
|
285
|
+
* The model resolution is skipped on a session whose mode already has a model
|
|
286
|
+
* persisted on the thread. That is the durable record of a deliberate choice —
|
|
287
|
+
* either an earlier start or a user's own switch — and re-applying a preference
|
|
288
|
+
* over it would undo the user's selection every time the process restarts.
|
|
289
|
+
* Memory settings have no such per-thread record, so they are re-applied on
|
|
290
|
+
* every start.
|
|
276
291
|
*/
|
|
277
292
|
function createChannelSessionStartHook(deps) {
|
|
278
|
-
const { projects, memorySettings } = deps;
|
|
293
|
+
const { projects, memorySettings, modelPacks } = deps;
|
|
279
294
|
const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));
|
|
280
295
|
return async ({ session, thread, requestContext }) => {
|
|
281
296
|
await seedSessionOrg(session, readRequestContextOrgId(requestContext));
|
|
@@ -291,17 +306,76 @@ function createChannelSessionStartHook(deps) {
|
|
|
291
306
|
await session.state.set({ factoryProjectId: owner.factoryProjectId });
|
|
292
307
|
await seedSessionOrg(session, owner.orgId);
|
|
293
308
|
const modeModelKey = `modeModelId_${session.mode.get()}`;
|
|
294
|
-
if (await session.thread.getSetting({ key: modeModelKey }))
|
|
295
|
-
|
|
296
|
-
|
|
309
|
+
if (!await session.thread.getSetting({ key: modeModelKey })) {
|
|
310
|
+
const factoryModelId = await resolveFactoryDefaultModelId(projects, owner.factoryProjectId);
|
|
311
|
+
const userModelId = await resolveActivePackBuildModel(modelPacks, owner);
|
|
312
|
+
await hydrateFactorySession(session, {
|
|
313
|
+
orgId: owner.orgId,
|
|
314
|
+
factoryProjectId: owner.factoryProjectId,
|
|
315
|
+
defaultModelId: factoryModelId,
|
|
316
|
+
memorySettings
|
|
317
|
+
});
|
|
318
|
+
const selectedModelId = userModelId ?? factoryModelId;
|
|
319
|
+
if (selectedModelId && selectedModelId !== factoryModelId) try {
|
|
320
|
+
await session.model.switch({ modelId: selectedModelId });
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.warn("[slack] Failed to apply the sender's model pack model", {
|
|
323
|
+
modelId: selectedModelId,
|
|
324
|
+
error: error instanceof Error ? error.message : String(error)
|
|
325
|
+
});
|
|
326
|
+
const currentModelId = session.model.get();
|
|
327
|
+
if (currentModelId && !factoryModelId) try {
|
|
328
|
+
await session.model.saveForMode({
|
|
329
|
+
modeId: session.mode.get(),
|
|
330
|
+
modelId: currentModelId
|
|
331
|
+
});
|
|
332
|
+
} catch (saveError) {
|
|
333
|
+
console.warn("[slack] Failed to persist the sender's model pack model", {
|
|
334
|
+
modelId: currentModelId,
|
|
335
|
+
error: saveError instanceof Error ? saveError.message : String(saveError)
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else if (!selectedModelId) {
|
|
340
|
+
const currentModelId = session.model.get();
|
|
341
|
+
if (currentModelId) await session.model.saveForMode({
|
|
342
|
+
modeId: session.mode.get(),
|
|
343
|
+
modelId: currentModelId
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
await applyPersonalMemorySettings(session, {
|
|
348
|
+
memorySettings,
|
|
297
349
|
orgId: owner.orgId,
|
|
298
|
-
|
|
299
|
-
defaultModelId,
|
|
300
|
-
memorySettings
|
|
350
|
+
userId: owner.userId
|
|
301
351
|
});
|
|
302
352
|
};
|
|
303
353
|
}
|
|
304
354
|
/**
|
|
355
|
+
* The `build` model of the sender's active model pack — the model that user
|
|
356
|
+
* chose for themselves — or `undefined` when they have no pack.
|
|
357
|
+
*
|
|
358
|
+
* Best-effort by design: an uninitialized model-packs domain, a read failure, or
|
|
359
|
+
* a pack saved without a build model all mean "no personal preference", which
|
|
360
|
+
* falls through to the factory default rather than failing the dispatch.
|
|
361
|
+
*/
|
|
362
|
+
async function resolveActivePackBuildModel(modelPacks, owner) {
|
|
363
|
+
if (!modelPacks) return void 0;
|
|
364
|
+
try {
|
|
365
|
+
return (await modelPacks.getActive({
|
|
366
|
+
orgId: owner.orgId,
|
|
367
|
+
userId: owner.userId
|
|
368
|
+
}))?.models.build || void 0;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.warn("[slack] model pack lookup failed for a new session", {
|
|
371
|
+
orgId: owner.orgId,
|
|
372
|
+
userId: owner.userId,
|
|
373
|
+
error: error instanceof Error ? error.message : String(error)
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
305
379
|
* The internal Mastra thread the framework created for a channel conversation.
|
|
306
380
|
* The handler's `thread.id` is the platform thread id (e.g. `slack:C123:ts`),
|
|
307
381
|
* NOT the internal UUID — the mapping lives in the stored thread's channel
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.js","names":[],"sources":["../../../src/integrations/slack/slack.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type {\n ChannelHandler,\n ChannelHandlerContext,\n ChannelHandlers,\n ChannelSessionResolve,\n ChannelSessionStart,\n ResolveResourceId,\n ResolveThreadId,\n} from '@mastra/core/channels';\nimport type { Mastra } from '@mastra/core/mastra';\nimport { createSlackAdapter } from '@mastra/slack';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\nimport { Card, CardText, Actions, LinkButton } from 'chat';\n\nimport {\n createSourceControlSessionLookup,\n hydrateFactorySession,\n resolveFactoryDefaultModelId,\n resolveFactoryProjectForSession,\n resolveFactorySourceControl,\n resolveFactorySourceRepository,\n} from '../../session/factory-session.js';\nimport { readRequestContextOrgId, seedSessionOrg } from '../../session/org-seed.js';\nimport type {\n ChannelAccountLink,\n ChannelAccountLinkKey,\n ChannelIdentityStorage,\n} from '../../storage/domains/channel-identity/base.js';\nimport type { FactoryActorExternalIdentity } from '../../storage/domains/comments/actor.js';\nimport { actorFromChannelAuthor } from '../../storage/domains/comments/actor.js';\nimport type { CommentsDomain } from '../../storage/domains/comments/domain.js';\nimport type { MemorySettingsStorage } from '../../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport type { ExternalWorkItemSource, WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport type { FactoryChannelsConfig } from '../base.js';\n\nimport { resolveEmojiShortcodes } from './emoji.js';\nimport { slackCommentSource } from './feed-publisher.js';\n\n// Derive the thread/message types from the core handler signature rather than\n// importing them from `chat` directly: mc-web can resolve a different `chat`\n// version than @mastra/core, and the two `Thread`/`Message` declarations are\n// structurally incompatible (private fields). Using the handler's own types\n// keeps everything on one version.\ntype HandlerThread = Parameters<ChannelHandler>[0];\ntype HandlerMessage = Parameters<ChannelHandler>[1];\n\nconst SLACK_REQUEST_TIMEOUT_MS = 15_000;\nconst MAX_WORK_ITEM_TITLE_CHARS = 80;\nconst graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n\n/**\n * A card is titled with what the sender wrote. Cutting counts graphemes, not\n * code points: `👍🏼` is a base plus a skin-tone modifier, and a cut between\n * them leaves the wrong emoji.\n */\nfunction workItemTitleFrom(messageText: string): string {\n const resolved = resolveEmojiShortcodes(messageText);\n const characters = [...graphemes.segment(resolved)].map(({ segment }) => segment);\n if (characters.length <= MAX_WORK_ITEM_TITLE_CHARS) return resolved;\n return `${characters.slice(0, MAX_WORK_ITEM_TITLE_CHARS - 1).join('')}…`;\n}\n\n/** Dependencies the Slack channel handlers close over, injected from the web entry. */\ninterface SlackChannelDeps {\n /**\n * The factory's reverse-index store mapping a Slack sender to a Mastra\n * tenant. When provided, inbound messages from an unlinked sender are not\n * dispatched — the run only proceeds (with the sender's tenant stamped on\n * the request context) once they've linked their account. Unlinked senders\n * get an ephemeral \"connect your account\" card instead.\n */\n accountLinks?: ChannelIdentityStorage;\n /**\n * Factory projects domain. When provided (alongside `accountLinks`), a\n * linked sender's run must also resolve to a Factory project before it\n * dispatches: their link's default factory, else their tenant's only\n * factory (stamped back onto the link), else an ephemeral \"pick a default\n * factory\" card and no run. Unset → no factory routing (runs dispatch as\n * before).\n */\n projects?: FactoryProjectsStorage;\n /**\n * Storage handle of the integration that owns source control\n * (`IntegrationContext.storage.sourceControlOwner`). Used to make new Slack\n * threads repo-backed: when the sender is linked and their factory has a\n * repository, the thread's resourceId becomes a Factory user-session id (repo\n * cloned on a `slack/{threadTs}` branch) instead of the chat-only\n * `channel:...` id. It also lets a started session read back the project it\n * belongs to. Nothing here is provider-specific — the connection is matched\n * by the handle's own `integrationId`. Absent (no source-control integration\n * registered) → chat-only sessions as before.\n */\n sourceControl?: SourceControlStorageHandle;\n /** Registered source-control partitions used to select the provider linked to each Factory project. */\n sourceControls?: readonly SourceControlStorageHandle[];\n /**\n * Observational-memory settings domain. When provided, a repo-backed session\n * adopts its factory project's shared memory settings on start, matching the\n * web kickoff.\n */\n memorySettings?: MemorySettingsStorage;\n /**\n * Factory work-items domain. When provided, a dispatched new-session thread\n * (DM or mention) upserts a Work-board card in Building (`execute`) carrying\n * the Slack thread as its external source and binding the repo-backed\n * session. Best-effort — a failure never blocks the run. Unset → no card.\n */\n workItems?: WorkItemsStorage;\n /**\n * Work-item feed. With it (and `workItems`), an `aside` lands as a comment on\n * the card the thread created. Unset → asides stay ignored, as before.\n */\n feed?: Pick<CommentsDomain, 'createComment'>;\n /** Overrides applied to the Slack channel adapter entry. */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nfunction configuredSourceControls(deps: SlackChannelDeps): readonly SourceControlStorageHandle[] {\n return deps.sourceControls ?? (deps.sourceControl ? [deps.sourceControl] : []);\n}\n\n/**\n * Read the Slack team id off a raw platform payload (Events API envelope or\n * slash-command body — both carry `team_id`), duck-typed to build the\n * workspace-scoped account-link key.\n */\nfunction rawTeamId(rawPayload: unknown): string | undefined {\n if (!rawPayload || typeof rawPayload !== 'object') return undefined;\n const raw = rawPayload as { team_id?: unknown; team?: unknown };\n if (typeof raw.team_id === 'string' && raw.team_id) return raw.team_id;\n if (typeof raw.team === 'string' && raw.team) return raw.team;\n if (raw.team && typeof raw.team === 'object') {\n const id = (raw.team as { id?: unknown }).id;\n if (typeof id === 'string' && id) return id;\n }\n return undefined;\n}\n\n/**\n * The Slack team id survives onto a normalized chat Message only on\n * `message.raw` (the Slack Events API envelope).\n */\nfunction slackTeamId(message: HandlerMessage): string | undefined {\n return rawTeamId(message.raw);\n}\n\n/**\n * Resolve the web-UI origin for links humans open in a browser (Connect card,\n * session deep links). Prefers `MASTRACODE_PUBLIC_URL` — the origin auth\n * cookies and OAuth redirect allow-lists are registered against — over the\n * channels tunnel, which only Slack's servers need to reach.\n */\nfunction webPublicUrl(): string | undefined {\n return process.env.MASTRACODE_PUBLIC_URL ?? process.env.MASTRACODE_CHANNELS_PUBLIC_URL;\n}\n\n/** Outcome of the sender-link gate for one inbound message. */\ntype LinkedSenderResult =\n /** Gating not configured — dispatch as before account linking existed. */\n | { status: 'ungated' }\n /** Sender unlinked — Connect card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** Sender linked — their tenant plus the sender key the link lives under. */\n | { status: 'linked'; link: ChannelAccountLink; key: ChannelAccountLinkKey };\n\n/**\n * Resolve the sender's account link, posting an ephemeral \"connect your\n * account\" card (visible only to the sender) linking into the web UI's\n * Slack-connect flow when they're unlinked.\n */\nexport async function resolveLinkedSender({\n thread,\n message,\n accountLinks,\n}: {\n thread: HandlerThread;\n message: HandlerMessage;\n accountLinks?: ChannelIdentityStorage;\n}): Promise<LinkedSenderResult> {\n if (!accountLinks) return { status: 'ungated' };\n const platform = thread.adapter.name;\n const externalUserId = message.author.userId;\n const externalTeamId = slackTeamId(message);\n // Without a team id we can't identify the workspace-scoped link; treat as\n // unlinked so a run never proceeds tenant-less.\n const key = externalTeamId ? { platform, externalTeamId, externalUserId } : undefined;\n const link = key ? await accountLinks.getAccountLink(key) : null;\n if (link && key) return { status: 'linked', link, key };\n\n const publicUrl = webPublicUrl();\n // A public origin is all the card needs. The link carries no identity: the\n // web app authenticates the visitor, then Slack's OIDC flow proves which\n // Slack account they control. Without an origin, still block, just no card.\n if (publicUrl) {\n await thread.postEphemeral(message.author, buildConnectCard(publicUrl), { fallbackToDM: true });\n }\n return { status: 'blocked' };\n}\n\n/**\n * The \"connect your account\" card. The link is deliberately identity-free —\n * `/connect/slack` sends the visitor to Connections, where \"Connect Slack\"\n * runs the OIDC flow and Slack itself asserts the (team, user) pair.\n */\nfunction buildConnectCard(publicUrl: string) {\n return Card({\n title: 'Connect your account',\n children: [\n CardText('Connect your account to use this agent.'),\n Actions([\n LinkButton({\n url: `${publicUrl}/connect/slack`,\n label: 'Connect account',\n }),\n ]),\n ],\n });\n}\n\n/** Outcome of factory routing for one linked sender's inbound message. */\ntype FactoryRouteResult =\n /** Factory routing not configured — dispatch without a factory. */\n | { status: 'ungated' }\n /** No factory resolved — prompt card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** The Factory project this sender's runs route to. */\n | { status: 'resolved'; factoryProjectId: string; slackWorkItemsEnabled: boolean };\n\n/**\n * Decide which Factory project a linked sender's run belongs to:\n *\n * 1. The link's `defaultFactoryProjectId`, when it still exists (a stale id —\n * deleted factory — falls through as if unset).\n * 2. Else, the tenant's only factory, stamped back onto the link so it shows\n * up (and stays editable) in Connected Accounts settings.\n * 3. Else — zero or several factories — an ephemeral \"pick a default factory\"\n * card deep-linking to settings, and the run is blocked.\n */\nexport async function resolveFactoryForLink({\n thread,\n message,\n link,\n key,\n accountLinks,\n projects,\n}: {\n thread: HandlerThread;\n message: HandlerMessage;\n link: ChannelAccountLink;\n key: ChannelAccountLinkKey;\n accountLinks: ChannelIdentityStorage;\n projects?: FactoryProjectsStorage;\n}): Promise<FactoryRouteResult> {\n if (!projects) return { status: 'ungated' };\n // Factories are org-scoped; a personal account (no org) has none and lands\n // on the prompt below.\n const orgId = link.orgId ?? '';\n\n if (link.defaultFactoryProjectId) {\n const existing = await projects.get({ orgId, id: link.defaultFactoryProjectId });\n if (existing) {\n return {\n status: 'resolved',\n factoryProjectId: existing.id,\n slackWorkItemsEnabled: existing.slackWorkItemsEnabled,\n };\n }\n }\n\n const factories = orgId ? await projects.list({ orgId }) : [];\n if (factories.length === 1) {\n const only = factories[0]!;\n await accountLinks.setDefaultFactory({ ...key, userId: link.userId, factoryProjectId: only.id });\n return {\n status: 'resolved',\n factoryProjectId: only.id,\n slackWorkItemsEnabled: only.slackWorkItemsEnabled,\n };\n }\n\n const publicUrl = webPublicUrl();\n if (publicUrl) {\n await thread.postEphemeral(\n message.author,\n Card({\n title: 'Pick a default factory',\n children: [\n CardText(\n factories.length === 0\n ? 'Your account has no factory yet. Create one in the web app, then message me again.'\n : 'Your account has several factories. Pick which one Slack sessions should go to, then message me again.',\n ),\n Actions([\n LinkButton({\n url: `${publicUrl}/settings/connections`,\n label: 'Open settings',\n }),\n ]),\n ],\n }),\n { fallbackToDM: true },\n );\n }\n return { status: 'blocked' };\n}\n\n/**\n * Deterministic per-thread branch name: `slack/{threadTs}` with characters\n * outside the sandbox git-ref allow-list (`[A-Za-z0-9_./-]`, and `.` for\n * readability) mapped to `-`. `thread.id` is `{channelId}:{threadTs}`\n * (platform-prefixed on handler threads) — the trailing segment is the ts.\n *\n * Top-level DM and channel conversations use the empty-threadTs thread form,\n * so the trailing segment can be empty; a bare `slack/` is not a valid git\n * ref. Fall back to the last non-empty segment (the channel id): one\n * deterministic branch per top-level conversation.\n */\nfunction threadBranch(threadId: string): string {\n const segments = threadId.split(':');\n const tail = segments.findLast(segment => segment.length > 0) ?? threadId;\n return `slack/${tail.replace(/[^A-Za-z0-9_/-]/g, '-')}`;\n}\n\n/**\n * Resolve the resourceId for a NEW Slack channel thread. A linked sender whose\n * factory has a repository gets a Factory user-session id — the controller\n * session then materializes the repo sandbox via the factory's dynamic\n * workspace (clone + PAT), the session shows up in the web Sessions list, and\n * View Session deep-links land on the normal workspace route. Everything else\n * (unlinked, unrouted, repo-less, or no source control) keeps the chat-only\n * `defaultResourceId`.\n *\n * Pure lookups only — cards for unlinked/unrouted senders are the dispatch\n * gate's job; this hook must never post.\n */\nexport function createChannelResourceIdResolver(deps: SlackChannelDeps): ResolveResourceId {\n const { accountLinks, projects } = deps;\n const sourceControls = configuredSourceControls(deps);\n return async ({ platform, thread, message }) => {\n // NOT the hook's `defaultResourceId`: configuring a custom resolver\n // bypasses AgentControllerChannels' own `channel:{thread.id}` derivation\n // (agent-controller-channels.ts `resolveChannelResourceId`), and the base\n // default is the per-USER memory key. Chat-only fallbacks must stay\n // per-thread, so reproduce the controller default here.\n const chatOnlyResourceId = `channel:${thread.id}`;\n if (!accountLinks || !projects || sourceControls.length === 0) return chatOnlyResourceId;\n try {\n const externalTeamId = rawTeamId(message.raw);\n if (!externalTeamId) return chatOnlyResourceId;\n const link = await accountLinks.getAccountLink({\n platform,\n externalTeamId,\n externalUserId: message.author.userId,\n });\n if (!link) return chatOnlyResourceId;\n\n // Same chain as `resolveFactoryForLink`, minus prompts/stamping: the\n // dispatch gate has already run (and stamped a lone factory) by the\n // time a new thread is created, so this is a read-only re-resolve.\n const orgId = link.orgId ?? '';\n let factoryProjectId: string | undefined;\n if (link.defaultFactoryProjectId && (await projects.get({ orgId, id: link.defaultFactoryProjectId }))) {\n factoryProjectId = link.defaultFactoryProjectId;\n } else if (orgId) {\n const factories = await projects.list({ orgId });\n if (factories.length === 1) factoryProjectId = factories[0]!.id;\n }\n if (!factoryProjectId) return chatOnlyResourceId;\n\n const sourceControl = await resolveFactorySourceControl({ sourceControls, orgId, factoryProjectId });\n if (!sourceControl) return chatOnlyResourceId;\n const repo = await resolveFactorySourceRepository({ sourceControl, orgId, factoryProjectId });\n if (!repo.found) return chatOnlyResourceId;\n\n const branch = threadBranch(thread.id);\n // Attributed to the Slack sender, not to whoever connected the repository:\n // unlike an autonomous rule run, a Slack thread has a real interactive user.\n const existing = await sourceControl.sessions.getForBranch({\n projectRepositoryId: repo.projectRepositoryId,\n userId: link.userId,\n branch,\n });\n if (existing) return existing.sessionId;\n const session = await sourceControl.sessions.create({\n sessionId: randomUUID(),\n projectRepositoryId: repo.projectRepositoryId,\n orgId,\n userId: link.userId,\n branch,\n baseBranch: repo.baseBranch,\n // DMs are the only private origin; channel threads are org-visible.\n visibility: thread.isDM ? 'private' : 'org',\n });\n return session.sessionId;\n } catch (error) {\n // Fall back to a chat-only session rather than dropping the message.\n console.warn('[slack] repo-backed session resolution failed for thread', thread.id, error);\n return chatOnlyResourceId;\n }\n };\n}\n\n/**\n * Thread id for a NEW Slack channel thread. Repo-backed threads take the\n * user-session id AS their thread id, matching the web convention\n * (FactoryStartCoordinator seeds threads with threadId = sessionId) so\n * `/workspaces/{sessionId}/threads/{sessionId}` resolves Slack-created\n * sessions exactly like web-created ones — no `?resourceId=` override needed.\n * Chat-only threads keep the default random id: their `channel:...`\n * resourceId is a memory key, not a unique thread id.\n */\nexport const resolveChannelThreadId: ResolveThreadId = ({ resourceId, defaultThreadId }) =>\n resourceId.startsWith('channel:') ? defaultThreadId : resourceId;\n\n/** Create channel sessions with their Factory ownership present in initial controller state. */\nexport function createChannelSessionResolver(deps: SlackChannelDeps): ChannelSessionResolve {\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ controller, thread, requestContext }) => {\n const sourceControl = thread.resourceId.startsWith('channel:')\n ? null\n : await sourceControlSessions.getSourceControlBySessionId(thread.resourceId);\n const owner = sourceControl\n ? await resolveFactoryProjectForSession({ sourceControl, sessionId: thread.resourceId })\n : null;\n return controller.createSession({\n id: thread.resourceId,\n ownerId: controller.id,\n resourceId: thread.resourceId,\n requestContext,\n ...(owner ? { tags: { factoryProjectId: owner.factoryProjectId } } : {}),\n });\n };\n}\n\n/**\n * Apply the factory's configuration to a Slack-created session the first time\n * its thread reaches the controller.\n *\n * Without this a Slack session runs on the SDK's built-in mode default\n * (`openai/gpt-5.5`), so a factory configured for any other provider fails every\n * message with a missing-credentials error. The web kickoff has always applied\n * the factory default; this brings Slack to the same footing.\n *\n * Only repo-backed threads are configured. Their resourceId IS the Factory\n * session id, which the source-control rows turn back into a project — a\n * chat-only `channel:...` id names no project, so there is nothing to read.\n *\n * Skips a session whose mode already has a model persisted on the thread. That\n * is the durable record of a deliberate choice — either an earlier start or a\n * user's own switch — and re-applying the factory default over it would undo\n * the user's selection every time the process restarts.\n */\nexport function createChannelSessionStartHook(deps: SlackChannelDeps): ChannelSessionStart {\n const { projects, memorySettings } = deps;\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ session, thread, requestContext }) => {\n // Seed the tenant org above every guard below. `gateDispatch` stamps it on\n // the message's request context before the session exists, so this needs no\n // storage read — which matters, because the guards below deliberately skip\n // storage on a restarted session. A channel-only thread and a thread whose\n // dispatch was ungated both land here with no org and are marked unresolved\n // rather than being left to look like a local session.\n await seedSessionOrg(session, readRequestContextOrgId(requestContext));\n\n if (!projects) return;\n if (thread.resourceId.startsWith('channel:')) return;\n\n const sourceControl = await sourceControlSessions.getSourceControlBySessionId(thread.resourceId);\n if (!sourceControl) return;\n const owner = await resolveFactoryProjectForSession({ sourceControl, sessionId: thread.resourceId });\n if (!owner) return;\n\n // Repo-backed Slack sessions are factory sessions: stamp the owning\n // project onto controller state so downstream reads (org-first credential\n // resolution, authority gates) recognize them, same as board runs.\n await session.state.set({ factoryProjectId: owner.factoryProjectId });\n // Route the org through the seed helper rather than stamping it directly:\n // an ungated dispatch marked this session unresolved above, and owner\n // recovery is the resolution that has to clear that marker with it.\n await seedSessionOrg(session, owner.orgId);\n\n const modeModelKey = `modeModelId_${session.mode.get()}`;\n if (await session.thread.getSetting({ key: modeModelKey })) return;\n\n const defaultModelId = await resolveFactoryDefaultModelId(projects, owner.factoryProjectId);\n await hydrateFactorySession(session, {\n orgId: owner.orgId,\n factoryProjectId: owner.factoryProjectId,\n defaultModelId,\n memorySettings,\n });\n };\n}\n\n/**\n * The internal Mastra thread the framework created for a channel conversation.\n * The handler's `thread.id` is the platform thread id (e.g. `slack:C123:ts`),\n * NOT the internal UUID — the mapping lives in the stored thread's channel\n * metadata.\n */\nasync function findInternalThread(mastra: Mastra | undefined, thread: HandlerThread) {\n const store = await mastra?.getStorage()?.getStore('memory');\n const { threads } = (await store?.listThreads({\n filter: {\n metadata: {\n channel_platform: thread.adapter.name,\n channel_externalThreadId: thread.id,\n channel_externalChannelId: thread.channelId,\n },\n },\n perPage: 1,\n })) ?? { threads: [] };\n return threads[0];\n}\n\n/**\n * Build the \"new session\" handler for mention / direct-message events. A mention or\n * DM on a not-yet-subscribed thread starts a NEW session; once subscribed, later\n * events are follow-ups and don't re-announce.\n */\n/**\n * Run the account-link + factory-routing gates for one inbound message.\n * Returns `null` when the run must not dispatch (a prompt card was posted\n * where possible); otherwise the dispatch context — with `routed` present\n * only when a linked sender resolved to a factory.\n */\nasync function gateDispatch(\n thread: HandlerThread,\n message: HandlerMessage,\n { accountLinks, projects }: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n): Promise<{\n routed?: { link: ChannelAccountLink; factoryProjectId: string; slackWorkItemsEnabled: boolean };\n} | null> {\n const sender = await resolveLinkedSender({ thread, message, accountLinks });\n if (sender.status === 'blocked') return null;\n // Linked senders must also route to a Factory project before a run starts.\n if (sender.status === 'linked' && accountLinks) {\n // Stamp the tenant on the run's request context — the single seam\n // `resolveCredentialStore` reads to load this sender's model credentials.\n // This belongs to the link, not to the routing: a linked sender whose\n // factory routing comes back `ungated` still exits below and dispatches, so\n // stamping only in the routed branch would silently run them on default\n // credentials.\n ctx.requestContext.set('user', { id: sender.link.userId, organizationId: sender.link.orgId });\n\n const route = await resolveFactoryForLink({ thread, message, ...sender, accountLinks, projects });\n if (route.status === 'blocked') return null;\n if (route.status === 'resolved') {\n return {\n routed: {\n link: sender.link,\n factoryProjectId: route.factoryProjectId,\n slackWorkItemsEnabled: route.slackWorkItemsEnabled,\n },\n };\n }\n }\n return {};\n}\n\n/**\n * A channel id and a `ts` name a thread only inside the workspace that issued\n * them, so the team scopes the card's key — without it two workspaces could\n * hold one key and an aside would land on another tenant's card.\n */\nexport function slackThreadSource(thread: HandlerThread, teamId?: string): ExternalWorkItemSource {\n return {\n integrationId: thread.adapter.name,\n type: 'slack-thread',\n ...(teamId ? { workspaceId: teamId } : {}),\n externalId: thread.id,\n };\n}\n\n/** Cards written before the key carried a team are still keyed bare. */\nasync function findThreadWorkItem(\n workItems: WorkItemsStorage,\n thread: HandlerThread,\n teamId?: string,\n): Promise<WorkItemRow | null> {\n const scoped = await workItems.getBySource(slackThreadSource(thread, teamId));\n if (scoped || !teamId) return scoped;\n return workItems.getBySource(slackThreadSource(thread));\n}\n\n/**\n * Upsert the Work-board card for a dispatched Slack-thread run. Keyed on the\n * thread via `externalSource` — the work-items domain's unique\n * `(factory_project_id, source_key)` index makes repeat messages reuse the\n * same card, and `reuseMode: 'preserve'` keeps a card a human already dragged\n * across stages untouched. The card lands in Building (`execute`) for every\n * dispatched thread (DM or mention) — there is deliberately no per-origin\n * stage split; smart routing is a follow-up.\n *\n * The session id / branch / threadId and the workspace deep-link are resolved\n * by the caller (which already looked up the internal thread), so this helper\n * just shapes and writes. Best-effort: the run is already dispatched, so a\n * failure logs instead of throwing — work-item creation must never abort a Slack run.\n */\nexport async function upsertThreadWorkItem({\n workItems,\n thread,\n message,\n link,\n factoryProjectId,\n session,\n url,\n}: {\n workItems: WorkItemsStorage;\n thread: HandlerThread;\n message: HandlerMessage;\n link: ChannelAccountLink;\n factoryProjectId: string;\n /**\n * The repo-backed Factory session to bind under the `chat` role, or\n * `undefined` for a chat-only thread (no Factory session to bind).\n */\n session?: { sessionId: string; branch: string; threadId: string };\n /** Workspace deep-link to the running session; omitted when no public URL. */\n url?: string;\n}): Promise<void> {\n try {\n const title = workItemTitleFrom(message.text);\n\n await workItems.upsert({\n orgId: link.orgId ?? '',\n userId: link.userId,\n factoryProjectId,\n reuseMode: 'preserve',\n input: {\n title: title || 'Slack thread',\n externalSource: { ...slackThreadSource(thread, slackTeamId(message)), ...(url ? { url } : {}) },\n stages: ['execute'],\n ...(session ? { sessions: { chat: session } } : {}),\n },\n });\n } catch (error) {\n console.warn('[slack] work-item creation failed for thread', thread.id, error);\n }\n}\n\nfunction createNewSessionChatHandler(deps: SlackChannelDeps): ChannelHandler {\n const { workItems } = deps;\n return async (thread, message, defaultHandler, ctx) => {\n // Gate on the sender having linked their Slack account to a Mastra tenant.\n // Unlinked → post the ephemeral Connect card and stop; no session/run is\n // created (which would otherwise be tenant-less and fail credential\n // resolution). This handler is the only gate — core dispatches whatever\n // reaches it — so every slot that can start a run must call it.\n const gate = await gateDispatch(thread, message, deps, ctx);\n if (!gate) return;\n\n // A mention on a not-yet-subscribed thread is a NEW session. The\n // default handler auto-subscribes, so once subscribed this is a\n // follow-up mention — don't re-announce.\n const isNewSession = !(await thread.isSubscribed());\n\n // Run the framework handler first so the internal Mastra thread and\n // controller session are created before we build the deep link.\n await defaultHandler(thread, message);\n\n if (!isNewSession) return;\n\n // The internal-thread lookup and deep-link are needed by BOTH the\n // announcement card AND work-item creation, so they run BEFORE the\n // card-only `MASTRACODE_PUBLIC_URL` gate — a deployment without a public\n // origin should still create board cards, just without a clickable link.\n const internalThread = await findInternalThread(ctx.mastra, thread);\n if (!internalThread) {\n console.warn('[onMention] no internal thread found for', thread.id);\n return;\n }\n\n // When the sender routed to a factory we know exactly which workspace the\n // session belongs to — deep-link straight into it. A repo-backed thread's\n // resourceId IS the Factory user-session id, so the link lands on the same\n // route a web-started run navigates to; chat-only threads keep the literal\n // `channel` segment (the real resource rides the `?resourceId=` override).\n // Unrouted senders fall back to the factory-agnostic /threads/ redirect.\n // One predicate drives both the path segment and the query param, so the\n // two can never disagree about what the URL already carries.\n const isChatOnly = internalThread.resourceId.startsWith('channel:');\n const workspaceSegment = isChatOnly ? 'channel' : encodeURIComponent(internalThread.resourceId);\n const threadPath = gate.routed\n ? `/factories/${encodeURIComponent(gate.routed.factoryProjectId)}/workspaces/${workspaceSegment}/threads/${encodeURIComponent(internalThread.id)}`\n : `/threads/${internalThread.id}`;\n\n // The param is an override for a URL that can't otherwise name its\n // resource. A routed repo-backed thread already spells the resourceId out\n // as its workspace segment, so appending it again is duplication the app\n // ignores. Chat-only threads need it (their segment is the literal string\n // `channel`), and so does the unrouted fallback, which has no workspace\n // segment at all — `ChannelThreadRedirect` forwards the search through.\n //\n // One shared deep-link: the card's button and the work-item `url` read the\n // SAME value so they can never drift. Undefined without a public origin —\n // the card is then skipped, but the work item is still created (url omitted).\n const needsResourceParam = isChatOnly || !gate.routed;\n const deepLink = process.env.MASTRACODE_PUBLIC_URL\n ? needsResourceParam\n ? `${process.env.MASTRACODE_PUBLIC_URL}${threadPath}?resourceId=${encodeURIComponent(internalThread.resourceId)}`\n : `${process.env.MASTRACODE_PUBLIC_URL}${threadPath}`\n : undefined;\n\n // A dispatched, routed new-session thread becomes a Work-board card in\n // Building. Only routed senders (linked → factory) have the org/user/factory\n // a work item needs. Bind the repo-backed Factory session under the `chat`\n // role; a chat-only `channel:` resourceId is NOT a session id, so bind\n // nothing rather than a bad id. Best-effort (the helper swallows failures).\n if (workItems && gate.routed?.slackWorkItemsEnabled) {\n const session = isChatOnly\n ? undefined\n : { sessionId: internalThread.resourceId, branch: threadBranch(thread.id), threadId: internalThread.id };\n await upsertThreadWorkItem({\n workItems,\n thread,\n message,\n link: gate.routed.link,\n factoryProjectId: gate.routed.factoryProjectId,\n session,\n url: deepLink,\n });\n }\n\n // The announcement card is only useful with a public origin to deep-link\n // to — otherwise the link would be `undefined/threads/...`. Without one the\n // session (and now the work item) still exist; we just skip the broken card.\n if (!deepLink) return;\n\n await thread.post(\n Card({\n title: 'New session started',\n children: [Actions([LinkButton({ url: deepLink, label: 'View session' })])],\n }),\n );\n };\n}\n\n/**\n * An `aside` never reaches the agent, but it is still part of the conversation:\n * record it on the card the thread created. The link is resolved here rather\n * than through `gateDispatch`, which posts a Connect card an aside must not\n * trigger. Best-effort — nothing here may surface in the Slack thread.\n */\nasync function ingestAside(\n thread: HandlerThread,\n message: HandlerMessage,\n { feed, workItems, accountLinks }: SlackChannelDeps,\n): Promise<void> {\n if (!feed || !workItems) return;\n const body = resolveEmojiShortcodes(message.text.replace(/^aside\\b[,:]?\\s*/i, '').trim());\n if (!body) return;\n try {\n const teamId = slackTeamId(message);\n const workItem = await findThreadWorkItem(workItems, thread, teamId);\n if (!workItem) return;\n\n const external: FactoryActorExternalIdentity = {\n platform: thread.adapter.name,\n ...(teamId ? { teamId } : {}),\n messageId: message.id,\n userId: message.author.userId,\n userName: message.author.userName,\n fullName: message.author.fullName,\n isBot: message.author.isBot,\n };\n const link =\n accountLinks && teamId\n ? await accountLinks.getAccountLink({\n platform: external.platform,\n externalTeamId: teamId,\n externalUserId: external.userId,\n })\n : null;\n\n const result = await feed.createComment({\n orgId: workItem.orgId,\n workItemId: workItem.id,\n author: actorFromChannelAuthor(external, link),\n body,\n occurredAt: message.metadata.dateSent,\n externalSource: slackCommentSource(thread.id, message.id, teamId),\n });\n if (result.status !== 'created') {\n console.warn('[slack] aside not ingested', { thread: thread.id, messageTs: message.id, status: result.status });\n }\n } catch (error) {\n // Slack was acked before this handler ran, so a lost aside is never redelivered.\n console.warn('[slack] aside ingest failed', { thread: thread.id, messageTs: message.id, error });\n }\n}\n\nexport const createHandlers = (deps: SlackChannelDeps): ChannelHandlers => {\n const newSessionChatHandler = createNewSessionChatHandler(deps);\n\n return {\n onSubscribedMessage: async (thread, message, defaultHandler, ctx) => {\n // `aside` as its own leading word lets humans talk in a subscribed\n // thread without the bot replying. Word boundary so messages that\n // merely start with \"aside...\" (e.g. \"asides can wait\") still route.\n if (/^aside\\b/i.test(message.text)) return ingestAside(thread, message, deps);\n // A subscribed follow-up from an unlinked sender must not run either\n // (e.g. the link was removed mid-conversation), and it must still\n // resolve a factory (e.g. the default was cleared or its factory\n // deleted mid-conversation).\n const gate = await gateDispatch(thread, message, deps, ctx);\n if (!gate) return;\n await defaultHandler(thread, message);\n },\n onMention: newSessionChatHandler,\n onDirectMessage: newSessionChatHandler,\n };\n};\n\n/** Slack app credentials, passed in explicitly rather than read from env here. */\ninterface SlackCredentials {\n clientId?: string;\n clientSecret?: string;\n signingSecret: string;\n botToken?: string;\n}\n\nexport function createSlackChannelsConfig(deps: SlackChannelDeps & { slack: SlackCredentials }): FactoryChannelsConfig {\n // Per HTTP attempt, not per call: the WebClient's own retry policy still\n // waits out a 429. Without it a dead socket hangs a post forever.\n const adapter = createSlackAdapter({ ...deps.slack, webClientOptions: { timeout: SLACK_REQUEST_TIMEOUT_MS } });\n const slack =\n deps.adapterOptions?.streaming === false\n ? { adapter, ...deps.adapterOptions }\n : { adapter, ...deps.adapterOptions, streaming: deps.adapterOptions?.streaming ?? true };\n\n return {\n adapters: { slack },\n handlers: createHandlers(deps),\n // New linked+repo-backed threads own a Factory user-session id as their\n // resourceId, which is what makes the controller session repo-backed.\n resolveResourceId: createChannelResourceIdResolver(deps),\n resolveThreadId: resolveChannelThreadId,\n resolveSession: createChannelSessionResolver(deps),\n // Those sessions are created by the channel machinery, not the web kickoff,\n // so this is where they pick up the factory's configuration.\n onSessionStart: createChannelSessionStartHook(deps),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slack.js","names":[],"sources":["../../../src/integrations/slack/slack.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type {\n ChannelHandler,\n ChannelHandlerContext,\n ChannelHandlers,\n ChannelSessionResolve,\n ChannelSessionStart,\n ResolveResourceId,\n ResolveThreadId,\n} from '@mastra/core/channels';\nimport type { Mastra } from '@mastra/core/mastra';\nimport { createSlackAdapter } from '@mastra/slack';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\nimport { Card, CardText, Actions, LinkButton } from 'chat';\n\nimport {\n createSourceControlSessionLookup,\n hydrateFactorySession,\n resolveFactoryDefaultModelId,\n resolveFactoryProjectForSession,\n resolveFactorySourceControl,\n resolveFactorySourceRepository,\n} from '../../session/factory-session.js';\nimport { applyPersonalMemorySettings } from '../../session/memory-settings-hydration.js';\nimport { readRequestContextOrgId, seedSessionOrg } from '../../session/org-seed.js';\nimport type {\n ChannelAccountLink,\n ChannelAccountLinkKey,\n ChannelIdentityStorage,\n} from '../../storage/domains/channel-identity/base.js';\nimport type { FactoryActorExternalIdentity } from '../../storage/domains/comments/actor.js';\nimport { actorFromChannelAuthor } from '../../storage/domains/comments/actor.js';\nimport type { CommentsDomain } from '../../storage/domains/comments/domain.js';\nimport type { MemorySettingsStorage } from '../../storage/domains/memory-settings/base.js';\nimport type { ModelPacksStorage } from '../../storage/domains/model-packs/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport type { ExternalWorkItemSource, WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport type { FactoryChannelsConfig } from '../base.js';\n\nimport { resolveEmojiShortcodes } from './emoji.js';\nimport { slackCommentSource } from './feed-publisher.js';\n\n// Derive the thread/message types from the core handler signature rather than\n// importing them from `chat` directly: mc-web can resolve a different `chat`\n// version than @mastra/core, and the two `Thread`/`Message` declarations are\n// structurally incompatible (private fields). Using the handler's own types\n// keeps everything on one version.\ntype HandlerThread = Parameters<ChannelHandler>[0];\ntype HandlerMessage = Parameters<ChannelHandler>[1];\n\nconst SLACK_REQUEST_TIMEOUT_MS = 15_000;\nconst MAX_WORK_ITEM_TITLE_CHARS = 80;\nconst graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n\n/**\n * A card is titled with what the sender wrote. Cutting counts graphemes, not\n * code points: `👍🏼` is a base plus a skin-tone modifier, and a cut between\n * them leaves the wrong emoji.\n */\nfunction workItemTitleFrom(messageText: string): string {\n const resolved = resolveEmojiShortcodes(messageText);\n const characters = [...graphemes.segment(resolved)].map(({ segment }) => segment);\n if (characters.length <= MAX_WORK_ITEM_TITLE_CHARS) return resolved;\n return `${characters.slice(0, MAX_WORK_ITEM_TITLE_CHARS - 1).join('')}…`;\n}\n\n/** Dependencies the Slack channel handlers close over, injected from the web entry. */\ninterface SlackChannelDeps {\n /**\n * The factory's reverse-index store mapping a Slack sender to a Mastra\n * tenant. When provided, inbound messages from an unlinked sender are not\n * dispatched — the run only proceeds (with the sender's tenant stamped on\n * the request context) once they've linked their account. Unlinked senders\n * get an ephemeral \"connect your account\" card instead.\n */\n accountLinks?: ChannelIdentityStorage;\n /**\n * Factory projects domain. When provided (alongside `accountLinks`), a\n * linked sender's run must also resolve to a Factory project before it\n * dispatches: their link's default factory, else their tenant's only\n * factory (stamped back onto the link), else an ephemeral \"pick a default\n * factory\" card and no run. Unset → no factory routing (runs dispatch as\n * before).\n */\n projects?: FactoryProjectsStorage;\n /**\n * Storage handle of the integration that owns source control\n * (`IntegrationContext.storage.sourceControlOwner`). Used to make new Slack\n * threads repo-backed: when the sender is linked and their factory has a\n * repository, the thread's resourceId becomes a Factory user-session id (repo\n * cloned on a `slack/{threadTs}` branch) instead of the chat-only\n * `channel:...` id. It also lets a started session read back the project it\n * belongs to. Nothing here is provider-specific — the connection is matched\n * by the handle's own `integrationId`. Absent (no source-control integration\n * registered) → chat-only sessions as before.\n */\n sourceControl?: SourceControlStorageHandle;\n /** Registered source-control partitions used to select the provider linked to each Factory project. */\n sourceControls?: readonly SourceControlStorageHandle[];\n /**\n * Observational-memory settings domain. When provided, a repo-backed session\n * adopts its factory project's shared memory settings on start, matching the\n * web kickoff — and the linked sender's own settings win over them for every\n * knob the sender has saved.\n */\n memorySettings?: MemorySettingsStorage;\n /**\n * Model-packs domain. When provided, a new repo-backed session starts on the\n * linked sender's active pack build model — the model that user picked for\n * themselves — before the factory's shared default. Read once, when the\n * thread's build model is first chosen; the choice is persisted on the thread\n * and never re-resolved. Unset → the factory default, as before.\n */\n modelPacks?: ModelPacksStorage;\n /**\n * Factory work-items domain. When provided, a dispatched new-session thread\n * (DM or mention) upserts a Work-board card in Building (`execute`) carrying\n * the Slack thread as its external source and binding the repo-backed\n * session. Best-effort — a failure never blocks the run. Unset → no card.\n */\n workItems?: WorkItemsStorage;\n /**\n * Work-item feed. With it (and `workItems`), an `aside` lands as a comment on\n * the card the thread created. Unset → asides stay ignored, as before.\n */\n feed?: Pick<CommentsDomain, 'createComment'>;\n /** Overrides applied to the Slack channel adapter entry. */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nfunction configuredSourceControls(deps: SlackChannelDeps): readonly SourceControlStorageHandle[] {\n return deps.sourceControls ?? (deps.sourceControl ? [deps.sourceControl] : []);\n}\n\n/**\n * Read the Slack team id off a raw platform payload (Events API envelope or\n * slash-command body — both carry `team_id`), duck-typed to build the\n * workspace-scoped account-link key.\n */\nfunction rawTeamId(rawPayload: unknown): string | undefined {\n if (!rawPayload || typeof rawPayload !== 'object') return undefined;\n const raw = rawPayload as { team_id?: unknown; team?: unknown };\n if (typeof raw.team_id === 'string' && raw.team_id) return raw.team_id;\n if (typeof raw.team === 'string' && raw.team) return raw.team;\n if (raw.team && typeof raw.team === 'object') {\n const id = (raw.team as { id?: unknown }).id;\n if (typeof id === 'string' && id) return id;\n }\n return undefined;\n}\n\n/**\n * The Slack team id survives onto a normalized chat Message only on\n * `message.raw` (the Slack Events API envelope).\n */\nfunction slackTeamId(message: HandlerMessage): string | undefined {\n return rawTeamId(message.raw);\n}\n\n/**\n * Resolve the web-UI origin for links humans open in a browser (Connect card,\n * session deep links). Prefers `MASTRACODE_PUBLIC_URL` — the origin auth\n * cookies and OAuth redirect allow-lists are registered against — over the\n * channels tunnel, which only Slack's servers need to reach.\n */\nfunction webPublicUrl(): string | undefined {\n return process.env.MASTRACODE_PUBLIC_URL ?? process.env.MASTRACODE_CHANNELS_PUBLIC_URL;\n}\n\n/** Outcome of the sender-link gate for one inbound message. */\ntype LinkedSenderResult =\n /** Gating not configured — dispatch as before account linking existed. */\n | { status: 'ungated' }\n /** Sender unlinked — Connect card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** Sender linked — their tenant plus the sender key the link lives under. */\n | { status: 'linked'; link: ChannelAccountLink; key: ChannelAccountLinkKey };\n\n/**\n * Resolve the sender's account link, posting an ephemeral \"connect your\n * account\" card (visible only to the sender) linking into the web UI's\n * Slack-connect flow when they're unlinked.\n */\nexport async function resolveLinkedSender({\n thread,\n message,\n accountLinks,\n}: {\n thread: HandlerThread;\n message: HandlerMessage;\n accountLinks?: ChannelIdentityStorage;\n}): Promise<LinkedSenderResult> {\n if (!accountLinks) return { status: 'ungated' };\n const platform = thread.adapter.name;\n const externalUserId = message.author.userId;\n const externalTeamId = slackTeamId(message);\n // Without a team id we can't identify the workspace-scoped link; treat as\n // unlinked so a run never proceeds tenant-less.\n const key = externalTeamId ? { platform, externalTeamId, externalUserId } : undefined;\n const link = key ? await accountLinks.getAccountLink(key) : null;\n if (link && key) return { status: 'linked', link, key };\n\n const publicUrl = webPublicUrl();\n // A public origin is all the card needs. The link carries no identity: the\n // web app authenticates the visitor, then Slack's OIDC flow proves which\n // Slack account they control. Without an origin, still block, just no card.\n if (publicUrl) {\n await thread.postEphemeral(message.author, buildConnectCard(publicUrl), { fallbackToDM: true });\n }\n return { status: 'blocked' };\n}\n\n/**\n * The \"connect your account\" card. The link is deliberately identity-free —\n * `/connect/slack` sends the visitor to Connections, where \"Connect Slack\"\n * runs the OIDC flow and Slack itself asserts the (team, user) pair.\n */\nfunction buildConnectCard(publicUrl: string) {\n return Card({\n title: 'Connect your account',\n children: [\n CardText('Connect your account to use this agent.'),\n Actions([\n LinkButton({\n url: `${publicUrl}/connect/slack`,\n label: 'Connect account',\n }),\n ]),\n ],\n });\n}\n\n/** Outcome of factory routing for one linked sender's inbound message. */\ntype FactoryRouteResult =\n /** Factory routing not configured — dispatch without a factory. */\n | { status: 'ungated' }\n /** No factory resolved — prompt card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** The Factory project this sender's runs route to. */\n | { status: 'resolved'; factoryProjectId: string; slackWorkItemsEnabled: boolean };\n\n/**\n * Decide which Factory project a linked sender's run belongs to:\n *\n * 1. The link's `defaultFactoryProjectId`, when it still exists (a stale id —\n * deleted factory — falls through as if unset).\n * 2. Else, the tenant's only factory, stamped back onto the link so it shows\n * up (and stays editable) in Connected Accounts settings.\n * 3. Else — zero or several factories — an ephemeral \"pick a default factory\"\n * card deep-linking to settings, and the run is blocked.\n */\nexport async function resolveFactoryForLink({\n thread,\n message,\n link,\n key,\n accountLinks,\n projects,\n}: {\n thread: HandlerThread;\n message: HandlerMessage;\n link: ChannelAccountLink;\n key: ChannelAccountLinkKey;\n accountLinks: ChannelIdentityStorage;\n projects?: FactoryProjectsStorage;\n}): Promise<FactoryRouteResult> {\n if (!projects) return { status: 'ungated' };\n // Factories are org-scoped; a personal account (no org) has none and lands\n // on the prompt below.\n const orgId = link.orgId ?? '';\n\n if (link.defaultFactoryProjectId) {\n const existing = await projects.get({ orgId, id: link.defaultFactoryProjectId });\n if (existing) {\n return {\n status: 'resolved',\n factoryProjectId: existing.id,\n slackWorkItemsEnabled: existing.slackWorkItemsEnabled,\n };\n }\n }\n\n const factories = orgId ? await projects.list({ orgId }) : [];\n if (factories.length === 1) {\n const only = factories[0]!;\n await accountLinks.setDefaultFactory({ ...key, userId: link.userId, factoryProjectId: only.id });\n return {\n status: 'resolved',\n factoryProjectId: only.id,\n slackWorkItemsEnabled: only.slackWorkItemsEnabled,\n };\n }\n\n const publicUrl = webPublicUrl();\n if (publicUrl) {\n await thread.postEphemeral(\n message.author,\n Card({\n title: 'Pick a default factory',\n children: [\n CardText(\n factories.length === 0\n ? 'Your account has no factory yet. Create one in the web app, then message me again.'\n : 'Your account has several factories. Pick which one Slack sessions should go to, then message me again.',\n ),\n Actions([\n LinkButton({\n url: `${publicUrl}/settings/connections`,\n label: 'Open settings',\n }),\n ]),\n ],\n }),\n { fallbackToDM: true },\n );\n }\n return { status: 'blocked' };\n}\n\n/**\n * Deterministic per-thread branch name: `slack/{threadTs}` with characters\n * outside the sandbox git-ref allow-list (`[A-Za-z0-9_./-]`, and `.` for\n * readability) mapped to `-`. `thread.id` is `{channelId}:{threadTs}`\n * (platform-prefixed on handler threads) — the trailing segment is the ts.\n *\n * Top-level DM and channel conversations use the empty-threadTs thread form,\n * so the trailing segment can be empty; a bare `slack/` is not a valid git\n * ref. Fall back to the last non-empty segment (the channel id): one\n * deterministic branch per top-level conversation.\n */\nfunction threadBranch(threadId: string): string {\n const segments = threadId.split(':');\n const tail = segments.findLast(segment => segment.length > 0) ?? threadId;\n return `slack/${tail.replace(/[^A-Za-z0-9_/-]/g, '-')}`;\n}\n\n/**\n * Resolve the resourceId for a NEW Slack channel thread. A linked sender whose\n * factory has a repository gets a Factory user-session id — the controller\n * session then materializes the repo sandbox via the factory's dynamic\n * workspace (clone + PAT), the session shows up in the web Sessions list, and\n * View Session deep-links land on the normal workspace route. Everything else\n * (unlinked, unrouted, repo-less, or no source control) keeps the chat-only\n * `defaultResourceId`.\n *\n * Pure lookups only — cards for unlinked/unrouted senders are the dispatch\n * gate's job; this hook must never post.\n */\nexport function createChannelResourceIdResolver(deps: SlackChannelDeps): ResolveResourceId {\n const { accountLinks, projects } = deps;\n const sourceControls = configuredSourceControls(deps);\n return async ({ platform, thread, message }) => {\n // NOT the hook's `defaultResourceId`: configuring a custom resolver\n // bypasses AgentControllerChannels' own `channel:{thread.id}` derivation\n // (agent-controller-channels.ts `resolveChannelResourceId`), and the base\n // default is the per-USER memory key. Chat-only fallbacks must stay\n // per-thread, so reproduce the controller default here.\n const chatOnlyResourceId = `channel:${thread.id}`;\n if (!accountLinks || !projects || sourceControls.length === 0) return chatOnlyResourceId;\n try {\n const externalTeamId = rawTeamId(message.raw);\n if (!externalTeamId) return chatOnlyResourceId;\n const link = await accountLinks.getAccountLink({\n platform,\n externalTeamId,\n externalUserId: message.author.userId,\n });\n if (!link) return chatOnlyResourceId;\n\n // Same chain as `resolveFactoryForLink`, minus prompts/stamping: the\n // dispatch gate has already run (and stamped a lone factory) by the\n // time a new thread is created, so this is a read-only re-resolve.\n const orgId = link.orgId ?? '';\n let factoryProjectId: string | undefined;\n if (link.defaultFactoryProjectId && (await projects.get({ orgId, id: link.defaultFactoryProjectId }))) {\n factoryProjectId = link.defaultFactoryProjectId;\n } else if (orgId) {\n const factories = await projects.list({ orgId });\n if (factories.length === 1) factoryProjectId = factories[0]!.id;\n }\n if (!factoryProjectId) return chatOnlyResourceId;\n\n const sourceControl = await resolveFactorySourceControl({ sourceControls, orgId, factoryProjectId });\n if (!sourceControl) return chatOnlyResourceId;\n const repo = await resolveFactorySourceRepository({ sourceControl, orgId, factoryProjectId });\n if (!repo.found) return chatOnlyResourceId;\n\n const branch = threadBranch(thread.id);\n // Attributed to the Slack sender, not to whoever connected the repository:\n // unlike an autonomous rule run, a Slack thread has a real interactive user.\n const existing = await sourceControl.sessions.getForBranch({\n projectRepositoryId: repo.projectRepositoryId,\n userId: link.userId,\n branch,\n });\n if (existing) return existing.sessionId;\n const session = await sourceControl.sessions.create({\n sessionId: randomUUID(),\n projectRepositoryId: repo.projectRepositoryId,\n orgId,\n userId: link.userId,\n branch,\n baseBranch: repo.baseBranch,\n // DMs are the only private origin; channel threads are org-visible.\n visibility: thread.isDM ? 'private' : 'org',\n });\n return session.sessionId;\n } catch (error) {\n // Fall back to a chat-only session rather than dropping the message.\n console.warn('[slack] repo-backed session resolution failed for thread', thread.id, error);\n return chatOnlyResourceId;\n }\n };\n}\n\n/**\n * Thread id for a NEW Slack channel thread. Repo-backed threads take the\n * user-session id AS their thread id, matching the web convention\n * (FactoryStartCoordinator seeds threads with threadId = sessionId) so\n * `/workspaces/{sessionId}/threads/{sessionId}` resolves Slack-created\n * sessions exactly like web-created ones — no `?resourceId=` override needed.\n * Chat-only threads keep the default random id: their `channel:...`\n * resourceId is a memory key, not a unique thread id.\n */\nexport const resolveChannelThreadId: ResolveThreadId = ({ resourceId, defaultThreadId }) =>\n resourceId.startsWith('channel:') ? defaultThreadId : resourceId;\n\n/** Create channel sessions with their Factory ownership present in initial controller state. */\nexport function createChannelSessionResolver(deps: SlackChannelDeps): ChannelSessionResolve {\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ controller, thread, requestContext }) => {\n const sourceControl = thread.resourceId.startsWith('channel:')\n ? null\n : await sourceControlSessions.getSourceControlBySessionId(thread.resourceId);\n const owner = sourceControl\n ? await resolveFactoryProjectForSession({ sourceControl, sessionId: thread.resourceId })\n : null;\n return controller.createSession({\n id: thread.resourceId,\n ownerId: controller.id,\n resourceId: thread.resourceId,\n requestContext,\n ...(owner ? { tags: { factoryProjectId: owner.factoryProjectId } } : {}),\n });\n };\n}\n\n/**\n * Apply the factory's configuration to a Slack-created session the first time\n * its thread reaches the controller.\n *\n * Without this a Slack session runs on the SDK's built-in mode default\n * (`openai/gpt-5.5`), so a factory configured for any other provider fails every\n * message with a missing-credentials error. The web kickoff has always applied\n * the factory default; this brings Slack to the same footing.\n *\n * Only repo-backed threads are configured. Their resourceId IS the Factory\n * session id, which the source-control rows turn back into a project — a\n * chat-only `channel:...` id names no project, so there is nothing to read.\n *\n * The model this session starts on is picked here, once, in the order of who\n * chose it: the linked sender's active model pack, else the factory project's\n * default, else the SDK's built-in mode default. The choice is persisted on the\n * thread as `modeModelId_<mode>`, so it outlives the process that made it.\n *\n * Observational memory is configured here too, in the same order of who chose\n * it: the project's shared settings first, then the linked sender's own row,\n * which wins for every knob they have saved. The pair is re-applied on every\n * start rather than once — memory settings are stored preference, not a choice\n * made on this thread, and a restarted process re-resolves the project's row\n * before this hook runs.\n *\n * The model resolution is skipped on a session whose mode already has a model\n * persisted on the thread. That is the durable record of a deliberate choice —\n * either an earlier start or a user's own switch — and re-applying a preference\n * over it would undo the user's selection every time the process restarts.\n * Memory settings have no such per-thread record, so they are re-applied on\n * every start.\n */\nexport function createChannelSessionStartHook(deps: SlackChannelDeps): ChannelSessionStart {\n const { projects, memorySettings, modelPacks } = deps;\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ session, thread, requestContext }) => {\n // Seed the tenant org above every guard below. `gateDispatch` stamps it on\n // the message's request context before the session exists, so this needs no\n // storage read — which matters, because the model lookups below deliberately\n // skip storage on a restarted session. A channel-only thread and a thread\n // whose dispatch was ungated both land here with no org and are marked\n // unresolved rather than being left to look like a local session.\n await seedSessionOrg(session, readRequestContextOrgId(requestContext));\n\n if (!projects) return;\n if (thread.resourceId.startsWith('channel:')) return;\n\n const sourceControl = await sourceControlSessions.getSourceControlBySessionId(thread.resourceId);\n if (!sourceControl) return;\n const owner = await resolveFactoryProjectForSession({ sourceControl, sessionId: thread.resourceId });\n if (!owner) return;\n\n // Repo-backed Slack sessions are factory sessions: stamp the owning\n // project onto controller state so downstream reads (org-first credential\n // resolution, authority gates) recognize them, same as board runs.\n await session.state.set({ factoryProjectId: owner.factoryProjectId });\n // Route the org through the seed helper rather than stamping it directly:\n // an ungated dispatch marked this session unresolved above, and owner\n // recovery is the resolution that has to clear that marker with it.\n await seedSessionOrg(session, owner.orgId);\n\n const modeModelKey = `modeModelId_${session.mode.get()}`;\n if (!(await session.thread.getSetting({ key: modeModelKey }))) {\n const factoryModelId = await resolveFactoryDefaultModelId(projects, owner.factoryProjectId);\n const userModelId = await resolveActivePackBuildModel(modelPacks, owner);\n\n await hydrateFactorySession(session, {\n orgId: owner.orgId,\n factoryProjectId: owner.factoryProjectId,\n // The FACTORY model drives observational-memory's provider-aware\n // fallback, even when the sender's pack supplies the model the session\n // actually runs — a factory connected only to Anthropic should not\n // observe with an uncredentialed provider.\n defaultModelId: factoryModelId,\n memorySettings,\n });\n\n const selectedModelId = userModelId ?? factoryModelId;\n if (selectedModelId && selectedModelId !== factoryModelId) {\n // The sender's own choice beats the factory's. `switch` applies the model\n // and persists it as this mode's model on the thread in one step — which\n // is what makes the choice outlive this process. A switch that fails is\n // logged by the channel machinery and leaves the factory/SDK model that\n // `hydrateFactorySession` already applied: the message still answers.\n try {\n await session.model.switch({ modelId: selectedModelId });\n } catch (error) {\n console.warn(\"[slack] Failed to apply the sender's model pack model\", {\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n const currentModelId = session.model.get();\n if (currentModelId && !factoryModelId) {\n try {\n await session.model.saveForMode({ modeId: session.mode.get(), modelId: currentModelId });\n } catch (saveError) {\n console.warn(\"[slack] Failed to persist the sender's model pack model\", {\n modelId: currentModelId,\n error: saveError instanceof Error ? saveError.message : String(saveError),\n });\n }\n }\n }\n } else if (!selectedModelId) {\n // Neither preference exists, so the SDK's built-in mode default is this\n // thread's model of record. Pin it too: a later SDK upgrade that moves\n // that default must not silently retarget a thread that already started.\n const currentModelId = session.model.get();\n if (currentModelId) {\n await session.model.saveForMode({ modeId: session.mode.get(), modelId: currentModelId });\n }\n }\n }\n\n // The sender's own observational-memory settings, applied last so they beat\n // the project's — and on EVERY start, not just the first. Unlike the model,\n // this is stored preference rather than a choice made on this thread: a\n // restarted process re-resolves the project row before this hook runs, so\n // skipping it here would quietly put a thread back on the project's OM\n // configuration. Chat-only threads never reach this point.\n await applyPersonalMemorySettings(session, {\n memorySettings,\n orgId: owner.orgId,\n userId: owner.userId,\n });\n };\n}\n\n/**\n * The `build` model of the sender's active model pack — the model that user\n * chose for themselves — or `undefined` when they have no pack.\n *\n * Best-effort by design: an uninitialized model-packs domain, a read failure, or\n * a pack saved without a build model all mean \"no personal preference\", which\n * falls through to the factory default rather than failing the dispatch.\n */\nasync function resolveActivePackBuildModel(\n modelPacks: ModelPacksStorage | undefined,\n owner: { orgId: string; userId: string },\n): Promise<string | undefined> {\n if (!modelPacks) return undefined;\n try {\n const active = await modelPacks.getActive({ orgId: owner.orgId, userId: owner.userId });\n return active?.models.build || undefined;\n } catch (error) {\n console.warn('[slack] model pack lookup failed for a new session', {\n orgId: owner.orgId,\n userId: owner.userId,\n error: error instanceof Error ? error.message : String(error),\n });\n return undefined;\n }\n}\n\n/**\n * The internal Mastra thread the framework created for a channel conversation.\n * The handler's `thread.id` is the platform thread id (e.g. `slack:C123:ts`),\n * NOT the internal UUID — the mapping lives in the stored thread's channel\n * metadata.\n */\nasync function findInternalThread(mastra: Mastra | undefined, thread: HandlerThread) {\n const store = await mastra?.getStorage()?.getStore('memory');\n const { threads } = (await store?.listThreads({\n filter: {\n metadata: {\n channel_platform: thread.adapter.name,\n channel_externalThreadId: thread.id,\n channel_externalChannelId: thread.channelId,\n },\n },\n perPage: 1,\n })) ?? { threads: [] };\n return threads[0];\n}\n\n/**\n * Build the \"new session\" handler for mention / direct-message events. A mention or\n * DM on a not-yet-subscribed thread starts a NEW session; once subscribed, later\n * events are follow-ups and don't re-announce.\n */\n/**\n * Run the account-link + factory-routing gates for one inbound message.\n * Returns `null` when the run must not dispatch (a prompt card was posted\n * where possible); otherwise the dispatch context — with `routed` present\n * only when a linked sender resolved to a factory.\n */\nasync function gateDispatch(\n thread: HandlerThread,\n message: HandlerMessage,\n { accountLinks, projects }: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n): Promise<{\n routed?: { link: ChannelAccountLink; factoryProjectId: string; slackWorkItemsEnabled: boolean };\n} | null> {\n const sender = await resolveLinkedSender({ thread, message, accountLinks });\n if (sender.status === 'blocked') return null;\n // Linked senders must also route to a Factory project before a run starts.\n if (sender.status === 'linked' && accountLinks) {\n // Stamp the tenant on the run's request context — the single seam\n // `resolveCredentialStore` reads to load this sender's model credentials.\n // This belongs to the link, not to the routing: a linked sender whose\n // factory routing comes back `ungated` still exits below and dispatches, so\n // stamping only in the routed branch would silently run them on default\n // credentials.\n ctx.requestContext.set('user', { id: sender.link.userId, organizationId: sender.link.orgId });\n\n const route = await resolveFactoryForLink({ thread, message, ...sender, accountLinks, projects });\n if (route.status === 'blocked') return null;\n if (route.status === 'resolved') {\n return {\n routed: {\n link: sender.link,\n factoryProjectId: route.factoryProjectId,\n slackWorkItemsEnabled: route.slackWorkItemsEnabled,\n },\n };\n }\n }\n return {};\n}\n\n/**\n * A channel id and a `ts` name a thread only inside the workspace that issued\n * them, so the team scopes the card's key — without it two workspaces could\n * hold one key and an aside would land on another tenant's card.\n */\nexport function slackThreadSource(thread: HandlerThread, teamId?: string): ExternalWorkItemSource {\n return {\n integrationId: thread.adapter.name,\n type: 'slack-thread',\n ...(teamId ? { workspaceId: teamId } : {}),\n externalId: thread.id,\n };\n}\n\n/** Cards written before the key carried a team are still keyed bare. */\nasync function findThreadWorkItem(\n workItems: WorkItemsStorage,\n thread: HandlerThread,\n teamId?: string,\n): Promise<WorkItemRow | null> {\n const scoped = await workItems.getBySource(slackThreadSource(thread, teamId));\n if (scoped || !teamId) return scoped;\n return workItems.getBySource(slackThreadSource(thread));\n}\n\n/**\n * Upsert the Work-board card for a dispatched Slack-thread run. Keyed on the\n * thread via `externalSource` — the work-items domain's unique\n * `(factory_project_id, source_key)` index makes repeat messages reuse the\n * same card, and `reuseMode: 'preserve'` keeps a card a human already dragged\n * across stages untouched. The card lands in Building (`execute`) for every\n * dispatched thread (DM or mention) — there is deliberately no per-origin\n * stage split; smart routing is a follow-up.\n *\n * The session id / branch / threadId and the workspace deep-link are resolved\n * by the caller (which already looked up the internal thread), so this helper\n * just shapes and writes. Best-effort: the run is already dispatched, so a\n * failure logs instead of throwing — work-item creation must never abort a Slack run.\n */\nexport async function upsertThreadWorkItem({\n workItems,\n thread,\n message,\n link,\n factoryProjectId,\n session,\n url,\n}: {\n workItems: WorkItemsStorage;\n thread: HandlerThread;\n message: HandlerMessage;\n link: ChannelAccountLink;\n factoryProjectId: string;\n /**\n * The repo-backed Factory session to bind under the `chat` role, or\n * `undefined` for a chat-only thread (no Factory session to bind).\n */\n session?: { sessionId: string; branch: string; threadId: string };\n /** Workspace deep-link to the running session; omitted when no public URL. */\n url?: string;\n}): Promise<void> {\n try {\n const title = workItemTitleFrom(message.text);\n\n await workItems.upsert({\n orgId: link.orgId ?? '',\n userId: link.userId,\n factoryProjectId,\n reuseMode: 'preserve',\n input: {\n title: title || 'Slack thread',\n externalSource: { ...slackThreadSource(thread, slackTeamId(message)), ...(url ? { url } : {}) },\n stages: ['execute'],\n ...(session ? { sessions: { chat: session } } : {}),\n },\n });\n } catch (error) {\n console.warn('[slack] work-item creation failed for thread', thread.id, error);\n }\n}\n\nfunction createNewSessionChatHandler(deps: SlackChannelDeps): ChannelHandler {\n const { workItems } = deps;\n return async (thread, message, defaultHandler, ctx) => {\n // Gate on the sender having linked their Slack account to a Mastra tenant.\n // Unlinked → post the ephemeral Connect card and stop; no session/run is\n // created (which would otherwise be tenant-less and fail credential\n // resolution). This handler is the only gate — core dispatches whatever\n // reaches it — so every slot that can start a run must call it.\n const gate = await gateDispatch(thread, message, deps, ctx);\n if (!gate) return;\n\n // A mention on a not-yet-subscribed thread is a NEW session. The\n // default handler auto-subscribes, so once subscribed this is a\n // follow-up mention — don't re-announce.\n const isNewSession = !(await thread.isSubscribed());\n\n // Run the framework handler first so the internal Mastra thread and\n // controller session are created before we build the deep link.\n await defaultHandler(thread, message);\n\n if (!isNewSession) return;\n\n // The internal-thread lookup and deep-link are needed by BOTH the\n // announcement card AND work-item creation, so they run BEFORE the\n // card-only `MASTRACODE_PUBLIC_URL` gate — a deployment without a public\n // origin should still create board cards, just without a clickable link.\n const internalThread = await findInternalThread(ctx.mastra, thread);\n if (!internalThread) {\n console.warn('[onMention] no internal thread found for', thread.id);\n return;\n }\n\n // When the sender routed to a factory we know exactly which workspace the\n // session belongs to — deep-link straight into it. A repo-backed thread's\n // resourceId IS the Factory user-session id, so the link lands on the same\n // route a web-started run navigates to; chat-only threads keep the literal\n // `channel` segment (the real resource rides the `?resourceId=` override).\n // Unrouted senders fall back to the factory-agnostic /threads/ redirect.\n // One predicate drives both the path segment and the query param, so the\n // two can never disagree about what the URL already carries.\n const isChatOnly = internalThread.resourceId.startsWith('channel:');\n const workspaceSegment = isChatOnly ? 'channel' : encodeURIComponent(internalThread.resourceId);\n const threadPath = gate.routed\n ? `/factories/${encodeURIComponent(gate.routed.factoryProjectId)}/workspaces/${workspaceSegment}/threads/${encodeURIComponent(internalThread.id)}`\n : `/threads/${internalThread.id}`;\n\n // The param is an override for a URL that can't otherwise name its\n // resource. A routed repo-backed thread already spells the resourceId out\n // as its workspace segment, so appending it again is duplication the app\n // ignores. Chat-only threads need it (their segment is the literal string\n // `channel`), and so does the unrouted fallback, which has no workspace\n // segment at all — `ChannelThreadRedirect` forwards the search through.\n //\n // One shared deep-link: the card's button and the work-item `url` read the\n // SAME value so they can never drift. Undefined without a public origin —\n // the card is then skipped, but the work item is still created (url omitted).\n const needsResourceParam = isChatOnly || !gate.routed;\n const deepLink = process.env.MASTRACODE_PUBLIC_URL\n ? needsResourceParam\n ? `${process.env.MASTRACODE_PUBLIC_URL}${threadPath}?resourceId=${encodeURIComponent(internalThread.resourceId)}`\n : `${process.env.MASTRACODE_PUBLIC_URL}${threadPath}`\n : undefined;\n\n // A dispatched, routed new-session thread becomes a Work-board card in\n // Building. Only routed senders (linked → factory) have the org/user/factory\n // a work item needs. Bind the repo-backed Factory session under the `chat`\n // role; a chat-only `channel:` resourceId is NOT a session id, so bind\n // nothing rather than a bad id. Best-effort (the helper swallows failures).\n if (workItems && gate.routed?.slackWorkItemsEnabled) {\n const session = isChatOnly\n ? undefined\n : { sessionId: internalThread.resourceId, branch: threadBranch(thread.id), threadId: internalThread.id };\n await upsertThreadWorkItem({\n workItems,\n thread,\n message,\n link: gate.routed.link,\n factoryProjectId: gate.routed.factoryProjectId,\n session,\n url: deepLink,\n });\n }\n\n // The announcement card is only useful with a public origin to deep-link\n // to — otherwise the link would be `undefined/threads/...`. Without one the\n // session (and now the work item) still exist; we just skip the broken card.\n if (!deepLink) return;\n\n await thread.post(\n Card({\n title: 'New session started',\n children: [Actions([LinkButton({ url: deepLink, label: 'View session' })])],\n }),\n );\n };\n}\n\n/**\n * An `aside` never reaches the agent, but it is still part of the conversation:\n * record it on the card the thread created. The link is resolved here rather\n * than through `gateDispatch`, which posts a Connect card an aside must not\n * trigger. Best-effort — nothing here may surface in the Slack thread.\n */\nasync function ingestAside(\n thread: HandlerThread,\n message: HandlerMessage,\n { feed, workItems, accountLinks }: SlackChannelDeps,\n): Promise<void> {\n if (!feed || !workItems) return;\n const body = resolveEmojiShortcodes(message.text.replace(/^aside\\b[,:]?\\s*/i, '').trim());\n if (!body) return;\n try {\n const teamId = slackTeamId(message);\n const workItem = await findThreadWorkItem(workItems, thread, teamId);\n if (!workItem) return;\n\n const external: FactoryActorExternalIdentity = {\n platform: thread.adapter.name,\n ...(teamId ? { teamId } : {}),\n messageId: message.id,\n userId: message.author.userId,\n userName: message.author.userName,\n fullName: message.author.fullName,\n isBot: message.author.isBot,\n };\n const link =\n accountLinks && teamId\n ? await accountLinks.getAccountLink({\n platform: external.platform,\n externalTeamId: teamId,\n externalUserId: external.userId,\n })\n : null;\n\n const result = await feed.createComment({\n orgId: workItem.orgId,\n workItemId: workItem.id,\n author: actorFromChannelAuthor(external, link),\n body,\n occurredAt: message.metadata.dateSent,\n externalSource: slackCommentSource(thread.id, message.id, teamId),\n });\n if (result.status !== 'created') {\n console.warn('[slack] aside not ingested', { thread: thread.id, messageTs: message.id, status: result.status });\n }\n } catch (error) {\n // Slack was acked before this handler ran, so a lost aside is never redelivered.\n console.warn('[slack] aside ingest failed', { thread: thread.id, messageTs: message.id, error });\n }\n}\n\nexport const createHandlers = (deps: SlackChannelDeps): ChannelHandlers => {\n const newSessionChatHandler = createNewSessionChatHandler(deps);\n\n return {\n onSubscribedMessage: async (thread, message, defaultHandler, ctx) => {\n // `aside` as its own leading word lets humans talk in a subscribed\n // thread without the bot replying. Word boundary so messages that\n // merely start with \"aside...\" (e.g. \"asides can wait\") still route.\n if (/^aside\\b/i.test(message.text)) return ingestAside(thread, message, deps);\n // A subscribed follow-up from an unlinked sender must not run either\n // (e.g. the link was removed mid-conversation), and it must still\n // resolve a factory (e.g. the default was cleared or its factory\n // deleted mid-conversation).\n const gate = await gateDispatch(thread, message, deps, ctx);\n if (!gate) return;\n await defaultHandler(thread, message);\n },\n onMention: newSessionChatHandler,\n onDirectMessage: newSessionChatHandler,\n };\n};\n\n/** Slack app credentials, passed in explicitly rather than read from env here. */\ninterface SlackCredentials {\n clientId?: string;\n clientSecret?: string;\n signingSecret: string;\n botToken?: string;\n}\n\nexport function createSlackChannelsConfig(deps: SlackChannelDeps & { slack: SlackCredentials }): FactoryChannelsConfig {\n // Per HTTP attempt, not per call: the WebClient's own retry policy still\n // waits out a 429. Without it a dead socket hangs a post forever.\n const adapter = createSlackAdapter({ ...deps.slack, webClientOptions: { timeout: SLACK_REQUEST_TIMEOUT_MS } });\n const slack =\n deps.adapterOptions?.streaming === false\n ? { adapter, ...deps.adapterOptions }\n : { adapter, ...deps.adapterOptions, streaming: deps.adapterOptions?.streaming ?? true };\n\n return {\n adapters: { slack },\n handlers: createHandlers(deps),\n // New linked+repo-backed threads own a Factory user-session id as their\n // resourceId, which is what makes the controller session repo-backed.\n resolveResourceId: createChannelResourceIdResolver(deps),\n resolveThreadId: resolveChannelThreadId,\n resolveSession: createChannelSessionResolver(deps),\n // Those sessions are created by the channel machinery, not the web kickoff,\n // so this is where they pick up the factory's configuration.\n onSessionStart: createChannelSessionStartHook(deps),\n };\n}\n"],"mappings":"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"}
|
package/dist/routes/surface.d.ts
CHANGED
|
@@ -106,7 +106,7 @@ export declare function buildIntegrationContext(deps: Pick<FactoryApiRoutesDeps,
|
|
|
106
106
|
factoryReady: boolean;
|
|
107
107
|
/** Work-item feed service, so a channel integration can ingest platform messages. */
|
|
108
108
|
feed: CommentsDomain;
|
|
109
|
-
domains: Pick<FactoryApiRoutesDeps['domains'], 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings'>;
|
|
109
|
+
domains: Pick<FactoryApiRoutesDeps['domains'], 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings' | 'modelPacks'>;
|
|
110
110
|
/**
|
|
111
111
|
* Stable id of the registered source-control-owning integration (today:
|
|
112
112
|
* `'github'` when registered). Every call site must derive and pass it so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../src/routes/surface.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/surface.js
CHANGED
|
@@ -185,7 +185,8 @@ function buildIntegrationContext(deps, integrationId) {
|
|
|
185
185
|
projects: deps.domains.projects,
|
|
186
186
|
intake: deps.domains.intake,
|
|
187
187
|
channelIdentity: deps.domains.channelIdentity,
|
|
188
|
-
memorySettings: deps.domains.memorySettings
|
|
188
|
+
memorySettings: deps.domains.memorySettings,
|
|
189
|
+
modelPacks: deps.domains.modelPacks
|
|
189
190
|
},
|
|
190
191
|
...deps.factoryReady ? {
|
|
191
192
|
workItems: deps.domains.workItems,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface.js","names":[],"sources":["../../src/routes/surface.ts"],"sourcesContent":["import type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { FactoryIntegration, IntegrationContext } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport { MaterializeError } from '../integrations/github/sandbox.js';\nimport { FactoryDispatchError } from '../rules/dispatch-errors.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport type { MastraFactorySandboxConfig } from '../sandbox/session-sandbox.js';\nimport {\n createSourceControlSessionLookup,\n ensureFactorySourceSession,\n FactorySourceSessionResolutionError,\n resolveFactoryDefaultModelId,\n resolveFactoryProjectForSession,\n resolveFactorySourceControl,\n} from '../session/factory-session.js';\nimport type { EnsuredFactorySourceSession } from '../session/factory-session.js';\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter, AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type { CommentsDomain } from '../storage/domains/comments/domain.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { IntegrationStorage } from '../storage/domains/integrations/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { ModelPacksStorage } from '../storage/domains/model-packs/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport {\n SourceControlConnectionNotFoundError,\n type SourceControlStorage,\n type SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport {\n isAgentActor,\n type FactoryDispatchFailureCode,\n type WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { workItemBranch, workItemBranchSource, workItemThreadTitle } from '../work-item-branch.js';\nimport { buildAutomationRunRoutes } from './automation-runs.js';\nimport { ConfigRoutes } from './config.js';\nimport { invalidateCustomProvidersSnapshots } from './custom-provider-source.js';\nimport { buildFsRoutes } from './fs.js';\nimport { IntakeRoutes } from './intake.js';\nimport { KnowledgeRoutes } from './knowledge.js';\nimport { OAuthRoutes } from './oauth.js';\nimport type { RouteAuth } from './route.js';\nimport { SkillRoutes } from './skills.js';\nimport { buildSourceControlSessionRoutes } from './source-control-sessions.js';\nimport { buildSourceControlSettingsRoutes } from './source-control-settings.js';\nimport { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\nconst MATERIALIZE_FAILURE_CODE = {\n 'git-missing': 'repository_git_missing',\n 'egress-blocked': 'repository_egress_blocked',\n 'clone-failed': 'repository_clone_failed',\n 'pull-failed': 'repository_pull_failed',\n 'push-failed': 'repository_push_failed',\n 'commit-failed': 'repository_commit_failed',\n 'gh-missing': 'repository_cli_missing',\n 'pr-failed': 'repository_pr_failed',\n} satisfies Record<MaterializeError['code'], FactoryDispatchFailureCode>;\nexport interface IntegrationRegistration {\n integration: FactoryIntegration;\n ready: boolean;\n ensureReady: () => Promise<void>;\n}\n\nexport interface FactoryApiRoutesDeps {\n controllerId: string;\n controller: AgentController<MastraCodeState>;\n /** Registry of the sessions this process holds, owned by the host so it can watch them too. */\n liveSessions: LiveSessions;\n /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n /** Optional user directory for resolving persisted owners to display profiles. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n authStorage: AuthStorage;\n audit: AuditEmitter & AuditRecorder;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox surface (enablement, provider label, create callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** Root factory storage backend (distributed locks, app-db diagnostics). */\n factoryStorage?: FactoryStorage;\n integrationStorage: IntegrationStorage;\n sourceControlStorage: SourceControlStorage;\n /** App-table domain handles, registered and owned by `MastraFactory.prepare()`. */\n domains: {\n intake: IntakeStorage;\n modelCredentials: ModelCredentialsStorage;\n memorySettings: MemorySettingsStorage;\n customProviders: CustomProvidersStorage;\n filesystem: FilesystemStorage;\n modelPacks: ModelPacksStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n comments: WorkItemCommentsStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n configVersion: string;\n /** Boards installed for this Factory instance. */\n boardRegistry: BoardRegistry;\n /** Work-item feed service, handed to integrations that ingest platform messages. */\n feed: CommentsDomain;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n }) => void;\n}\n\nfunction guardIntegrationRoutes({\n integration,\n ready,\n ensureReady,\n routes,\n}: IntegrationRegistration & { routes: ApiRoute[] }): ApiRoute[] {\n if (ready) return routes;\n return routes.map(route => {\n if ('handler' in route) {\n const handler = route.handler;\n return {\n ...route,\n handler: async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context, async () => {});\n },\n };\n }\n\n const createHandler = route.createHandler;\n return {\n ...route,\n createHandler: async (args: Parameters<typeof createHandler>[0]) => {\n const handler = await createHandler(args);\n return async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context);\n };\n },\n };\n });\n}\n\n/**\n * Resolve the source-control session the work item already holds for this role,\n * when it still resolves to the item's org and factory project. Returns\n * `undefined` when there is no ref or the ref no longer resolves, so the caller\n * falls back to minting one.\n */\nasync function reuseBoundSession(\n sourceControl: SourceControlStorageHandle,\n input: FactoryBindingPreparationInput,\n): Promise<EnsuredFactorySourceSession | undefined> {\n const ref = input.item.sessions[input.role];\n if (!ref) return undefined;\n // At least as strict as the coordinator's resolveSourceSession: a ref it\n // would reject must fall through to minting, not hard-fail the run.\n const resolved = await resolveFactoryProjectForSession({ sourceControl, sessionId: ref.sessionId });\n if (\n !resolved ||\n resolved.orgId !== input.record.orgId ||\n resolved.factoryProjectId !== input.record.factoryProjectId\n ) {\n return undefined;\n }\n const session = await sourceControl.sessions.getBySessionId(ref.sessionId);\n if (!session) return undefined;\n return {\n sessionId: session.sessionId,\n userId: session.userId,\n projectRepositoryId: session.projectRepositoryId,\n branch: session.branch,\n baseBranch: session.baseBranch,\n };\n}\n\n/**\n * Start a factory run for a rule binding: ensure the source-control session the\n * coordinator requires, then hand it to `prepare` along with the factory's\n * default model. Exported for tests — this is the autonomous entry point with no\n * browser and no interactive user, so nothing else would catch a regression in\n * what it forwards.\n */\nexport async function prepareFactoryRuleBinding(\n sourceControlProvider: SourceControlStorageHandle | Pick<GithubIntegration, 'sourceControlStorage'>,\n coordinator: Pick<FactoryStartCoordinator, 'prepare'>,\n projects: FactoryProjectsStorage,\n boards: BoardRegistry,\n input: FactoryBindingPreparationInput,\n): Promise<void> {\n try {\n const sourceControl =\n 'sourceControlStorage' in sourceControlProvider\n ? sourceControlProvider.sourceControlStorage\n : sourceControlProvider;\n const source = workItemBranchSource(input.item.externalSource);\n const branch = workItemBranch({ id: input.item.id, source, metadata: input.item.metadata });\n // Only leaving a resting phase derives a lane from the role: roles don't own lanes,\n // and the Done close-out running in the triage seat must not drag the card back.\n // Both the current phase and the role's lane come from the installed board; an\n // unknown board or phase yields no destination rather than a guessed one.\n const board = boards.get(boardForWorkItem(input.item));\n const currentStage = input.item.stages.length === 1 ? input.item.stages[0] : undefined;\n const currentKind = currentStage === undefined ? undefined : board?.phaseKind(currentStage);\n const destinationStage =\n currentKind === undefined\n ? undefined\n : currentKind === 'resting'\n ? board?.phaseForRole(input.role)\n : currentStage;\n if (!destinationStage) {\n throw new FactoryDispatchError(\n 'unsupported_provider_item',\n `Factory skill invocation has no destination lane (role \"${input.role}\", stages [${input.item.stages.join(', ')}]).`,\n );\n }\n const repositorySlug =\n typeof input.item.metadata?.repository === 'string' ? input.item.metadata.repository : undefined;\n // Re-preparing a binding (server restart, retired controller session) must\n // land in the role's existing session: minting a replacement would repoint\n // the work item, flip the session's owner to the approver, and orphan the\n // previous sandbox.\n const approver = input.record.approvedBy ?? undefined;\n const preparedSession =\n (await reuseBoundSession(sourceControl, input)) ??\n (await ensureFactorySourceSession({\n sourceControl,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n repositorySlug,\n branch,\n // A person who approved the run is its interactive user: attribute it to\n // them, not the repo connector. An agent's pre-approval names no person.\n attributeToUserId: isAgentActor(approver) ? undefined : approver,\n }));\n\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),\n threadTitle: workItemThreadTitle({ source, title: input.item.title, metadata: input.item.metadata }),\n kickoffKey: input.record.id,\n destinationStage,\n workItem: {\n id: input.item.id,\n role: input.role,\n input: {\n externalSource: input.item.externalSource,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n stages: ['intake'],\n sessions: input.item.sessions,\n metadata: input.item.metadata,\n },\n },\n });\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n if (error instanceof FactorySourceSessionResolutionError) {\n const code = error.reason === 'connection' ? 'source_control_missing' : 'source_repository_missing';\n throw new FactoryDispatchError(code, error.message, { cause: error });\n }\n if (error instanceof SourceControlConnectionNotFoundError) {\n throw new FactoryDispatchError('source_control_missing', error.message, { cause: error });\n }\n if (error instanceof MaterializeError) {\n throw new FactoryDispatchError(MATERIALIZE_FAILURE_CODE[error.code], error.message, { cause: error });\n }\n throw error;\n }\n}\n\n/**\n * Build the {@link IntegrationContext} handed to an integration when the\n * factory collects its capabilities (routes, workers). One shape everywhere:\n * `assembleFactoryApiRoutes` uses it per registration, and `MastraFactory` uses it\n * when collecting integration workers at finalize.\n */\nexport function buildIntegrationContext(\n deps: Pick<\n FactoryApiRoutesDeps,\n | 'controller'\n | 'publicOrigin'\n | 'auth'\n | 'sandbox'\n | 'users'\n | 'factoryStorage'\n | 'integrationStorage'\n | 'sourceControlStorage'\n | 'integrations'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n configVersion: string;\n boardRegistry: BoardRegistry;\n factoryReady: boolean;\n /** Work-item feed service, so a channel integration can ingest platform messages. */\n feed: CommentsDomain;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings'\n >;\n /**\n * Stable id of the registered source-control-owning integration (today:\n * `'github'` when registered). Every call site must derive and pass it so\n * `routes()`, `channels()`, and `workers()` all see the same context shape.\n */\n sourceControlOwnerId?: string;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n sandbox: deps.sandbox,\n ...(deps.users ? { users: deps.users } : {}),\n factoryStorage: deps.factoryStorage,\n baseUrl: deps.publicOrigin,\n controller: deps.controller,\n stateSigner: deps.stateSigner,\n storage: {\n generic: deps.integrationStorage.forIntegration(integrationId),\n sourceControl: deps.sourceControlStorage.forIntegration(integrationId),\n ...(deps.sourceControlOwnerId\n ? { sourceControlOwner: deps.sourceControlStorage.forIntegration(deps.sourceControlOwnerId) }\n : {}),\n sourceControls: (deps.integrations ?? [])\n .filter(({ integration }) => integration.versionControl)\n .map(({ integration }) => deps.sourceControlStorage.forIntegration(integration.id)),\n projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n },\n ...(deps.factoryReady ? { workItems: deps.domains.workItems, feed: deps.feed } : {}),\n ...(deps.factoryReady\n ? {\n runtime: { configVersion: deps.configVersion, workItems: deps.domains.workItems, boards: deps.boardRegistry },\n }\n : {}),\n ...(deps.emitAudit ? { hooks: { emitAudit: deps.emitAudit } } : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status`, `/web/linear/status`, and `/web/jira/status`\n * unconditionally, so when an integration is absent (or not ready) the status\n * contract must still hold. Unknown custom ids get no stub — the SPA doesn't\n * poll them.\n */\nfunction disabledIntegrationStatusRoutes(deps: FactoryApiRoutesDeps, id: string, configured = false): ApiRoute[] {\n if (id === 'github') {\n return [\n registerApiRoute('/web/github/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n installations: [],\n reason: 'missing_config',\n diagnostics: getGithubFeatureDiagnostics({\n github: undefined,\n auth: deps.auth,\n appDbConfigured: deps.factoryStorage !== undefined,\n stateSigner: deps.stateSigner,\n sandbox: deps.sandbox,\n }),\n }),\n }),\n ];\n }\n if (id === 'linear') {\n return [\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: {\n linearAppConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: true,\n },\n }),\n }),\n ];\n }\n if (id === 'jira') {\n return [\n registerApiRoute('/web/jira/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n configured,\n site: null,\n reason: 'missing_config',\n diagnostics: {\n jiraConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: deps.factoryStorage !== undefined,\n },\n }),\n }),\n ];\n }\n return [];\n}\n\n/**\n * Stub for `GET /web/channel-accounts` when NO Slack integration is\n * registered. The SPA's Connections section polls the path unconditionally;\n * without a stub the SPA fallback serves HTML, which the UI can only read as\n * \"old server / unknown\". The machine-readable reason lets it say the truth:\n * the integration isn't registered.\n *\n * Mounted only for ABSENT slack — a registered integration owns the path via\n * its connect routes (or, when the state signer is unstable, gets no routes\n * at all and the UI falls back to the generic copy). Static payload, leaks\n * nothing → no auth needed, same posture as the github/linear stubs.\n */\nfunction absentSlackChannelAccountsRoutes(): ApiRoute[] {\n return [\n registerApiRoute('/web/channel-accounts', {\n method: 'GET',\n requiresAuth: false,\n handler: c => c.json({ accounts: [], canConnect: false, reason: 'not_registered' }),\n }),\n ];\n}\n\n/**\n * Assemble the custom `/web/*` API routes as Mastra `server.apiRoutes`:\n * - fs browser routes (project picker), confined to `fsRoot`\n * - config routes (provider/API-key/model-pack/OM management)\n * - every registered integration's `routes()` surface (full set when ready,\n * disabled-status stub otherwise), plus stubs for absent known ids\n */\nexport function assembleFactoryApiRoutes(deps: FactoryApiRoutesDeps): ApiRoute[] {\n const emitAudit: AuditEmitter['emit'] = args => deps.audit.emit(args);\n const registrations = deps.integrations ?? [];\n const githubRegistration = registrations.find(({ integration }) => integration.id === 'github');\n const githubStorage = githubRegistration ? deps.sourceControlStorage.forIntegration('github') : undefined;\n const githubIntegration = githubRegistration?.integration as GithubIntegration | undefined;\n const sourceControlRegistrations = registrations.filter(({ integration }) => integration.versionControl);\n const sourceControlIntegrationIds = [\n ...new Set(['github', ...sourceControlRegistrations.map(({ integration }) => integration.id)]),\n ];\n const sourceControls = sourceControlIntegrationIds.map(id => deps.sourceControlStorage.forIntegration(id));\n const sourceControlSessions = createSourceControlSessionLookup(sourceControls);\n\n const integrationRoutes = registrations.flatMap(registration => {\n const { integration } = registration;\n if (!deps.stateSigner) return disabledIntegrationStatusRoutes(deps, integration.id, true);\n const context = buildIntegrationContext(\n {\n ...deps,\n stateSigner: deps.stateSigner,\n emitAudit,\n ...(githubRegistration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n return guardIntegrationRoutes({ ...registration, routes: integration.routes(context) });\n });\n // Session persistence belongs to the linked source-control provider, not to\n // GitHub. Replace legacy GitHub-owned handlers with one resolver spanning\n // the legacy GitHub partition and every registered source-control partition.\n const sharedSessionRouteKeys = new Set([\n 'GET /web/github/projects/:id/sessions',\n 'POST /web/github/projects/:id/sessions',\n 'GET /web/user-sessions/:sessionId',\n 'DELETE /web/user-sessions/:sessionId',\n 'POST /web/user-sessions/:sessionId/title',\n ]);\n const providerRoutes = integrationRoutes.filter(\n route => !sharedSessionRouteKeys.has(route.method + ' ' + route.path),\n );\n const sourceControlSessionRoutes =\n sourceControls.length === 0\n ? []\n : buildSourceControlSessionRoutes({\n auth: deps.auth,\n sourceControls,\n ...(deps.users ? { users: deps.users } : {}),\n controller: deps.controller,\n memorySettings: deps.domains.memorySettings,\n sessionRetirement: deps.sessionRetirement,\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n });\n // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear', 'jira']\n .filter(id => !registrations.some(({ integration }) => integration.id === id))\n .flatMap(id => disabledIntegrationStatusRoutes(deps, id));\n // Absent slack gets the channel-accounts not-registered stub (registered\n // slack owns the path via its own connect routes).\n const slackAbsentStubs = registrations.some(({ integration }) => integration.id === 'slack')\n ? []\n : absentSlackChannelAccountsRoutes();\n\n const transitionService = deps.factoryReady\n ? (deps.factoryTransitionService ??\n new FactoryTransitionService({\n configVersion: deps.configVersion,\n boards: deps.boardRegistry,\n storage: deps.domains.workItems,\n audit: deps.audit,\n }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n request =>\n resolveFactorySourceControl({\n sourceControls,\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n sessionId: request.sessionId,\n }),\n deps.domains.memorySettings,\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n ...(sourceControlRegistrations.length > 0\n ? {\n prepareBinding: async (input: FactoryBindingPreparationInput) => {\n const sourceControl = await resolveFactorySourceControl({\n sourceControls,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n });\n if (!sourceControl) {\n throw new FactoryDispatchError(\n 'source_control_missing',\n 'Factory project has no linked source-control repository.',\n );\n }\n return prepareFactoryRuleBinding(\n sourceControl,\n startCoordinator,\n deps.domains.projects,\n deps.boardRegistry,\n input,\n );\n },\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n sessions: sourceControlSessions,\n filesystem: deps.domains.filesystem,\n },\n }),\n ...new ConfigRoutes({\n auth: deps.auth,\n controller: deps.controller,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n modelPacks: deps.domains.modelPacks,\n sourceControlSessions,\n memorySettings: deps.domains.memorySettings,\n factoryProjects: deps.domains.projects,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n onCustomProvidersChanged: invalidateCustomProvidersSnapshots,\n }).routes(),\n ...new OAuthRoutes({\n auth: deps.auth,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n memorySettings: deps.domains.memorySettings,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n }).routes(),\n ...new SkillRoutes({\n auth: deps.auth,\n controllerId: deps.controllerId,\n controller: deps.controller,\n sourceControlStorage: githubStorage,\n ensureSourceControlReady: githubRegistration?.ensureReady,\n }).routes(),\n ...sourceControlSessionRoutes,\n ...(sourceControls.length === 0 ? [] : buildSourceControlSettingsRoutes({ auth: deps.auth, sourceControls })),\n ...providerRoutes,\n ...absentStubs,\n ...slackAbsentStubs,\n ...(deps.intakeReady\n ? new IntakeRoutes({\n auth: deps.auth,\n audit: deps.audit,\n intake: deps.domains.intake,\n projects: deps.domains.projects,\n boardRegistry: deps.boardRegistry,\n // Relocation reads and writes work items, which init fail-soft separately from intake.\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n integrations: (deps.integrations ?? []).flatMap(({ integration }) =>\n integration.intake ? [{ id: integration.id, intake: integration.intake }] : [],\n ),\n }).routes()\n : []),\n ...(deps.factoryReady && deps.knowledgeEnabled\n ? new KnowledgeRoutes({\n auth: deps.auth,\n projects: deps.domains.projects,\n knowledge: async () => deps.factoryStorage?.getMastraStorage().getStore('knowledge'),\n }).routes()\n : []),\n ...(deps.factoryReady\n ? new WorkItemRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n boardRegistry: deps.boardRegistry,\n comments: deps.domains.comments,\n queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: deps.liveSessions,\n }).routes()\n : []),\n ...(deps.factoryReady\n ? buildAutomationRunRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n configVersion: deps.configVersion,\n })\n : []),\n ];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"surface.js","names":[],"sources":["../../src/routes/surface.ts"],"sourcesContent":["import type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { FactoryStorage } from '@mastra/core/storage';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { FactoryIntegration, IntegrationContext } from '../integrations/base.js';\nimport { getGithubFeatureDiagnostics } from '../integrations/github/config.js';\nimport type { GithubIntegration } from '../integrations/github/integration.js';\nimport { MaterializeError } from '../integrations/github/sandbox.js';\nimport { FactoryDispatchError } from '../rules/dispatch-errors.js';\nimport type { FactoryBindingPreparationInput } from '../rules/dispatcher.js';\nimport { FactoryStartCoordinator } from '../rules/start-coordinator.js';\nimport { FactoryTransitionService } from '../rules/transition-service.js';\nimport type { MastraFactorySandboxConfig } from '../sandbox/session-sandbox.js';\nimport {\n createSourceControlSessionLookup,\n ensureFactorySourceSession,\n FactorySourceSessionResolutionError,\n resolveFactoryDefaultModelId,\n resolveFactoryProjectForSession,\n resolveFactorySourceControl,\n} from '../session/factory-session.js';\nimport type { EnsuredFactorySourceSession } from '../session/factory-session.js';\nimport type { LiveSessions } from '../session/live-sessions.js';\nimport type { StateSigner } from '../state-signing.js';\nimport type { AuditEmitter, AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { ChannelIdentityStorage } from '../storage/domains/channel-identity/base.js';\nimport type { WorkItemCommentsStorage } from '../storage/domains/comments/base.js';\nimport type { CommentsDomain } from '../storage/domains/comments/domain.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport type { CustomProvidersStorage } from '../storage/domains/custom-providers/base.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { IntegrationStorage } from '../storage/domains/integrations/base.js';\nimport type { MemorySettingsStorage } from '../storage/domains/memory-settings/base.js';\nimport type { ModelPacksStorage } from '../storage/domains/model-packs/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';\nimport {\n SourceControlConnectionNotFoundError,\n type SourceControlStorage,\n type SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport {\n isAgentActor,\n type FactoryDispatchFailureCode,\n type WorkItemsStorage,\n} from '../storage/domains/work-items/base.js';\nimport { workItemBranch, workItemBranchSource, workItemThreadTitle } from '../work-item-branch.js';\nimport { buildAutomationRunRoutes } from './automation-runs.js';\nimport { ConfigRoutes } from './config.js';\nimport { invalidateCustomProvidersSnapshots } from './custom-provider-source.js';\nimport { buildFsRoutes } from './fs.js';\nimport { IntakeRoutes } from './intake.js';\nimport { KnowledgeRoutes } from './knowledge.js';\nimport { OAuthRoutes } from './oauth.js';\nimport type { RouteAuth } from './route.js';\nimport { SkillRoutes } from './skills.js';\nimport { buildSourceControlSessionRoutes } from './source-control-sessions.js';\nimport { buildSourceControlSettingsRoutes } from './source-control-settings.js';\nimport { invalidateTenantCredentialSnapshots } from './tenant-credentials.js';\nimport { WorkItemRoutes } from './work-items.js';\n\nconst MATERIALIZE_FAILURE_CODE = {\n 'git-missing': 'repository_git_missing',\n 'egress-blocked': 'repository_egress_blocked',\n 'clone-failed': 'repository_clone_failed',\n 'pull-failed': 'repository_pull_failed',\n 'push-failed': 'repository_push_failed',\n 'commit-failed': 'repository_commit_failed',\n 'gh-missing': 'repository_cli_missing',\n 'pr-failed': 'repository_pr_failed',\n} satisfies Record<MaterializeError['code'], FactoryDispatchFailureCode>;\nexport interface IntegrationRegistration {\n integration: FactoryIntegration;\n ready: boolean;\n ensureReady: () => Promise<void>;\n}\n\nexport interface FactoryApiRoutesDeps {\n controllerId: string;\n controller: AgentController<MastraCodeState>;\n /** Registry of the sessions this process holds, owned by the host so it can watch them too. */\n liveSessions: LiveSessions;\n /** Request-auth seam threaded from the host (no service locator). */\n auth: RouteAuth;\n /** Optional user directory for resolving persisted owners to display profiles. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\n authStorage: AuthStorage;\n audit: AuditEmitter & AuditRecorder;\n fsRoot?: string;\n publicOrigin: string;\n stateSigner?: StateSigner;\n /** Sandbox surface (enablement, provider label, create callback). */\n sandbox?: MastraFactorySandboxConfig;\n /** Root factory storage backend (distributed locks, app-db diagnostics). */\n factoryStorage?: FactoryStorage;\n integrationStorage: IntegrationStorage;\n sourceControlStorage: SourceControlStorage;\n /** App-table domain handles, registered and owned by `MastraFactory.prepare()`. */\n domains: {\n intake: IntakeStorage;\n modelCredentials: ModelCredentialsStorage;\n memorySettings: MemorySettingsStorage;\n customProviders: CustomProvidersStorage;\n filesystem: FilesystemStorage;\n modelPacks: ModelPacksStorage;\n projects: FactoryProjectsStorage;\n queueHealth: QueueHealthStorage;\n workItems: WorkItemsStorage;\n channelIdentity: ChannelIdentityStorage;\n comments: WorkItemCommentsStorage;\n };\n integrations?: IntegrationRegistration[];\n intakeReady: boolean;\n factoryReady: boolean;\n knowledgeEnabled: boolean;\n /** Resolved Factory rule set, threaded from the host (no service locator). */\n configVersion: string;\n /** Boards installed for this Factory instance. */\n boardRegistry: BoardRegistry;\n /** Work-item feed service, handed to integrations that ingest platform messages. */\n feed: CommentsDomain;\n factoryTransitionService?: FactoryTransitionService;\n sessionRetirement?: import('../sandbox/session-retirement.js').SessionRetirementCoordinator;\n onFactoryRuntime?: (runtime: {\n transitionService: FactoryTransitionService;\n prepareBinding?: (input: FactoryBindingPreparationInput) => Promise<void>;\n }) => void;\n}\n\nfunction guardIntegrationRoutes({\n integration,\n ready,\n ensureReady,\n routes,\n}: IntegrationRegistration & { routes: ApiRoute[] }): ApiRoute[] {\n if (ready) return routes;\n return routes.map(route => {\n if ('handler' in route) {\n const handler = route.handler;\n return {\n ...route,\n handler: async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context, async () => {});\n },\n };\n }\n\n const createHandler = route.createHandler;\n return {\n ...route,\n createHandler: async (args: Parameters<typeof createHandler>[0]) => {\n const handler = await createHandler(args);\n return async (context: Parameters<typeof handler>[0]) => {\n try {\n await ensureReady();\n } catch {\n return context.json(\n { error: 'integration_unavailable', message: `${integration.id} integration is unavailable.` },\n 503,\n );\n }\n return handler(context);\n };\n },\n };\n });\n}\n\n/**\n * Resolve the source-control session the work item already holds for this role,\n * when it still resolves to the item's org and factory project. Returns\n * `undefined` when there is no ref or the ref no longer resolves, so the caller\n * falls back to minting one.\n */\nasync function reuseBoundSession(\n sourceControl: SourceControlStorageHandle,\n input: FactoryBindingPreparationInput,\n): Promise<EnsuredFactorySourceSession | undefined> {\n const ref = input.item.sessions[input.role];\n if (!ref) return undefined;\n // At least as strict as the coordinator's resolveSourceSession: a ref it\n // would reject must fall through to minting, not hard-fail the run.\n const resolved = await resolveFactoryProjectForSession({ sourceControl, sessionId: ref.sessionId });\n if (\n !resolved ||\n resolved.orgId !== input.record.orgId ||\n resolved.factoryProjectId !== input.record.factoryProjectId\n ) {\n return undefined;\n }\n const session = await sourceControl.sessions.getBySessionId(ref.sessionId);\n if (!session) return undefined;\n return {\n sessionId: session.sessionId,\n userId: session.userId,\n projectRepositoryId: session.projectRepositoryId,\n branch: session.branch,\n baseBranch: session.baseBranch,\n };\n}\n\n/**\n * Start a factory run for a rule binding: ensure the source-control session the\n * coordinator requires, then hand it to `prepare` along with the factory's\n * default model. Exported for tests — this is the autonomous entry point with no\n * browser and no interactive user, so nothing else would catch a regression in\n * what it forwards.\n */\nexport async function prepareFactoryRuleBinding(\n sourceControlProvider: SourceControlStorageHandle | Pick<GithubIntegration, 'sourceControlStorage'>,\n coordinator: Pick<FactoryStartCoordinator, 'prepare'>,\n projects: FactoryProjectsStorage,\n boards: BoardRegistry,\n input: FactoryBindingPreparationInput,\n): Promise<void> {\n try {\n const sourceControl =\n 'sourceControlStorage' in sourceControlProvider\n ? sourceControlProvider.sourceControlStorage\n : sourceControlProvider;\n const source = workItemBranchSource(input.item.externalSource);\n const branch = workItemBranch({ id: input.item.id, source, metadata: input.item.metadata });\n // Only leaving a resting phase derives a lane from the role: roles don't own lanes,\n // and the Done close-out running in the triage seat must not drag the card back.\n // Both the current phase and the role's lane come from the installed board; an\n // unknown board or phase yields no destination rather than a guessed one.\n const board = boards.get(boardForWorkItem(input.item));\n const currentStage = input.item.stages.length === 1 ? input.item.stages[0] : undefined;\n const currentKind = currentStage === undefined ? undefined : board?.phaseKind(currentStage);\n const destinationStage =\n currentKind === undefined\n ? undefined\n : currentKind === 'resting'\n ? board?.phaseForRole(input.role)\n : currentStage;\n if (!destinationStage) {\n throw new FactoryDispatchError(\n 'unsupported_provider_item',\n `Factory skill invocation has no destination lane (role \"${input.role}\", stages [${input.item.stages.join(', ')}]).`,\n );\n }\n const repositorySlug =\n typeof input.item.metadata?.repository === 'string' ? input.item.metadata.repository : undefined;\n // Re-preparing a binding (server restart, retired controller session) must\n // land in the role's existing session: minting a replacement would repoint\n // the work item, flip the session's owner to the approver, and orphan the\n // previous sandbox.\n const approver = input.record.approvedBy ?? undefined;\n const preparedSession =\n (await reuseBoundSession(sourceControl, input)) ??\n (await ensureFactorySourceSession({\n sourceControl,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n repositorySlug,\n branch,\n // A person who approved the run is its interactive user: attribute it to\n // them, not the repo connector. An agent's pre-approval names no person.\n attributeToUserId: isAgentActor(approver) ? undefined : approver,\n }));\n\n await coordinator.prepare({\n orgId: input.record.orgId,\n userId: preparedSession.userId,\n factoryProjectId: input.record.factoryProjectId,\n sessionId: preparedSession.sessionId,\n defaultModelId: await resolveFactoryDefaultModelId(projects, input.record.factoryProjectId),\n threadTitle: workItemThreadTitle({ source, title: input.item.title, metadata: input.item.metadata }),\n kickoffKey: input.record.id,\n destinationStage,\n workItem: {\n id: input.item.id,\n role: input.role,\n input: {\n externalSource: input.item.externalSource,\n parentWorkItemId: input.item.parentWorkItemId,\n title: input.item.title,\n stages: ['intake'],\n sessions: input.item.sessions,\n metadata: input.item.metadata,\n },\n },\n });\n } catch (error) {\n if (error instanceof FactoryDispatchError) throw error;\n if (error instanceof FactorySourceSessionResolutionError) {\n const code = error.reason === 'connection' ? 'source_control_missing' : 'source_repository_missing';\n throw new FactoryDispatchError(code, error.message, { cause: error });\n }\n if (error instanceof SourceControlConnectionNotFoundError) {\n throw new FactoryDispatchError('source_control_missing', error.message, { cause: error });\n }\n if (error instanceof MaterializeError) {\n throw new FactoryDispatchError(MATERIALIZE_FAILURE_CODE[error.code], error.message, { cause: error });\n }\n throw error;\n }\n}\n\n/**\n * Build the {@link IntegrationContext} handed to an integration when the\n * factory collects its capabilities (routes, workers). One shape everywhere:\n * `assembleFactoryApiRoutes` uses it per registration, and `MastraFactory` uses it\n * when collecting integration workers at finalize.\n */\nexport function buildIntegrationContext(\n deps: Pick<\n FactoryApiRoutesDeps,\n | 'controller'\n | 'publicOrigin'\n | 'auth'\n | 'sandbox'\n | 'users'\n | 'factoryStorage'\n | 'integrationStorage'\n | 'sourceControlStorage'\n | 'integrations'\n > & {\n stateSigner: StateSigner;\n emitAudit?: AuditEmitter['emit'];\n configVersion: string;\n boardRegistry: BoardRegistry;\n factoryReady: boolean;\n /** Work-item feed service, so a channel integration can ingest platform messages. */\n feed: CommentsDomain;\n domains: Pick<\n FactoryApiRoutesDeps['domains'],\n 'projects' | 'intake' | 'workItems' | 'channelIdentity' | 'memorySettings' | 'modelPacks'\n >;\n /**\n * Stable id of the registered source-control-owning integration (today:\n * `'github'` when registered). Every call site must derive and pass it so\n * `routes()`, `channels()`, and `workers()` all see the same context shape.\n */\n sourceControlOwnerId?: string;\n },\n integrationId: string,\n): IntegrationContext {\n return {\n auth: deps.auth,\n sandbox: deps.sandbox,\n ...(deps.users ? { users: deps.users } : {}),\n factoryStorage: deps.factoryStorage,\n baseUrl: deps.publicOrigin,\n controller: deps.controller,\n stateSigner: deps.stateSigner,\n storage: {\n generic: deps.integrationStorage.forIntegration(integrationId),\n sourceControl: deps.sourceControlStorage.forIntegration(integrationId),\n ...(deps.sourceControlOwnerId\n ? { sourceControlOwner: deps.sourceControlStorage.forIntegration(deps.sourceControlOwnerId) }\n : {}),\n sourceControls: (deps.integrations ?? [])\n .filter(({ integration }) => integration.versionControl)\n .map(({ integration }) => deps.sourceControlStorage.forIntegration(integration.id)),\n projects: deps.domains.projects,\n intake: deps.domains.intake,\n channelIdentity: deps.domains.channelIdentity,\n memorySettings: deps.domains.memorySettings,\n modelPacks: deps.domains.modelPacks,\n },\n ...(deps.factoryReady ? { workItems: deps.domains.workItems, feed: deps.feed } : {}),\n ...(deps.factoryReady\n ? {\n runtime: { configVersion: deps.configVersion, workItems: deps.domains.workItems, boards: deps.boardRegistry },\n }\n : {}),\n ...(deps.emitAudit ? { hooks: { emitAudit: deps.emitAudit } } : {}),\n };\n}\n\n/**\n * Disabled-status stub for the well-known integration ids. The SPA polls\n * `/web/github/status`, `/web/linear/status`, and `/web/jira/status`\n * unconditionally, so when an integration is absent (or not ready) the status\n * contract must still hold. Unknown custom ids get no stub — the SPA doesn't\n * poll them.\n */\nfunction disabledIntegrationStatusRoutes(deps: FactoryApiRoutesDeps, id: string, configured = false): ApiRoute[] {\n if (id === 'github') {\n return [\n registerApiRoute('/web/github/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n installations: [],\n reason: 'missing_config',\n diagnostics: getGithubFeatureDiagnostics({\n github: undefined,\n auth: deps.auth,\n appDbConfigured: deps.factoryStorage !== undefined,\n stateSigner: deps.stateSigner,\n sandbox: deps.sandbox,\n }),\n }),\n }),\n ];\n }\n if (id === 'linear') {\n return [\n registerApiRoute('/web/linear/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n connected: false,\n workspace: null,\n reason: 'missing_config',\n diagnostics: {\n linearAppConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: true,\n },\n }),\n }),\n ];\n }\n if (id === 'jira') {\n return [\n registerApiRoute('/web/jira/status', {\n method: 'GET',\n requiresAuth: false,\n handler: c =>\n c.json({\n enabled: false,\n configured,\n site: null,\n reason: 'missing_config',\n diagnostics: {\n jiraConfigured: configured,\n factoryAuthEnabled: deps.auth.enabled(),\n appDbConfigured: deps.factoryStorage !== undefined,\n },\n }),\n }),\n ];\n }\n return [];\n}\n\n/**\n * Stub for `GET /web/channel-accounts` when NO Slack integration is\n * registered. The SPA's Connections section polls the path unconditionally;\n * without a stub the SPA fallback serves HTML, which the UI can only read as\n * \"old server / unknown\". The machine-readable reason lets it say the truth:\n * the integration isn't registered.\n *\n * Mounted only for ABSENT slack — a registered integration owns the path via\n * its connect routes (or, when the state signer is unstable, gets no routes\n * at all and the UI falls back to the generic copy). Static payload, leaks\n * nothing → no auth needed, same posture as the github/linear stubs.\n */\nfunction absentSlackChannelAccountsRoutes(): ApiRoute[] {\n return [\n registerApiRoute('/web/channel-accounts', {\n method: 'GET',\n requiresAuth: false,\n handler: c => c.json({ accounts: [], canConnect: false, reason: 'not_registered' }),\n }),\n ];\n}\n\n/**\n * Assemble the custom `/web/*` API routes as Mastra `server.apiRoutes`:\n * - fs browser routes (project picker), confined to `fsRoot`\n * - config routes (provider/API-key/model-pack/OM management)\n * - every registered integration's `routes()` surface (full set when ready,\n * disabled-status stub otherwise), plus stubs for absent known ids\n */\nexport function assembleFactoryApiRoutes(deps: FactoryApiRoutesDeps): ApiRoute[] {\n const emitAudit: AuditEmitter['emit'] = args => deps.audit.emit(args);\n const registrations = deps.integrations ?? [];\n const githubRegistration = registrations.find(({ integration }) => integration.id === 'github');\n const githubStorage = githubRegistration ? deps.sourceControlStorage.forIntegration('github') : undefined;\n const githubIntegration = githubRegistration?.integration as GithubIntegration | undefined;\n const sourceControlRegistrations = registrations.filter(({ integration }) => integration.versionControl);\n const sourceControlIntegrationIds = [\n ...new Set(['github', ...sourceControlRegistrations.map(({ integration }) => integration.id)]),\n ];\n const sourceControls = sourceControlIntegrationIds.map(id => deps.sourceControlStorage.forIntegration(id));\n const sourceControlSessions = createSourceControlSessionLookup(sourceControls);\n\n const integrationRoutes = registrations.flatMap(registration => {\n const { integration } = registration;\n if (!deps.stateSigner) return disabledIntegrationStatusRoutes(deps, integration.id, true);\n const context = buildIntegrationContext(\n {\n ...deps,\n stateSigner: deps.stateSigner,\n emitAudit,\n ...(githubRegistration ? { sourceControlOwnerId: 'github' } : {}),\n },\n integration.id,\n );\n return guardIntegrationRoutes({ ...registration, routes: integration.routes(context) });\n });\n // Session persistence belongs to the linked source-control provider, not to\n // GitHub. Replace legacy GitHub-owned handlers with one resolver spanning\n // the legacy GitHub partition and every registered source-control partition.\n const sharedSessionRouteKeys = new Set([\n 'GET /web/github/projects/:id/sessions',\n 'POST /web/github/projects/:id/sessions',\n 'GET /web/user-sessions/:sessionId',\n 'DELETE /web/user-sessions/:sessionId',\n 'POST /web/user-sessions/:sessionId/title',\n ]);\n const providerRoutes = integrationRoutes.filter(\n route => !sharedSessionRouteKeys.has(route.method + ' ' + route.path),\n );\n const sourceControlSessionRoutes =\n sourceControls.length === 0\n ? []\n : buildSourceControlSessionRoutes({\n auth: deps.auth,\n sourceControls,\n ...(deps.users ? { users: deps.users } : {}),\n controller: deps.controller,\n memorySettings: deps.domains.memorySettings,\n sessionRetirement: deps.sessionRetirement,\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n });\n // Absent known integrations still get their disabled-status stub.\n const absentStubs = ['github', 'linear', 'jira']\n .filter(id => !registrations.some(({ integration }) => integration.id === id))\n .flatMap(id => disabledIntegrationStatusRoutes(deps, id));\n // Absent slack gets the channel-accounts not-registered stub (registered\n // slack owns the path via its own connect routes).\n const slackAbsentStubs = registrations.some(({ integration }) => integration.id === 'slack')\n ? []\n : absentSlackChannelAccountsRoutes();\n\n const transitionService = deps.factoryReady\n ? (deps.factoryTransitionService ??\n new FactoryTransitionService({\n configVersion: deps.configVersion,\n boards: deps.boardRegistry,\n storage: deps.domains.workItems,\n audit: deps.audit,\n }))\n : undefined;\n const startCoordinator = transitionService\n ? new FactoryStartCoordinator(\n deps.controller,\n deps.domains.workItems,\n transitionService,\n request =>\n resolveFactorySourceControl({\n sourceControls,\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n sessionId: request.sessionId,\n }),\n deps.domains.memorySettings,\n )\n : undefined;\n if (transitionService && startCoordinator) {\n deps.onFactoryRuntime?.({\n transitionService,\n ...(sourceControlRegistrations.length > 0\n ? {\n prepareBinding: async (input: FactoryBindingPreparationInput) => {\n const sourceControl = await resolveFactorySourceControl({\n sourceControls,\n orgId: input.record.orgId,\n factoryProjectId: input.record.factoryProjectId,\n });\n if (!sourceControl) {\n throw new FactoryDispatchError(\n 'source_control_missing',\n 'Factory project has no linked source-control repository.',\n );\n }\n return prepareFactoryRuleBinding(\n sourceControl,\n startCoordinator,\n deps.domains.projects,\n deps.boardRegistry,\n input,\n );\n },\n }\n : {}),\n });\n }\n\n return [\n ...buildFsRoutes({\n root: deps.fsRoot,\n sessionFs: {\n auth: deps.auth,\n sessions: sourceControlSessions,\n filesystem: deps.domains.filesystem,\n },\n }),\n ...new ConfigRoutes({\n auth: deps.auth,\n controller: deps.controller,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n modelPacks: deps.domains.modelPacks,\n sourceControlSessions,\n memorySettings: deps.domains.memorySettings,\n factoryProjects: deps.domains.projects,\n customProviders: deps.domains.customProviders,\n features: { knowledge: deps.knowledgeEnabled },\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n onCustomProvidersChanged: invalidateCustomProvidersSnapshots,\n }).routes(),\n ...new OAuthRoutes({\n auth: deps.auth,\n authStorage: deps.authStorage,\n modelCredentials: deps.domains.modelCredentials,\n memorySettings: deps.domains.memorySettings,\n onCredentialsChanged: invalidateTenantCredentialSnapshots,\n }).routes(),\n ...new SkillRoutes({\n auth: deps.auth,\n controllerId: deps.controllerId,\n controller: deps.controller,\n sourceControlStorage: githubStorage,\n ensureSourceControlReady: githubRegistration?.ensureReady,\n }).routes(),\n ...sourceControlSessionRoutes,\n ...(sourceControls.length === 0 ? [] : buildSourceControlSettingsRoutes({ auth: deps.auth, sourceControls })),\n ...providerRoutes,\n ...absentStubs,\n ...slackAbsentStubs,\n ...(deps.intakeReady\n ? new IntakeRoutes({\n auth: deps.auth,\n audit: deps.audit,\n intake: deps.domains.intake,\n projects: deps.domains.projects,\n boardRegistry: deps.boardRegistry,\n // Relocation reads and writes work items, which init fail-soft separately from intake.\n ...(deps.factoryReady ? { workItems: deps.domains.workItems } : {}),\n integrations: (deps.integrations ?? []).flatMap(({ integration }) =>\n integration.intake ? [{ id: integration.id, intake: integration.intake }] : [],\n ),\n }).routes()\n : []),\n ...(deps.factoryReady && deps.knowledgeEnabled\n ? new KnowledgeRoutes({\n auth: deps.auth,\n projects: deps.domains.projects,\n knowledge: async () => deps.factoryStorage?.getMastraStorage().getStore('knowledge'),\n }).routes()\n : []),\n ...(deps.factoryReady\n ? new WorkItemRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n boardRegistry: deps.boardRegistry,\n comments: deps.domains.comments,\n queueHealth: deps.domains.queueHealth,\n transitionService,\n startCoordinator,\n liveSessions: deps.liveSessions,\n }).routes()\n : []),\n ...(deps.factoryReady\n ? buildAutomationRunRoutes({\n auth: deps.auth,\n audit: deps.audit,\n projects: deps.domains.projects,\n workItems: deps.domains.workItems,\n configVersion: deps.configVersion,\n })\n : []),\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,MAAM,2BAA2B;CAC/B,eAAe;CACf,kBAAkB;CAClB,gBAAgB;CAChB,eAAe;CACf,eAAe;CACf,iBAAiB;CACjB,cAAc;CACd,aAAa;AACf;AA2DA,SAAS,uBAAuB,EAC9B,aACA,OACA,aACA,UAC+D;CAC/D,IAAI,OAAO,OAAO;CAClB,OAAO,OAAO,KAAI,UAAS;EACzB,IAAI,aAAa,OAAO;GACtB,MAAM,UAAU,MAAM;GACtB,OAAO;IACL,GAAG;IACH,SAAS,OAAO,YAA2C;KACzD,IAAI;MACF,MAAM,YAAY;KACpB,QAAQ;MACN,OAAO,QAAQ,KACb;OAAE,OAAO;OAA2B,SAAS,GAAG,YAAY,GAAG;MAA8B,GAC7F,GACF;KACF;KACA,OAAO,QAAQ,SAAS,YAAY,CAAC,CAAC;IACxC;GACF;EACF;EAEA,MAAM,gBAAgB,MAAM;EAC5B,OAAO;GACL,GAAG;GACH,eAAe,OAAO,SAA8C;IAClE,MAAM,UAAU,MAAM,cAAc,IAAI;IACxC,OAAO,OAAO,YAA2C;KACvD,IAAI;MACF,MAAM,YAAY;KACpB,QAAQ;MACN,OAAO,QAAQ,KACb;OAAE,OAAO;OAA2B,SAAS,GAAG,YAAY,GAAG;MAA8B,GAC7F,GACF;KACF;KACA,OAAO,QAAQ,OAAO;IACxB;GACF;EACF;CACF,CAAC;AACH;;;;;;;AAQA,eAAe,kBACb,eACA,OACkD;CAClD,MAAM,MAAM,MAAM,KAAK,SAAS,MAAM;CACtC,IAAI,CAAC,KAAK,OAAO,KAAA;CAGjB,MAAM,WAAW,MAAM,gCAAgC;EAAE;EAAe,WAAW,IAAI;CAAU,CAAC;CAClG,IACE,CAAC,YACD,SAAS,UAAU,MAAM,OAAO,SAChC,SAAS,qBAAqB,MAAM,OAAO,kBAE3C;CAEF,MAAM,UAAU,MAAM,cAAc,SAAS,eAAe,IAAI,SAAS;CACzE,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,OAAO;EACL,WAAW,QAAQ;EACnB,QAAQ,QAAQ;EAChB,qBAAqB,QAAQ;EAC7B,QAAQ,QAAQ;EAChB,YAAY,QAAQ;CACtB;AACF;;;;;;;;AASA,eAAsB,0BACpB,uBACA,aACA,UACA,QACA,OACe;CACf,IAAI;EACF,MAAM,gBACJ,0BAA0B,wBACtB,sBAAsB,uBACtB;EACN,MAAM,SAAS,qBAAqB,MAAM,KAAK,cAAc;EAC7D,MAAM,SAAS,eAAe;GAAE,IAAI,MAAM,KAAK;GAAI;GAAQ,UAAU,MAAM,KAAK;EAAS,CAAC;EAK1F,MAAM,QAAQ,OAAO,IAAI,iBAAiB,MAAM,IAAI,CAAC;EACrD,MAAM,eAAe,MAAM,KAAK,OAAO,WAAW,IAAI,MAAM,KAAK,OAAO,KAAK,KAAA;EAC7E,MAAM,cAAc,iBAAiB,KAAA,IAAY,KAAA,IAAY,OAAO,UAAU,YAAY;EAC1F,MAAM,mBACJ,gBAAgB,KAAA,IACZ,KAAA,IACA,gBAAgB,YACd,OAAO,aAAa,MAAM,IAAI,IAC9B;EACR,IAAI,CAAC,kBACH,MAAM,IAAI,qBACR,6BACA,2DAA2D,MAAM,KAAK,aAAa,MAAM,KAAK,OAAO,KAAK,IAAI,EAAE,IAClH;EAEF,MAAM,iBACJ,OAAO,MAAM,KAAK,UAAU,eAAe,WAAW,MAAM,KAAK,SAAS,aAAa,KAAA;EAKzF,MAAM,WAAW,MAAM,OAAO,cAAc,KAAA;EAC5C,MAAM,kBACH,MAAM,kBAAkB,eAAe,KAAK,KAC5C,MAAM,2BAA2B;GAChC;GACA,OAAO,MAAM,OAAO;GACpB,kBAAkB,MAAM,OAAO;GAC/B;GACA;GAGA,mBAAmB,aAAa,QAAQ,IAAI,KAAA,IAAY;EAC1D,CAAC;EAEH,MAAM,YAAY,QAAQ;GACxB,OAAO,MAAM,OAAO;GACpB,QAAQ,gBAAgB;GACxB,kBAAkB,MAAM,OAAO;GAC/B,WAAW,gBAAgB;GAC3B,gBAAgB,MAAM,6BAA6B,UAAU,MAAM,OAAO,gBAAgB;GAC1F,aAAa,oBAAoB;IAAE;IAAQ,OAAO,MAAM,KAAK;IAAO,UAAU,MAAM,KAAK;GAAS,CAAC;GACnG,YAAY,MAAM,OAAO;GACzB;GACA,UAAU;IACR,IAAI,MAAM,KAAK;IACf,MAAM,MAAM;IACZ,OAAO;KACL,gBAAgB,MAAM,KAAK;KAC3B,kBAAkB,MAAM,KAAK;KAC7B,OAAO,MAAM,KAAK;KAClB,QAAQ,CAAC,QAAQ;KACjB,UAAU,MAAM,KAAK;KACrB,UAAU,MAAM,KAAK;IACvB;GACF;EACF,CAAC;CACH,SAAS,OAAO;EACd,IAAI,iBAAiB,sBAAsB,MAAM;EACjD,IAAI,iBAAiB,qCAEnB,MAAM,IAAI,qBADG,MAAM,WAAW,eAAe,2BAA2B,6BACnC,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;EAEtE,IAAI,iBAAiB,sCACnB,MAAM,IAAI,qBAAqB,0BAA0B,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;EAE1F,IAAI,iBAAiB,kBACnB,MAAM,IAAI,qBAAqB,yBAAyB,MAAM,OAAO,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;EAEtG,MAAM;CACR;AACF;;;;;;;AAQA,SAAgB,wBACd,MA8BA,eACoB;CACpB,OAAO;EACL,MAAM,KAAK;EACX,SAAS,KAAK;EACd,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;EAC1C,gBAAgB,KAAK;EACrB,SAAS,KAAK;EACd,YAAY,KAAK;EACjB,aAAa,KAAK;EAClB,SAAS;GACP,SAAS,KAAK,mBAAmB,eAAe,aAAa;GAC7D,eAAe,KAAK,qBAAqB,eAAe,aAAa;GACrE,GAAI,KAAK,uBACL,EAAE,oBAAoB,KAAK,qBAAqB,eAAe,KAAK,oBAAoB,EAAE,IAC1F,CAAC;GACL,iBAAiB,KAAK,gBAAgB,CAAC,EAAA,CACpC,QAAQ,EAAE,kBAAkB,YAAY,cAAc,CAAC,CACvD,KAAK,EAAE,kBAAkB,KAAK,qBAAqB,eAAe,YAAY,EAAE,CAAC;GACpF,UAAU,KAAK,QAAQ;GACvB,QAAQ,KAAK,QAAQ;GACrB,iBAAiB,KAAK,QAAQ;GAC9B,gBAAgB,KAAK,QAAQ;GAC7B,YAAY,KAAK,QAAQ;EAC3B;EACA,GAAI,KAAK,eAAe;GAAE,WAAW,KAAK,QAAQ;GAAW,MAAM,KAAK;EAAK,IAAI,CAAC;EAClF,GAAI,KAAK,eACL,EACE,SAAS;GAAE,eAAe,KAAK;GAAe,WAAW,KAAK,QAAQ;GAAW,QAAQ,KAAK;EAAc,EAC9G,IACA,CAAC;EACL,GAAI,KAAK,YAAY,EAAE,OAAO,EAAE,WAAW,KAAK,UAAU,EAAE,IAAI,CAAC;CACnE;AACF;;;;;;;;AASA,SAAS,gCAAgC,MAA4B,IAAY,aAAa,OAAmB;CAC/G,IAAI,OAAO,UACT,OAAO,CACL,iBAAiB,sBAAsB;EACrC,QAAQ;EACR,cAAc;EACd,UAAS,MACP,EAAE,KAAK;GACL,SAAS;GACT,WAAW;GACX,eAAe,CAAC;GAChB,QAAQ;GACR,aAAa,4BAA4B;IACvC,QAAQ,KAAA;IACR,MAAM,KAAK;IACX,iBAAiB,KAAK,mBAAmB,KAAA;IACzC,aAAa,KAAK;IAClB,SAAS,KAAK;GAChB,CAAC;EACH,CAAC;CACL,CAAC,CACH;CAEF,IAAI,OAAO,UACT,OAAO,CACL,iBAAiB,sBAAsB;EACrC,QAAQ;EACR,cAAc;EACd,UAAS,MACP,EAAE,KAAK;GACL,SAAS;GACT,WAAW;GACX,WAAW;GACX,QAAQ;GACR,aAAa;IACX,qBAAqB;IACrB,oBAAoB,KAAK,KAAK,QAAQ;IACtC,iBAAiB;GACnB;EACF,CAAC;CACL,CAAC,CACH;CAEF,IAAI,OAAO,QACT,OAAO,CACL,iBAAiB,oBAAoB;EACnC,QAAQ;EACR,cAAc;EACd,UAAS,MACP,EAAE,KAAK;GACL,SAAS;GACT;GACA,MAAM;GACN,QAAQ;GACR,aAAa;IACX,gBAAgB;IAChB,oBAAoB,KAAK,KAAK,QAAQ;IACtC,iBAAiB,KAAK,mBAAmB,KAAA;GAC3C;EACF,CAAC;CACL,CAAC,CACH;CAEF,OAAO,CAAC;AACV;;;;;;;;;;;;;AAcA,SAAS,mCAA+C;CACtD,OAAO,CACL,iBAAiB,yBAAyB;EACxC,QAAQ;EACR,cAAc;EACd,UAAS,MAAK,EAAE,KAAK;GAAE,UAAU,CAAC;GAAG,YAAY;GAAO,QAAQ;EAAiB,CAAC;CACpF,CAAC,CACH;AACF;;;;;;;;AASA,SAAgB,yBAAyB,MAAwC;CAC/E,MAAM,aAAkC,SAAQ,KAAK,MAAM,KAAK,IAAI;CACpE,MAAM,gBAAgB,KAAK,gBAAgB,CAAC;CAC5C,MAAM,qBAAqB,cAAc,MAAM,EAAE,kBAAkB,YAAY,OAAO,QAAQ;CAC9F,MAAM,gBAAgB,qBAAqB,KAAK,qBAAqB,eAAe,QAAQ,IAAI,KAAA;CACtE,oBAAoB;CAC9C,MAAM,6BAA6B,cAAc,QAAQ,EAAE,kBAAkB,YAAY,cAAc;CAIvG,MAAM,iBAAiB,CAFrB,mBAAG,IAAI,IAAI,CAAC,UAAU,GAAG,2BAA2B,KAAK,EAAE,kBAAkB,YAAY,EAAE,CAAC,CAAC,CAE9C,CAAC,CAAC,KAAI,OAAM,KAAK,qBAAqB,eAAe,EAAE,CAAC;CACzG,MAAM,wBAAwB,iCAAiC,cAAc;CAE7E,MAAM,oBAAoB,cAAc,SAAQ,iBAAgB;EAC9D,MAAM,EAAE,gBAAgB;EACxB,IAAI,CAAC,KAAK,aAAa,OAAO,gCAAgC,MAAM,YAAY,IAAI,IAAI;EACxF,MAAM,UAAU,wBACd;GACE,GAAG;GACH,aAAa,KAAK;GAClB;GACA,GAAI,qBAAqB,EAAE,sBAAsB,SAAS,IAAI,CAAC;EACjE,GACA,YAAY,EACd;EACA,OAAO,uBAAuB;GAAE,GAAG;GAAc,QAAQ,YAAY,OAAO,OAAO;EAAE,CAAC;CACxF,CAAC;CAID,MAAM,yCAAyB,IAAI,IAAI;EACrC;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,MAAM,iBAAiB,kBAAkB,QACvC,UAAS,CAAC,uBAAuB,IAAI,MAAM,SAAS,MAAM,MAAM,IAAI,CACtE;CACA,MAAM,6BACJ,eAAe,WAAW,IACtB,CAAC,IACD,gCAAgC;EAC9B,MAAM,KAAK;EACX;EACA,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;EAC1C,YAAY,KAAK;EACjB,gBAAgB,KAAK,QAAQ;EAC7B,mBAAmB,KAAK;EACxB,GAAI,KAAK,eAAe,EAAE,WAAW,KAAK,QAAQ,UAAU,IAAI,CAAC;CACnE,CAAC;CAEP,MAAM,cAAc;EAAC;EAAU;EAAU;CAAM,CAAC,CAC7C,QAAO,OAAM,CAAC,cAAc,MAAM,EAAE,kBAAkB,YAAY,OAAO,EAAE,CAAC,CAAC,CAC7E,SAAQ,OAAM,gCAAgC,MAAM,EAAE,CAAC;CAG1D,MAAM,mBAAmB,cAAc,MAAM,EAAE,kBAAkB,YAAY,OAAO,OAAO,IACvF,CAAC,IACD,iCAAiC;CAErC,MAAM,oBAAoB,KAAK,eAC1B,KAAK,4BACN,IAAI,yBAAyB;EAC3B,eAAe,KAAK;EACpB,QAAQ,KAAK;EACb,SAAS,KAAK,QAAQ;EACtB,OAAO,KAAK;CACd,CAAC,IACD,KAAA;CACJ,MAAM,mBAAmB,oBACrB,IAAI,wBACF,KAAK,YACL,KAAK,QAAQ,WACb,oBACA,YACE,4BAA4B;EAC1B;EACA,OAAO,QAAQ;EACf,kBAAkB,QAAQ;EAC1B,WAAW,QAAQ;CACrB,CAAC,GACH,KAAK,QAAQ,cACf,IACA,KAAA;CACJ,IAAI,qBAAqB,kBACvB,KAAK,mBAAmB;EACtB;EACA,GAAI,2BAA2B,SAAS,IACpC,EACE,gBAAgB,OAAO,UAA0C;GAC/D,MAAM,gBAAgB,MAAM,4BAA4B;IACtD;IACA,OAAO,MAAM,OAAO;IACpB,kBAAkB,MAAM,OAAO;GACjC,CAAC;GACD,IAAI,CAAC,eACH,MAAM,IAAI,qBACR,0BACA,0DACF;GAEF,OAAO,0BACL,eACA,kBACA,KAAK,QAAQ,UACb,KAAK,eACL,KACF;EACF,EACF,IACA,CAAC;CACP,CAAC;CAGH,OAAO;EACL,GAAG,cAAc;GACf,MAAM,KAAK;GACX,WAAW;IACT,MAAM,KAAK;IACX,UAAU;IACV,YAAY,KAAK,QAAQ;GAC3B;EACF,CAAC;EACD,GAAG,IAAI,aAAa;GAClB,MAAM,KAAK;GACX,YAAY,KAAK;GACjB,aAAa,KAAK;GAClB,kBAAkB,KAAK,QAAQ;GAC/B,YAAY,KAAK,QAAQ;GACzB;GACA,gBAAgB,KAAK,QAAQ;GAC7B,iBAAiB,KAAK,QAAQ;GAC9B,iBAAiB,KAAK,QAAQ;GAC9B,UAAU,EAAE,WAAW,KAAK,iBAAiB;GAC7C,sBAAsB;GACtB,0BAA0B;EAC5B,CAAC,CAAC,CAAC,OAAO;EACV,GAAG,IAAI,YAAY;GACjB,MAAM,KAAK;GACX,aAAa,KAAK;GAClB,kBAAkB,KAAK,QAAQ;GAC/B,gBAAgB,KAAK,QAAQ;GAC7B,sBAAsB;EACxB,CAAC,CAAC,CAAC,OAAO;EACV,GAAG,IAAI,YAAY;GACjB,MAAM,KAAK;GACX,cAAc,KAAK;GACnB,YAAY,KAAK;GACjB,sBAAsB;GACtB,0BAA0B,oBAAoB;EAChD,CAAC,CAAC,CAAC,OAAO;EACV,GAAG;EACH,GAAI,eAAe,WAAW,IAAI,CAAC,IAAI,iCAAiC;GAAE,MAAM,KAAK;GAAM;EAAe,CAAC;EAC3G,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAI,KAAK,cACL,IAAI,aAAa;GACf,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,QAAQ,KAAK,QAAQ;GACrB,UAAU,KAAK,QAAQ;GACvB,eAAe,KAAK;GAEpB,GAAI,KAAK,eAAe,EAAE,WAAW,KAAK,QAAQ,UAAU,IAAI,CAAC;GACjE,eAAe,KAAK,gBAAgB,CAAC,EAAA,CAAG,SAAS,EAAE,kBACjD,YAAY,SAAS,CAAC;IAAE,IAAI,YAAY;IAAI,QAAQ,YAAY;GAAO,CAAC,IAAI,CAAC,CAC/E;EACF,CAAC,CAAC,CAAC,OAAO,IACV,CAAC;EACL,GAAI,KAAK,gBAAgB,KAAK,mBAC1B,IAAI,gBAAgB;GAClB,MAAM,KAAK;GACX,UAAU,KAAK,QAAQ;GACvB,WAAW,YAAY,KAAK,gBAAgB,iBAAiB,CAAC,CAAC,SAAS,WAAW;EACrF,CAAC,CAAC,CAAC,OAAO,IACV,CAAC;EACL,GAAI,KAAK,eACL,IAAI,eAAe;GACjB,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,UAAU,KAAK,QAAQ;GACvB,WAAW,KAAK,QAAQ;GACxB,eAAe,KAAK;GACpB,UAAU,KAAK,QAAQ;GACvB,aAAa,KAAK,QAAQ;GAC1B;GACA;GACA,cAAc,KAAK;EACrB,CAAC,CAAC,CAAC,OAAO,IACV,CAAC;EACL,GAAI,KAAK,eACL,yBAAyB;GACvB,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,UAAU,KAAK,QAAQ;GACvB,WAAW,KAAK,QAAQ;GACxB,eAAe,KAAK;EACtB,CAAC,IACD,CAAC;CACP;AACF"}
|
|
@@ -45,6 +45,28 @@ export interface OMConfigurableSession {
|
|
|
45
45
|
* the web session boot seed.
|
|
46
46
|
*/
|
|
47
47
|
export declare function applyStoredMemorySettings(session: OMConfigurableSession, record: MemorySettingsRecord | null, fallbackOmModelId?: string): Promise<void>;
|
|
48
|
+
export interface PersonalMemorySettingsArgs {
|
|
49
|
+
/** Without the domain there is no personal preference to read. */
|
|
50
|
+
memorySettings: Pick<MemorySettingsStorage, 'get'> | undefined;
|
|
51
|
+
orgId: string;
|
|
52
|
+
/** The human whose settings apply — for a channel session, the linked sender. */
|
|
53
|
+
userId: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Layer a user's own memory-settings row over whatever the session already runs
|
|
57
|
+
* with, so a personal preference beats the one the project path resolved.
|
|
58
|
+
*
|
|
59
|
+
* The row wins only for the knobs the user has actually saved: an unsaved knob
|
|
60
|
+
* keeps the value already on the session (the project's row, or the
|
|
61
|
+
* provider-aware fallback resolved from the factory default model). That
|
|
62
|
+
* distinction matters because `applyStoredMemorySettings` treats a null knob as
|
|
63
|
+
* "reset to the built-in default", and switching observation onto an
|
|
64
|
+
* uncredentialed provider would fail every observe cycle.
|
|
65
|
+
*
|
|
66
|
+
* Best-effort: a missing row, an uninitialized domain, or a read failure all
|
|
67
|
+
* mean "no personal preference" and leave the session exactly as it was.
|
|
68
|
+
*/
|
|
69
|
+
export declare function applyPersonalMemorySettings(session: OMConfigurableSession, { memorySettings, orgId, userId }: PersonalMemorySettingsArgs): Promise<void>;
|
|
48
70
|
export interface MemorySettingsHydrationSession extends OMConfigurableSession {
|
|
49
71
|
readonly identity: {
|
|
50
72
|
getResourceId(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-settings-hydration.d.ts","sourceRoot":"","sources":["../../src/session/memory-settings-hydration.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC3B,MAAM,4CAA4C,CAAC;AACpD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAG5F,yDAAyD;AACzD,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,2DAA2D;AAC3D,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED;;;;GAIG;AACH,UAAU,aAAa;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,mBAAmB,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mFAAmF;AACnF,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE;QAAE,QAAQ,EAAE,WAAW,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,CAAC;IACtD,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;QAC/C,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KACvD,CAAC;CACH;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED,MAAM,WAAW,8BAA+B,SAAQ,qBAAqB;IAC3E,QAAQ,CAAC,QAAQ,EAAE;QAAE,aAAa,IAAI,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,MAAM,WAAW,mCAAmC;IAClD,4FAA4F;IAC5F,aAAa,EAAE;QACb,QAAQ,EAAE,IAAI,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;KAC1E,CAAC;IACF,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,EACvC,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,mCAAmC,GAC/E,OAAO,CAAC,IAAI,CAAC,CAyCf"}
|
|
1
|
+
{"version":3,"file":"memory-settings-hydration.d.ts","sourceRoot":"","sources":["../../src/session/memory-settings-hydration.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC3B,MAAM,4CAA4C,CAAC;AACpD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAG5F,yDAAyD;AACzD,eAAO,MAAM,6BAA6B,QAAS,CAAC;AACpD,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,2DAA2D;AAC3D,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAED;;;;GAIG;AACH,UAAU,aAAa;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,CAAC,GAAG,EAAE,mBAAmB,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mFAAmF;AACnF,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE;QAAE,QAAQ,EAAE,WAAW,CAAC;QAAC,SAAS,EAAE,WAAW,CAAA;KAAE,CAAC;IACtD,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;QAC/C,GAAG,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KACvD,CAAC;CACH;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED,MAAM,WAAW,0BAA0B;IACzC,kEAAkE;IAClE,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,qBAAqB,EAC9B,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,0BAA0B,GAC5D,OAAO,CAAC,IAAI,CAAC,CAiBf;AAUD,MAAM,WAAW,8BAA+B,SAAQ,qBAAqB;IAC3E,QAAQ,CAAC,QAAQ,EAAE;QAAE,aAAa,IAAI,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,MAAM,WAAW,mCAAmC;IAClD,4FAA4F;IAC5F,aAAa,EAAE;QACb,QAAQ,EAAE,IAAI,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;KAC1E,CAAC;IACF,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,8BAA8B,EACvC,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,mCAAmC,GAC/E,OAAO,CAAC,IAAI,CAAC,CAyCf"}
|
|
@@ -31,6 +31,47 @@ async function applyStoredMemorySettings(session, record, fallbackOmModelId) {
|
|
|
31
31
|
if (Object.keys(updates).length > 0) await session.state.set(updates);
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
+
* Layer a user's own memory-settings row over whatever the session already runs
|
|
35
|
+
* with, so a personal preference beats the one the project path resolved.
|
|
36
|
+
*
|
|
37
|
+
* The row wins only for the knobs the user has actually saved: an unsaved knob
|
|
38
|
+
* keeps the value already on the session (the project's row, or the
|
|
39
|
+
* provider-aware fallback resolved from the factory default model). That
|
|
40
|
+
* distinction matters because `applyStoredMemorySettings` treats a null knob as
|
|
41
|
+
* "reset to the built-in default", and switching observation onto an
|
|
42
|
+
* uncredentialed provider would fail every observe cycle.
|
|
43
|
+
*
|
|
44
|
+
* Best-effort: a missing row, an uninitialized domain, or a read failure all
|
|
45
|
+
* mean "no personal preference" and leave the session exactly as it was.
|
|
46
|
+
*/
|
|
47
|
+
async function applyPersonalMemorySettings(session, { memorySettings, orgId, userId }) {
|
|
48
|
+
if (!memorySettings) return;
|
|
49
|
+
try {
|
|
50
|
+
const record = await memorySettings.get({
|
|
51
|
+
orgId,
|
|
52
|
+
userId
|
|
53
|
+
});
|
|
54
|
+
if (!record) return;
|
|
55
|
+
const state = session.state.get() ?? {};
|
|
56
|
+
await applyStoredMemorySettings(session, {
|
|
57
|
+
...record,
|
|
58
|
+
observerModelId: record.observerModelId ?? session.om.observer.modelId() ?? null,
|
|
59
|
+
reflectorModelId: record.reflectorModelId ?? session.om.reflector.modelId() ?? null,
|
|
60
|
+
observationThreshold: record.observationThreshold ?? storedNumber(state.observationThreshold),
|
|
61
|
+
reflectionThreshold: record.reflectionThreshold ?? storedNumber(state.reflectionThreshold),
|
|
62
|
+
observeAttachments: record.observeAttachments ?? storedObserveAttachments(state.observeAttachments)
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn("[Factory memory-settings hydration] Unable to apply the user's memory settings.", error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function storedNumber(value) {
|
|
69
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
70
|
+
}
|
|
71
|
+
function storedObserveAttachments(value) {
|
|
72
|
+
return value === "auto" || typeof value === "boolean" ? value : null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
34
75
|
* Seed a freshly created controller session's tenant org and its
|
|
35
76
|
* observational-memory settings from the owner's source-control row. Registered
|
|
36
77
|
* as a blocking session-created listener so the seed lands before the caller can
|
|
@@ -83,6 +124,6 @@ async function hydrateSessionMemorySettings(session, { sourceControl, projects,
|
|
|
83
124
|
}
|
|
84
125
|
}
|
|
85
126
|
//#endregion
|
|
86
|
-
export { DEFAULT_OBSERVATION_THRESHOLD, DEFAULT_REFLECTION_THRESHOLD, applyStoredMemorySettings, hydrateSessionMemorySettings };
|
|
127
|
+
export { DEFAULT_OBSERVATION_THRESHOLD, DEFAULT_REFLECTION_THRESHOLD, applyPersonalMemorySettings, applyStoredMemorySettings, hydrateSessionMemorySettings };
|
|
87
128
|
|
|
88
129
|
//# sourceMappingURL=memory-settings-hydration.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-settings-hydration.js","names":[],"sources":["../../src/session/memory-settings-hydration.ts"],"sourcesContent":["import { DEFAULT_OM_MODEL_ID } from '@mastra/code-sdk/constants';\nimport { resolveProviderOMDefault } from '@mastra/code-sdk/onboarding/packs';\n\nimport {\n factoryMemorySettingsUserId,\n type MemorySettingsRecord,\n type MemorySettingsStorage,\n} from '../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { seedSessionOrg } from './org-seed.js';\n\n/** Default thresholds mirror the TUI `/om` fallbacks. */\nexport const DEFAULT_OBSERVATION_THRESHOLD = 30_000;\nexport const DEFAULT_REFLECTION_THRESHOLD = 40_000;\n\n/** One observational-memory role's read/switch surface. */\ninterface OMRoleSlice {\n modelId: () => string | undefined;\n switchModel: (args: { modelId: string }) => Promise<unknown>;\n}\n\n/**\n * Session-state fields memory-settings hydration writes. The index signatures\n * mirror `MastraCodeState` so the concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\ninterface OMStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n factoryOrgId?: string;\n}\n\n/** The slice of a session needed to apply stored observational-memory settings. */\nexport interface OMConfigurableSession {\n om: { observer: OMRoleSlice; reflector: OMRoleSlice };\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OMStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * Apply a stored memory-settings row onto a session, so the DB — not whatever\n * happens to sit in persisted session state (e.g. a stale boot-time seed from\n * before memory settings moved to the DB) — is what the web surface reads and\n * what the session's OM actually runs with. The row is authoritative: knobs\n * without a stored value reset to the built-in defaults. This is the single\n * application path shared by the settings routes, coordinator hydration, and\n * the web session boot seed.\n */\nexport async function applyStoredMemorySettings(\n session: OMConfigurableSession,\n record: MemorySettingsRecord | null,\n fallbackOmModelId?: string,\n): Promise<void> {\n for (const role of ['observer', 'reflector'] as const) {\n const stored = role === 'observer' ? record?.observerModelId : record?.reflectorModelId;\n const target = stored ?? fallbackOmModelId ?? DEFAULT_OM_MODEL_ID;\n if (session.om[role].modelId() !== target) {\n await session.om[role].switchModel({ modelId: target });\n }\n }\n const state = session.state.get() ?? {};\n const updates: OMStateWrites = {};\n const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;\n if (state.observationThreshold !== observationThreshold) {\n updates.observationThreshold = observationThreshold;\n }\n const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;\n if (state.reflectionThreshold !== reflectionThreshold) {\n updates.reflectionThreshold = reflectionThreshold;\n }\n const observeAttachments = record?.observeAttachments ?? 'auto';\n if ((state.observeAttachments ?? 'auto') !== observeAttachments) {\n updates.observeAttachments = observeAttachments;\n }\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n}\n\nexport interface MemorySettingsHydrationSession extends OMConfigurableSession {\n readonly identity: { getResourceId(): string };\n}\n\nexport interface MemorySettingsHydrationDependencies {\n /** GitHub-integration source-control rows — the only creator of web user sessions today. */\n sourceControl: {\n sessions: Pick<SourceControlStorageHandle['sessions'], 'getBySessionId'>;\n };\n projects: Pick<FactoryProjectsStorage, 'get'>;\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Seed a freshly created controller session's tenant org and its\n * observational-memory settings from the owner's source-control row. Registered\n * as a blocking session-created listener so the seed lands before the caller can\n * start a run.\n *\n * The org seed matters beyond settings. Subconscious knowledge curation scopes\n * every node and record on `factoryOrgId`; before the SDK refusal guard,\n * missing it made curation substitute the session owner id. For web chat sessions\n * that is the agent controller's own id rather than a tenant, so curated\n * knowledge landed under an org rung no reader ever queries. Same rule as the\n * start coordinator: the org\n * comes from the row the session was created from, never improvised from an\n * owner id.\n *\n * Tagged sessions may be coordinator-owned runs or web sessions that only carry\n * browser-seeded Factory state. This path resolves their source-control row and\n * reapplies a stored project settings row when one exists. If no project row\n * exists, it preserves the coordinator's provider-aware fallback.\n * Sessions without a GitHub source-control row (e.g. chat-only channel sessions)\n * hydrate through `hydrateFactorySession` with their own resolved tenant.\n * Best-effort: failures are logged, never thrown.\n */\nexport async function hydrateSessionMemorySettings(\n session: MemorySettingsHydrationSession,\n { sourceControl, projects, memorySettings }: MemorySettingsHydrationDependencies,\n): Promise<void> {\n const state = session.state.get() ?? {};\n const isFactoryRun = Boolean(state.factoryProjectId);\n try {\n const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());\n // No row, or a row whose org is blank, leaves the session with no tenant.\n // Mark it rather than returning silently: an unmarked projectless factory\n // session is indistinguishable from a local one, and curation would file it\n // under the local scope — the same bug wearing a different rung.\n await seedSessionOrg(session, record?.orgId);\n if (!record) return;\n const factoryProjectId = isFactoryRun ? String(state.factoryProjectId) : undefined;\n const settings = await memorySettings.get({\n orgId: record.orgId,\n userId: factoryProjectId ? factoryMemorySettingsUserId(factoryProjectId) : record.userId,\n });\n // Coordinator hydration applies a provider-aware fallback when no project row\n // exists. Do not replace that fallback with the generic OM default here.\n if (factoryProjectId && !settings) return;\n const project = factoryProjectId ? await projects.get({ orgId: record.orgId, id: factoryProjectId }) : null;\n const provider = project?.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider\n ? resolveProviderOMDefault(provider, project.defaultModelId ?? undefined).modelId\n : undefined;\n await applyStoredMemorySettings(\n session,\n factoryProjectId && settings && !fallbackOmModelId\n ? {\n ...settings,\n observerModelId: settings?.observerModelId ?? session.om.observer.modelId() ?? null,\n reflectorModelId: settings?.reflectorModelId ?? session.om.reflector.modelId() ?? null,\n }\n : settings,\n fallbackOmModelId,\n );\n } catch (error) {\n console.warn('[Factory memory-settings hydration] Unable to apply stored memory settings.', error);\n // A failed lookup is an unresolved org, not an absent one — unless the seed\n // already landed and a later step is what threw.\n if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, undefined);\n }\n}\n"],"mappings":";;;;;;AAaA,MAAa,gCAAgC;AAC7C,MAAa,+BAA+B;;;;;;;;;;AAwC5C,eAAsB,0BACpB,SACA,QACA,mBACe;CACf,KAAK,MAAM,QAAQ,CAAC,YAAY,WAAW,GAAY;EAErD,MAAM,UADS,SAAS,aAAa,QAAQ,kBAAkB,QAAQ,qBAC9C,qBAAqB;EAC9C,IAAI,QAAQ,GAAG,KAAK,CAAC,QAAQ,MAAM,QACjC,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,EAAE,SAAS,OAAO,CAAC;CAE1D;CACA,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,UAAyB,CAAC;CAChC,MAAM,uBAAuB,QAAQ,wBAAA;CACrC,IAAI,MAAM,yBAAyB,sBACjC,QAAQ,uBAAuB;CAEjC,MAAM,sBAAsB,QAAQ,uBAAA;CACpC,IAAI,MAAM,wBAAwB,qBAChC,QAAQ,sBAAsB;CAEhC,MAAM,qBAAqB,QAAQ,sBAAsB;CACzD,KAAK,MAAM,sBAAsB,YAAY,oBAC3C,QAAQ,qBAAqB;CAE/B,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,MAAM,QAAQ,MAAM,IAAI,OAAO;AACtE;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,eAAsB,6BACpB,SACA,EAAE,eAAe,UAAU,kBACZ;CACf,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,CAAC;CACtC,MAAM,eAAe,QAAQ,MAAM,gBAAgB;CACnD,IAAI;EACF,MAAM,SAAS,MAAM,cAAc,SAAS,eAAe,QAAQ,SAAS,cAAc,CAAC;EAK3F,MAAM,eAAe,SAAS,QAAQ,KAAK;EAC3C,IAAI,CAAC,QAAQ;EACb,MAAM,mBAAmB,eAAe,OAAO,MAAM,gBAAgB,IAAI,KAAA;EACzE,MAAM,WAAW,MAAM,eAAe,IAAI;GACxC,OAAO,OAAO;GACd,QAAQ,mBAAmB,4BAA4B,gBAAgB,IAAI,OAAO;EACpF,CAAC;EAGD,IAAI,oBAAoB,CAAC,UAAU;EACnC,MAAM,UAAU,mBAAmB,MAAM,SAAS,IAAI;GAAE,OAAO,OAAO;GAAO,IAAI;EAAiB,CAAC,IAAI;EACvG,MAAM,WAAW,SAAS,gBAAgB,MAAM,GAAG,CAAC,CAAC;EACrD,MAAM,oBAAoB,WACtB,yBAAyB,UAAU,QAAQ,kBAAkB,KAAA,CAAS,CAAC,CAAC,UACxE,KAAA;EACJ,MAAM,0BACJ,SACA,oBAAoB,YAAY,CAAC,oBAC7B;GACE,GAAG;GACH,iBAAiB,UAAU,mBAAmB,QAAQ,GAAG,SAAS,QAAQ,KAAK;GAC/E,kBAAkB,UAAU,oBAAoB,QAAQ,GAAG,UAAU,QAAQ,KAAK;EACpF,IACA,UACJ,iBACF;CACF,SAAS,OAAO;EACd,QAAQ,KAAK,+EAA+E,KAAK;EAGjG,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,EAAE,cAAc,MAAM,eAAe,SAAS,KAAA,CAAS;CACjF;AACF"}
|
|
1
|
+
{"version":3,"file":"memory-settings-hydration.js","names":[],"sources":["../../src/session/memory-settings-hydration.ts"],"sourcesContent":["import { DEFAULT_OM_MODEL_ID } from '@mastra/code-sdk/constants';\nimport { resolveProviderOMDefault } from '@mastra/code-sdk/onboarding/packs';\n\nimport {\n factoryMemorySettingsUserId,\n type MemorySettingsRecord,\n type MemorySettingsStorage,\n} from '../storage/domains/memory-settings/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport { seedSessionOrg } from './org-seed.js';\n\n/** Default thresholds mirror the TUI `/om` fallbacks. */\nexport const DEFAULT_OBSERVATION_THRESHOLD = 30_000;\nexport const DEFAULT_REFLECTION_THRESHOLD = 40_000;\n\n/** One observational-memory role's read/switch surface. */\ninterface OMRoleSlice {\n modelId: () => string | undefined;\n switchModel: (args: { modelId: string }) => Promise<unknown>;\n}\n\n/**\n * Session-state fields memory-settings hydration writes. The index signatures\n * mirror `MastraCodeState` so the concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\ninterface OMStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n factoryOrgId?: string;\n}\n\n/** The slice of a session needed to apply stored observational-memory settings. */\nexport interface OMConfigurableSession {\n om: { observer: OMRoleSlice; reflector: OMRoleSlice };\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OMStateWrites) => Promise<void> | void;\n };\n}\n\n/**\n * Apply a stored memory-settings row onto a session, so the DB — not whatever\n * happens to sit in persisted session state (e.g. a stale boot-time seed from\n * before memory settings moved to the DB) — is what the web surface reads and\n * what the session's OM actually runs with. The row is authoritative: knobs\n * without a stored value reset to the built-in defaults. This is the single\n * application path shared by the settings routes, coordinator hydration, and\n * the web session boot seed.\n */\nexport async function applyStoredMemorySettings(\n session: OMConfigurableSession,\n record: MemorySettingsRecord | null,\n fallbackOmModelId?: string,\n): Promise<void> {\n for (const role of ['observer', 'reflector'] as const) {\n const stored = role === 'observer' ? record?.observerModelId : record?.reflectorModelId;\n const target = stored ?? fallbackOmModelId ?? DEFAULT_OM_MODEL_ID;\n if (session.om[role].modelId() !== target) {\n await session.om[role].switchModel({ modelId: target });\n }\n }\n const state = session.state.get() ?? {};\n const updates: OMStateWrites = {};\n const observationThreshold = record?.observationThreshold ?? DEFAULT_OBSERVATION_THRESHOLD;\n if (state.observationThreshold !== observationThreshold) {\n updates.observationThreshold = observationThreshold;\n }\n const reflectionThreshold = record?.reflectionThreshold ?? DEFAULT_REFLECTION_THRESHOLD;\n if (state.reflectionThreshold !== reflectionThreshold) {\n updates.reflectionThreshold = reflectionThreshold;\n }\n const observeAttachments = record?.observeAttachments ?? 'auto';\n if ((state.observeAttachments ?? 'auto') !== observeAttachments) {\n updates.observeAttachments = observeAttachments;\n }\n if (Object.keys(updates).length > 0) await session.state.set(updates);\n}\n\nexport interface PersonalMemorySettingsArgs {\n /** Without the domain there is no personal preference to read. */\n memorySettings: Pick<MemorySettingsStorage, 'get'> | undefined;\n orgId: string;\n /** The human whose settings apply — for a channel session, the linked sender. */\n userId: string;\n}\n\n/**\n * Layer a user's own memory-settings row over whatever the session already runs\n * with, so a personal preference beats the one the project path resolved.\n *\n * The row wins only for the knobs the user has actually saved: an unsaved knob\n * keeps the value already on the session (the project's row, or the\n * provider-aware fallback resolved from the factory default model). That\n * distinction matters because `applyStoredMemorySettings` treats a null knob as\n * \"reset to the built-in default\", and switching observation onto an\n * uncredentialed provider would fail every observe cycle.\n *\n * Best-effort: a missing row, an uninitialized domain, or a read failure all\n * mean \"no personal preference\" and leave the session exactly as it was.\n */\nexport async function applyPersonalMemorySettings(\n session: OMConfigurableSession,\n { memorySettings, orgId, userId }: PersonalMemorySettingsArgs,\n): Promise<void> {\n if (!memorySettings) return;\n try {\n const record = await memorySettings.get({ orgId, userId });\n if (!record) return;\n const state = session.state.get() ?? {};\n await applyStoredMemorySettings(session, {\n ...record,\n observerModelId: record.observerModelId ?? session.om.observer.modelId() ?? null,\n reflectorModelId: record.reflectorModelId ?? session.om.reflector.modelId() ?? null,\n observationThreshold: record.observationThreshold ?? storedNumber(state.observationThreshold),\n reflectionThreshold: record.reflectionThreshold ?? storedNumber(state.reflectionThreshold),\n observeAttachments: record.observeAttachments ?? storedObserveAttachments(state.observeAttachments),\n });\n } catch (error) {\n console.warn(\"[Factory memory-settings hydration] Unable to apply the user's memory settings.\", error);\n }\n}\n\nfunction storedNumber(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null;\n}\n\nfunction storedObserveAttachments(value: unknown): 'auto' | boolean | null {\n return value === 'auto' || typeof value === 'boolean' ? value : null;\n}\n\nexport interface MemorySettingsHydrationSession extends OMConfigurableSession {\n readonly identity: { getResourceId(): string };\n}\n\nexport interface MemorySettingsHydrationDependencies {\n /** GitHub-integration source-control rows — the only creator of web user sessions today. */\n sourceControl: {\n sessions: Pick<SourceControlStorageHandle['sessions'], 'getBySessionId'>;\n };\n projects: Pick<FactoryProjectsStorage, 'get'>;\n memorySettings: Pick<MemorySettingsStorage, 'get'>;\n}\n\n/**\n * Seed a freshly created controller session's tenant org and its\n * observational-memory settings from the owner's source-control row. Registered\n * as a blocking session-created listener so the seed lands before the caller can\n * start a run.\n *\n * The org seed matters beyond settings. Subconscious knowledge curation scopes\n * every node and record on `factoryOrgId`; before the SDK refusal guard,\n * missing it made curation substitute the session owner id. For web chat sessions\n * that is the agent controller's own id rather than a tenant, so curated\n * knowledge landed under an org rung no reader ever queries. Same rule as the\n * start coordinator: the org\n * comes from the row the session was created from, never improvised from an\n * owner id.\n *\n * Tagged sessions may be coordinator-owned runs or web sessions that only carry\n * browser-seeded Factory state. This path resolves their source-control row and\n * reapplies a stored project settings row when one exists. If no project row\n * exists, it preserves the coordinator's provider-aware fallback.\n * Sessions without a GitHub source-control row (e.g. chat-only channel sessions)\n * hydrate through `hydrateFactorySession` with their own resolved tenant.\n * Best-effort: failures are logged, never thrown.\n */\nexport async function hydrateSessionMemorySettings(\n session: MemorySettingsHydrationSession,\n { sourceControl, projects, memorySettings }: MemorySettingsHydrationDependencies,\n): Promise<void> {\n const state = session.state.get() ?? {};\n const isFactoryRun = Boolean(state.factoryProjectId);\n try {\n const record = await sourceControl.sessions.getBySessionId(session.identity.getResourceId());\n // No row, or a row whose org is blank, leaves the session with no tenant.\n // Mark it rather than returning silently: an unmarked projectless factory\n // session is indistinguishable from a local one, and curation would file it\n // under the local scope — the same bug wearing a different rung.\n await seedSessionOrg(session, record?.orgId);\n if (!record) return;\n const factoryProjectId = isFactoryRun ? String(state.factoryProjectId) : undefined;\n const settings = await memorySettings.get({\n orgId: record.orgId,\n userId: factoryProjectId ? factoryMemorySettingsUserId(factoryProjectId) : record.userId,\n });\n // Coordinator hydration applies a provider-aware fallback when no project row\n // exists. Do not replace that fallback with the generic OM default here.\n if (factoryProjectId && !settings) return;\n const project = factoryProjectId ? await projects.get({ orgId: record.orgId, id: factoryProjectId }) : null;\n const provider = project?.defaultModelId?.split('/')[0];\n const fallbackOmModelId = provider\n ? resolveProviderOMDefault(provider, project.defaultModelId ?? undefined).modelId\n : undefined;\n await applyStoredMemorySettings(\n session,\n factoryProjectId && settings && !fallbackOmModelId\n ? {\n ...settings,\n observerModelId: settings?.observerModelId ?? session.om.observer.modelId() ?? null,\n reflectorModelId: settings?.reflectorModelId ?? session.om.reflector.modelId() ?? null,\n }\n : settings,\n fallbackOmModelId,\n );\n } catch (error) {\n console.warn('[Factory memory-settings hydration] Unable to apply stored memory settings.', error);\n // A failed lookup is an unresolved org, not an absent one — unless the seed\n // already landed and a later step is what threw.\n if (!session.state.get()?.factoryOrgId) await seedSessionOrg(session, undefined);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/comments/routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/comments/routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAiB,MAAM,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAOpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,aAAa,CAAC;AAWjE,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAWD,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,wBAAwB,GAAG,QAAQ,EAAE,CAqSrF"}
|
|
@@ -5,8 +5,14 @@ import { decodeCommentCursor } from "./base.js";
|
|
|
5
5
|
import { UUID_RE, isRecord, parseCreateCommentBody, parseEditCommentBody, readJson } from "./parse.js";
|
|
6
6
|
import { toWireComment } from "./wire.js";
|
|
7
7
|
import { registerApiRoute } from "@mastra/core/server";
|
|
8
|
+
import { withAck } from "@mastra/core/events";
|
|
8
9
|
import { streamSSE } from "hono/streaming";
|
|
9
10
|
//#region src/storage/domains/comments/routes.ts
|
|
11
|
+
/**
|
|
12
|
+
* HTTP for the item feed. Every handler is the same shape: resolve the tenant,
|
|
13
|
+
* validate the path ids, delegate to a `CommentsDomain` service method, map its
|
|
14
|
+
* result status to a status code, then emit audit.
|
|
15
|
+
*/
|
|
10
16
|
const MAX_AUDIT_BODY_SNAPSHOT = 1024;
|
|
11
17
|
/** Comment frames only — proxies drop an idle stream, and `write` can't detect a dead peer. */
|
|
12
18
|
const FEED_KEEPALIVE_MS = 25e3;
|
|
@@ -259,15 +265,17 @@ function buildCommentRoutes(dependencies) {
|
|
|
259
265
|
})) return c.json({ error: "Project not found" }, 404);
|
|
260
266
|
const topic = feedTopic(tenant.orgId, projectId);
|
|
261
267
|
return streamSSE(c, async (stream) => {
|
|
262
|
-
const onEvent = async (event) => {
|
|
268
|
+
const onEvent = withAck(async (event) => {
|
|
263
269
|
if (stream.aborted) return;
|
|
264
270
|
const data = event.data;
|
|
265
271
|
const workItemId = isRecord(data) && typeof data.workItemId === "string" ? data.workItemId : void 0;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
try {
|
|
273
|
+
await stream.writeSSE({
|
|
274
|
+
event: "feed",
|
|
275
|
+
data: JSON.stringify(workItemId ? { workItemId } : {})
|
|
276
|
+
});
|
|
277
|
+
} catch {}
|
|
278
|
+
});
|
|
271
279
|
const closed = new Promise((resolve) => stream.onAbort(resolve));
|
|
272
280
|
let keepalive;
|
|
273
281
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","names":[],"sources":["../../../../src/storage/domains/comments/routes.ts"],"sourcesContent":["/**\n * HTTP for the item feed. Every handler is the same shape: resolve the tenant,\n * validate the path ids, delegate to a `CommentsDomain` service method, map its\n * result status to a status code, then emit audit.\n */\n\nimport type { EventCallback, PubSub } from '@mastra/core/events';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\nimport { streamSSE } from 'hono/streaming';\n\nimport { getFactoryAuthUser } from '../../../auth.js';\nimport { feedTopic } from '../../../feed-events.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { AuditEmitter } from '../audit/domain.js';\nimport type { FactoryProjectsStorage } from '../projects/base.js';\nimport type { WorkItemsStorage } from '../work-items/base.js';\nimport { actorFromAuthUser } from './actor.js';\nimport type { WorkItemCommentsStorage } from './base.js';\nimport { decodeCommentCursor } from './base.js';\nimport type { CommentEditor, CommentsDomain } from './domain.js';\nimport { isRecord, parseCreateCommentBody, parseEditCommentBody, readJson, UUID_RE } from './parse.js';\nimport { toWireComment } from './wire.js';\nimport type { WireCommentPage } from './wire.js';\n\nconst MAX_AUDIT_BODY_SNAPSHOT = 1024;\n/** Comment frames only — proxies drop an idle stream, and `write` can't detect a dead peer. */\nconst FEED_KEEPALIVE_MS = 25_000;\n// Ceiling: one broker subscription per open tab, not per project. A per-replica\n// topic multiplexer is the upgrade path if tab counts ever make that hurt.\n\nexport interface CommentRouteDependencies {\n domain: CommentsDomain;\n auth: RouteAuth;\n comments: WorkItemCommentsStorage;\n workItems: WorkItemsStorage;\n projects: FactoryProjectsStorage;\n pubsub: PubSub;\n audit?: AuditEmitter;\n}\n\ninterface Tenant {\n orgId: string;\n userId: string;\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nexport function buildCommentRoutes(dependencies: CommentRouteDependencies): ApiRoute[] {\n const { domain, auth, comments, workItems, projects, pubsub, audit } = dependencies;\n\n async function resolveTenant(c: Context): Promise<Tenant | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json({ error: 'organization_required', message: 'The item feed requires an organization.' }, 403),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n function editorFor(c: Context, tenant: Tenant): CommentEditor {\n return {\n userId: tenant.userId,\n canModerate: () => auth.isOrganizationAdmin(c, tenant.orgId),\n };\n }\n\n async function emitMentionAudit(\n c: Context,\n target: { factoryProjectId: string; workItemId: string; title?: string },\n commentId: string,\n mentionedIds: string[],\n ): Promise<void> {\n if (mentionedIds.length === 0) return;\n await audit?.emit({\n context: c,\n input: {\n action: 'factory.work_item.comment_mentioned',\n factoryProjectId: target.factoryProjectId,\n targets: [{ type: 'work_item', id: target.workItemId, ...(target.title ? { name: target.title } : {}) }],\n metadata: { commentId, mentionedIds },\n },\n });\n }\n\n return [\n registerApiRoute('/web/factory/work-items/:workItemId/comments', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const workItemId = c.req.param('workItemId');\n if (!workItemId || !UUID_RE.test(workItemId)) return c.json({ error: 'Work item not found' }, 404);\n await workItems.ensureReady();\n const workItem = await workItems.get({ orgId: tenant.orgId, id: workItemId });\n if (!workItem) return c.json({ error: 'Work item not found' }, 404);\n\n await comments.ensureReady();\n const limitRaw = c.req.query('limit');\n const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined;\n const before = c.req.query('before') || undefined;\n if (before && !decodeCommentCursor(before)) return c.json({ error: 'invalid_cursor' }, 422);\n const around = c.req.query('around') || undefined;\n if (around && !UUID_RE.test(around)) return c.json({ error: 'invalid_comment_id' }, 422);\n const page = await comments.list({\n orgId: tenant.orgId,\n factoryProjectId: workItem.factoryProjectId,\n workItemId,\n before,\n ...(around ? { around } : {}),\n ...(limit !== undefined && Number.isFinite(limit) ? { limit } : {}),\n });\n const wirePage: WireCommentPage = {\n comments: page.comments.map(comment => toWireComment(comment, tenant.userId)),\n ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),\n };\n return c.json(wirePage);\n },\n }),\n\n registerApiRoute('/web/factory/work-items/:workItemId/comments', {\n method: 'POST',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const workItemId = c.req.param('workItemId');\n if (!workItemId || !UUID_RE.test(workItemId)) return c.json({ error: 'Work item not found' }, 404);\n\n const parsed = parseCreateCommentBody(await readJson(c));\n if (!parsed) return c.json({ error: 'invalid_comment' }, 422);\n\n const result = await domain.createComment({\n orgId: tenant.orgId,\n workItemId,\n author: actorFromAuthUser(tenant.userId, getFactoryAuthUser(c)),\n body: parsed.body,\n ...(parsed.replyTo ? { replyTo: parsed.replyTo } : {}),\n ...(parsed.mentions ? { mentions: parsed.mentions } : {}),\n ...(parsed.clientToken ? { clientToken: parsed.clientToken } : {}),\n });\n if (result.status === 'work_item_not_found') return c.json({ error: 'Work item not found' }, 404);\n if (result.status === 'token_conflict') return c.json({ error: 'comment_token_conflict' }, 409);\n if (result.status === 'invalid') return c.json({ error: 'invalid_comment', message: result.message }, 422);\n\n const { comment, workItem } = result;\n await audit?.emit({\n context: c,\n input: {\n action: 'factory.work_item.comment_created',\n factoryProjectId: workItem.factoryProjectId,\n targets: [{ type: 'work_item', id: workItem.id, name: workItem.title }],\n metadata: { commentId: comment.id },\n },\n });\n await emitMentionAudit(\n c,\n { factoryProjectId: workItem.factoryProjectId, workItemId: workItem.id, title: workItem.title },\n comment.id,\n comment.mentions.map(mention => mention.id).filter(id => id !== comment.author.id),\n );\n return c.json({ comment: toWireComment(comment, tenant.userId) }, 201);\n },\n }),\n\n registerApiRoute('/web/factory/work-items/:workItemId/comments/:commentId', {\n method: 'PATCH',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const workItemId = c.req.param('workItemId');\n const commentId = c.req.param('commentId');\n if (!workItemId || !UUID_RE.test(workItemId) || !commentId || !UUID_RE.test(commentId)) {\n return c.json({ error: 'Comment not found' }, 404);\n }\n\n const parsed = parseEditCommentBody(await readJson(c));\n if (!parsed) return c.json({ error: 'invalid_comment' }, 422);\n\n const result = await domain.editComment({\n orgId: tenant.orgId,\n workItemId,\n commentId,\n editor: editorFor(c, tenant),\n body: parsed.body,\n ...(parsed.mentions ? { mentions: parsed.mentions } : {}),\n ...(parsed.expectedRevision !== undefined ? { expectedRevision: parsed.expectedRevision } : {}),\n });\n if (result.status === 'not_found') return c.json({ error: 'Comment not found' }, 404);\n if (result.status === 'forbidden') return c.json({ error: 'not_comment_author' }, 403);\n if (result.status === 'not_editable') return c.json({ error: 'comment_not_editable' }, 409);\n if (result.status === 'conflict') return c.json({ error: 'comment_conflict' }, 409);\n if (result.status === 'invalid') return c.json({ error: 'invalid_comment', message: result.message }, 422);\n\n const { comment } = result;\n await audit?.emit({\n context: c,\n input: {\n action: 'factory.work_item.comment_edited',\n factoryProjectId: comment.factoryProjectId,\n targets: [{ type: 'work_item', id: comment.workItemId }],\n metadata: {\n commentId: comment.id,\n previousBody: result.previousBody.slice(0, MAX_AUDIT_BODY_SNAPSHOT),\n },\n },\n });\n await emitMentionAudit(\n c,\n { factoryProjectId: comment.factoryProjectId, workItemId: comment.workItemId },\n comment.id,\n result.addedMentions.map(mention => mention.id),\n );\n return c.json({ comment: toWireComment(comment, tenant.userId) });\n },\n }),\n\n registerApiRoute('/web/factory/work-items/:workItemId/comments/:commentId', {\n method: 'DELETE',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const workItemId = c.req.param('workItemId');\n const commentId = c.req.param('commentId');\n if (!workItemId || !UUID_RE.test(workItemId) || !commentId || !UUID_RE.test(commentId)) {\n return c.json({ error: 'Comment not found' }, 404);\n }\n\n const result = await domain.deleteComment({\n orgId: tenant.orgId,\n workItemId,\n commentId,\n editor: editorFor(c, tenant),\n });\n if (result.status === 'not_found') return c.json({ error: 'Comment not found' }, 404);\n if (result.status === 'forbidden') return c.json({ error: 'not_comment_author' }, 403);\n if (result.status === 'not_editable') return c.json({ error: 'comment_not_editable' }, 409);\n\n await audit?.emit({\n context: c,\n input: {\n action: 'factory.work_item.comment_deleted',\n factoryProjectId: result.comment.factoryProjectId,\n targets: [{ type: 'work_item', id: result.comment.workItemId }],\n metadata: { commentId: result.comment.id },\n },\n });\n return c.json({ comment: toWireComment(result.comment, tenant.userId) });\n },\n }),\n\n registerApiRoute('/web/factory/projects/:id/mention-roster', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: 'Project not found' }, 404);\n await projects.ensureReady();\n const project = await projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) return c.json({ error: 'Project not found' }, 404);\n\n await comments.ensureReady();\n const roster = await domain.mentionRoster({ orgId: tenant.orgId, factoryProjectId: projectId });\n const query = c.req.query('q')?.trim().toLowerCase();\n const members = query\n ? roster.filter(member => (member.name ?? member.id).toLowerCase().startsWith(query))\n : roster;\n return c.json({ members });\n },\n }),\n\n registerApiRoute('/web/factory/projects/:id/feed-events', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: 'Project not found' }, 404);\n await projects.ensureReady();\n const project = await projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) return c.json({ error: 'Project not found' }, 404);\n\n const topic = feedTopic(tenant.orgId, projectId);\n return streamSSE(c, async stream => {\n const onEvent: EventCallback = async event => {\n if (stream.aborted) return;\n const data = event.data;\n const workItemId = isRecord(data) && typeof data.workItemId === 'string' ? data.workItemId : undefined;\n await stream.writeSSE({ event: 'feed', data: JSON.stringify(workItemId ? { workItemId } : {}) });\n };\n // Claimed before any await: `onAbort` handlers registered after the\n // reader is gone never run, and a broker subscribe is a round trip.\n const closed = new Promise<void>(resolve => stream.onAbort(resolve));\n let keepalive: ReturnType<typeof setInterval> | undefined;\n try {\n // Without `latest`, a retaining broker replays its whole backlog into\n // every new connection as spurious invalidations.\n await pubsub.subscribe(topic, onEvent, { startFrom: 'latest' });\n if (!stream.aborted) {\n keepalive = setInterval(() => {\n if (stream.aborted) return;\n void stream.write(': ping\\n\\n');\n }, FEED_KEEPALIVE_MS);\n // `streamSSE` closes the stream the moment this callback returns.\n await closed;\n }\n } finally {\n clearInterval(keepalive);\n await pubsub.unsubscribe(topic, onEvent);\n }\n });\n },\n }),\n ];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"routes.js","names":[],"sources":["../../../../src/storage/domains/comments/routes.ts"],"sourcesContent":["/**\n * HTTP for the item feed. Every handler is the same shape: resolve the tenant,\n * validate the path ids, delegate to a `CommentsDomain` service method, map its\n * result status to a status code, then emit audit.\n */\n\nimport { withAck } from '@mastra/core/events';\nimport type { EventCallback, PubSub } from '@mastra/core/events';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\nimport { streamSSE } from 'hono/streaming';\n\nimport { getFactoryAuthUser } from '../../../auth.js';\nimport { feedTopic } from '../../../feed-events.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { AuditEmitter } from '../audit/domain.js';\nimport type { FactoryProjectsStorage } from '../projects/base.js';\nimport type { WorkItemsStorage } from '../work-items/base.js';\nimport { actorFromAuthUser } from './actor.js';\nimport type { WorkItemCommentsStorage } from './base.js';\nimport { decodeCommentCursor } from './base.js';\nimport type { CommentEditor, CommentsDomain } from './domain.js';\nimport { isRecord, parseCreateCommentBody, parseEditCommentBody, readJson, UUID_RE } from './parse.js';\nimport { toWireComment } from './wire.js';\nimport type { WireCommentPage } from './wire.js';\n\nconst MAX_AUDIT_BODY_SNAPSHOT = 1024;\n/** Comment frames only — proxies drop an idle stream, and `write` can't detect a dead peer. */\nconst FEED_KEEPALIVE_MS = 25_000;\n// Ceiling: one broker subscription per open tab, not per project. A per-replica\n// topic multiplexer is the upgrade path if tab counts ever make that hurt.\n\nexport interface CommentRouteDependencies {\n domain: CommentsDomain;\n auth: RouteAuth;\n comments: WorkItemCommentsStorage;\n workItems: WorkItemsStorage;\n projects: FactoryProjectsStorage;\n pubsub: PubSub;\n audit?: AuditEmitter;\n}\n\ninterface Tenant {\n orgId: string;\n userId: string;\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nexport function buildCommentRoutes(dependencies: CommentRouteDependencies): ApiRoute[] {\n const { domain, auth, comments, workItems, projects, pubsub, audit } = dependencies;\n\n async function resolveTenant(c: Context): Promise<Tenant | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json({ error: 'organization_required', message: 'The item feed requires an organization.' }, 403),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n function editorFor(c: Context, tenant: Tenant): CommentEditor {\n return {\n userId: tenant.userId,\n canModerate: () => auth.isOrganizationAdmin(c, tenant.orgId),\n };\n }\n\n async function emitMentionAudit(\n c: Context,\n target: { factoryProjectId: string; workItemId: string; title?: string },\n commentId: string,\n mentionedIds: string[],\n ): Promise<void> {\n if (mentionedIds.length === 0) return;\n await audit?.emit({\n context: c,\n input: {\n action: 'factory.work_item.comment_mentioned',\n factoryProjectId: target.factoryProjectId,\n targets: [{ type: 'work_item', id: target.workItemId, ...(target.title ? { name: target.title } : {}) }],\n metadata: { commentId, mentionedIds },\n },\n });\n }\n\n return [\n registerApiRoute('/web/factory/work-items/:workItemId/comments', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const workItemId = c.req.param('workItemId');\n if (!workItemId || !UUID_RE.test(workItemId)) return c.json({ error: 'Work item not found' }, 404);\n await workItems.ensureReady();\n const workItem = await workItems.get({ orgId: tenant.orgId, id: workItemId });\n if (!workItem) return c.json({ error: 'Work item not found' }, 404);\n\n await comments.ensureReady();\n const limitRaw = c.req.query('limit');\n const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined;\n const before = c.req.query('before') || undefined;\n if (before && !decodeCommentCursor(before)) return c.json({ error: 'invalid_cursor' }, 422);\n const around = c.req.query('around') || undefined;\n if (around && !UUID_RE.test(around)) return c.json({ error: 'invalid_comment_id' }, 422);\n const page = await comments.list({\n orgId: tenant.orgId,\n factoryProjectId: workItem.factoryProjectId,\n workItemId,\n before,\n ...(around ? { around } : {}),\n ...(limit !== undefined && Number.isFinite(limit) ? { limit } : {}),\n });\n const wirePage: WireCommentPage = {\n comments: page.comments.map(comment => toWireComment(comment, tenant.userId)),\n ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),\n };\n return c.json(wirePage);\n },\n }),\n\n registerApiRoute('/web/factory/work-items/:workItemId/comments', {\n method: 'POST',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const workItemId = c.req.param('workItemId');\n if (!workItemId || !UUID_RE.test(workItemId)) return c.json({ error: 'Work item not found' }, 404);\n\n const parsed = parseCreateCommentBody(await readJson(c));\n if (!parsed) return c.json({ error: 'invalid_comment' }, 422);\n\n const result = await domain.createComment({\n orgId: tenant.orgId,\n workItemId,\n author: actorFromAuthUser(tenant.userId, getFactoryAuthUser(c)),\n body: parsed.body,\n ...(parsed.replyTo ? { replyTo: parsed.replyTo } : {}),\n ...(parsed.mentions ? { mentions: parsed.mentions } : {}),\n ...(parsed.clientToken ? { clientToken: parsed.clientToken } : {}),\n });\n if (result.status === 'work_item_not_found') return c.json({ error: 'Work item not found' }, 404);\n if (result.status === 'token_conflict') return c.json({ error: 'comment_token_conflict' }, 409);\n if (result.status === 'invalid') return c.json({ error: 'invalid_comment', message: result.message }, 422);\n\n const { comment, workItem } = result;\n await audit?.emit({\n context: c,\n input: {\n action: 'factory.work_item.comment_created',\n factoryProjectId: workItem.factoryProjectId,\n targets: [{ type: 'work_item', id: workItem.id, name: workItem.title }],\n metadata: { commentId: comment.id },\n },\n });\n await emitMentionAudit(\n c,\n { factoryProjectId: workItem.factoryProjectId, workItemId: workItem.id, title: workItem.title },\n comment.id,\n comment.mentions.map(mention => mention.id).filter(id => id !== comment.author.id),\n );\n return c.json({ comment: toWireComment(comment, tenant.userId) }, 201);\n },\n }),\n\n registerApiRoute('/web/factory/work-items/:workItemId/comments/:commentId', {\n method: 'PATCH',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const workItemId = c.req.param('workItemId');\n const commentId = c.req.param('commentId');\n if (!workItemId || !UUID_RE.test(workItemId) || !commentId || !UUID_RE.test(commentId)) {\n return c.json({ error: 'Comment not found' }, 404);\n }\n\n const parsed = parseEditCommentBody(await readJson(c));\n if (!parsed) return c.json({ error: 'invalid_comment' }, 422);\n\n const result = await domain.editComment({\n orgId: tenant.orgId,\n workItemId,\n commentId,\n editor: editorFor(c, tenant),\n body: parsed.body,\n ...(parsed.mentions ? { mentions: parsed.mentions } : {}),\n ...(parsed.expectedRevision !== undefined ? { expectedRevision: parsed.expectedRevision } : {}),\n });\n if (result.status === 'not_found') return c.json({ error: 'Comment not found' }, 404);\n if (result.status === 'forbidden') return c.json({ error: 'not_comment_author' }, 403);\n if (result.status === 'not_editable') return c.json({ error: 'comment_not_editable' }, 409);\n if (result.status === 'conflict') return c.json({ error: 'comment_conflict' }, 409);\n if (result.status === 'invalid') return c.json({ error: 'invalid_comment', message: result.message }, 422);\n\n const { comment } = result;\n await audit?.emit({\n context: c,\n input: {\n action: 'factory.work_item.comment_edited',\n factoryProjectId: comment.factoryProjectId,\n targets: [{ type: 'work_item', id: comment.workItemId }],\n metadata: {\n commentId: comment.id,\n previousBody: result.previousBody.slice(0, MAX_AUDIT_BODY_SNAPSHOT),\n },\n },\n });\n await emitMentionAudit(\n c,\n { factoryProjectId: comment.factoryProjectId, workItemId: comment.workItemId },\n comment.id,\n result.addedMentions.map(mention => mention.id),\n );\n return c.json({ comment: toWireComment(comment, tenant.userId) });\n },\n }),\n\n registerApiRoute('/web/factory/work-items/:workItemId/comments/:commentId', {\n method: 'DELETE',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const workItemId = c.req.param('workItemId');\n const commentId = c.req.param('commentId');\n if (!workItemId || !UUID_RE.test(workItemId) || !commentId || !UUID_RE.test(commentId)) {\n return c.json({ error: 'Comment not found' }, 404);\n }\n\n const result = await domain.deleteComment({\n orgId: tenant.orgId,\n workItemId,\n commentId,\n editor: editorFor(c, tenant),\n });\n if (result.status === 'not_found') return c.json({ error: 'Comment not found' }, 404);\n if (result.status === 'forbidden') return c.json({ error: 'not_comment_author' }, 403);\n if (result.status === 'not_editable') return c.json({ error: 'comment_not_editable' }, 409);\n\n await audit?.emit({\n context: c,\n input: {\n action: 'factory.work_item.comment_deleted',\n factoryProjectId: result.comment.factoryProjectId,\n targets: [{ type: 'work_item', id: result.comment.workItemId }],\n metadata: { commentId: result.comment.id },\n },\n });\n return c.json({ comment: toWireComment(result.comment, tenant.userId) });\n },\n }),\n\n registerApiRoute('/web/factory/projects/:id/mention-roster', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: 'Project not found' }, 404);\n await projects.ensureReady();\n const project = await projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) return c.json({ error: 'Project not found' }, 404);\n\n await comments.ensureReady();\n const roster = await domain.mentionRoster({ orgId: tenant.orgId, factoryProjectId: projectId });\n const query = c.req.query('q')?.trim().toLowerCase();\n const members = query\n ? roster.filter(member => (member.name ?? member.id).toLowerCase().startsWith(query))\n : roster;\n return c.json({ members });\n },\n }),\n\n registerApiRoute('/web/factory/projects/:id/feed-events', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: 'Project not found' }, 404);\n await projects.ensureReady();\n const project = await projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) return c.json({ error: 'Project not found' }, 404);\n\n const topic = feedTopic(tenant.orgId, projectId);\n return streamSSE(c, async stream => {\n // `withAck` settles every delivery: on a durable broker (Redis\n // Streams) an unacked event stays in the subscription's pending set\n // and in the broker client's in-flight bookkeeping for the life of\n // this connection — a leak that grows with every feed touch on\n // long-lived dashboard tabs. Feed frames are fire-and-forget\n // invalidations, so a delivery is acked even when the tab is gone.\n const onEvent: EventCallback = withAck(async event => {\n if (stream.aborted) return;\n const data = event.data;\n const workItemId = isRecord(data) && typeof data.workItemId === 'string' ? data.workItemId : undefined;\n try {\n await stream.writeSSE({ event: 'feed', data: JSON.stringify(workItemId ? { workItemId } : {}) });\n } catch {\n // A write to a half-closed socket must not reach `withAck` as a\n // rejection: the nack would republish the event to the shared\n // feed topic, duplicating the invalidation for every tab.\n }\n });\n // Claimed before any await: `onAbort` handlers registered after the\n // reader is gone never run, and a broker subscribe is a round trip.\n const closed = new Promise<void>(resolve => stream.onAbort(resolve));\n let keepalive: ReturnType<typeof setInterval> | undefined;\n try {\n // Without `latest`, a retaining broker replays its whole backlog into\n // every new connection as spurious invalidations.\n await pubsub.subscribe(topic, onEvent, { startFrom: 'latest' });\n if (!stream.aborted) {\n keepalive = setInterval(() => {\n if (stream.aborted) return;\n void stream.write(': ping\\n\\n');\n }, FEED_KEEPALIVE_MS);\n // `streamSSE` closes the stream the moment this callback returns.\n await closed;\n }\n } finally {\n clearInterval(keepalive);\n await pubsub.unsubscribe(topic, onEvent);\n }\n });\n },\n }),\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;AA2BA,MAAM,0BAA0B;;AAEhC,MAAM,oBAAoB;AAmB1B,SAAS,MAAM,GAAqB;CAClC,OAAO;AACT;AAEA,SAAgB,mBAAmB,cAAoD;CACrF,MAAM,EAAE,QAAQ,MAAM,UAAU,WAAW,UAAU,QAAQ,UAAU;CAEvE,eAAe,cAAc,GAAsD;EACjF,MAAM,KAAK,WAAW,CAAC;EACvB,MAAM,SAAS,KAAK,OAAO,CAAC;EAC5B,IAAI,CAAC,QAAQ,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG,EAAE;EACvE,IAAI,CAAC,OAAO,OACV,OAAO,EACL,UAAU,EAAE,KAAK;GAAE,OAAO;GAAyB,SAAS;EAA0C,GAAG,GAAG,EAC9G;EAEF,OAAO;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;EAAO;CACtD;CAEA,SAAS,UAAU,GAAY,QAA+B;EAC5D,OAAO;GACL,QAAQ,OAAO;GACf,mBAAmB,KAAK,oBAAoB,GAAG,OAAO,KAAK;EAC7D;CACF;CAEA,eAAe,iBACb,GACA,QACA,WACA,cACe;EACf,IAAI,aAAa,WAAW,GAAG;EAC/B,MAAM,OAAO,KAAK;GAChB,SAAS;GACT,OAAO;IACL,QAAQ;IACR,kBAAkB,OAAO;IACzB,SAAS,CAAC;KAAE,MAAM;KAAa,IAAI,OAAO;KAAY,GAAI,OAAO,QAAQ,EAAE,MAAM,OAAO,MAAM,IAAI,CAAC;IAAG,CAAC;IACvG,UAAU;KAAE;KAAW;IAAa;GACtC;EACF,CAAC;CACH;CAEA,OAAO;EACL,iBAAiB,gDAAgD;GAC/D,QAAQ;GACR,SAAS,OAAM,OAAM;IACnB,MAAM,IAAI,MAAM,EAAE;IAClB,MAAM,SAAS,MAAM,cAAc,CAAC;IACpC,IAAI,cAAc,QAAQ,OAAO,OAAO;IAExC,MAAM,aAAa,EAAE,IAAI,MAAM,YAAY;IAC3C,IAAI,CAAC,cAAc,CAAC,QAAQ,KAAK,UAAU,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,sBAAsB,GAAG,GAAG;IACjG,MAAM,UAAU,YAAY;IAC5B,MAAM,WAAW,MAAM,UAAU,IAAI;KAAE,OAAO,OAAO;KAAO,IAAI;IAAW,CAAC;IAC5E,IAAI,CAAC,UAAU,OAAO,EAAE,KAAK,EAAE,OAAO,sBAAsB,GAAG,GAAG;IAElE,MAAM,SAAS,YAAY;IAC3B,MAAM,WAAW,EAAE,IAAI,MAAM,OAAO;IACpC,MAAM,QAAQ,WAAW,OAAO,SAAS,UAAU,EAAE,IAAI,KAAA;IACzD,MAAM,SAAS,EAAE,IAAI,MAAM,QAAQ,KAAK,KAAA;IACxC,IAAI,UAAU,CAAC,oBAAoB,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,iBAAiB,GAAG,GAAG;IAC1F,MAAM,SAAS,EAAE,IAAI,MAAM,QAAQ,KAAK,KAAA;IACxC,IAAI,UAAU,CAAC,QAAQ,KAAK,MAAM,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,qBAAqB,GAAG,GAAG;IACvF,MAAM,OAAO,MAAM,SAAS,KAAK;KAC/B,OAAO,OAAO;KACd,kBAAkB,SAAS;KAC3B;KACA;KACA,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;KAC3B,GAAI,UAAU,KAAA,KAAa,OAAO,SAAS,KAAK,IAAI,EAAE,MAAM,IAAI,CAAC;IACnE,CAAC;IACD,MAAM,WAA4B;KAChC,UAAU,KAAK,SAAS,KAAI,YAAW,cAAc,SAAS,OAAO,MAAM,CAAC;KAC5E,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;IAC3D;IACA,OAAO,EAAE,KAAK,QAAQ;GACxB;EACF,CAAC;EAED,iBAAiB,gDAAgD;GAC/D,QAAQ;GACR,SAAS,OAAM,OAAM;IACnB,MAAM,IAAI,MAAM,EAAE;IAClB,MAAM,SAAS,MAAM,cAAc,CAAC;IACpC,IAAI,cAAc,QAAQ,OAAO,OAAO;IAExC,MAAM,aAAa,EAAE,IAAI,MAAM,YAAY;IAC3C,IAAI,CAAC,cAAc,CAAC,QAAQ,KAAK,UAAU,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,sBAAsB,GAAG,GAAG;IAEjG,MAAM,SAAS,uBAAuB,MAAM,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,QAAQ,OAAO,EAAE,KAAK,EAAE,OAAO,kBAAkB,GAAG,GAAG;IAE5D,MAAM,SAAS,MAAM,OAAO,cAAc;KACxC,OAAO,OAAO;KACd;KACA,QAAQ,kBAAkB,OAAO,QAAQ,mBAAmB,CAAC,CAAC;KAC9D,MAAM,OAAO;KACb,GAAI,OAAO,UAAU,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;KACpD,GAAI,OAAO,WAAW,EAAE,UAAU,OAAO,SAAS,IAAI,CAAC;KACvD,GAAI,OAAO,cAAc,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;IAClE,CAAC;IACD,IAAI,OAAO,WAAW,uBAAuB,OAAO,EAAE,KAAK,EAAE,OAAO,sBAAsB,GAAG,GAAG;IAChG,IAAI,OAAO,WAAW,kBAAkB,OAAO,EAAE,KAAK,EAAE,OAAO,yBAAyB,GAAG,GAAG;IAC9F,IAAI,OAAO,WAAW,WAAW,OAAO,EAAE,KAAK;KAAE,OAAO;KAAmB,SAAS,OAAO;IAAQ,GAAG,GAAG;IAEzG,MAAM,EAAE,SAAS,aAAa;IAC9B,MAAM,OAAO,KAAK;KAChB,SAAS;KACT,OAAO;MACL,QAAQ;MACR,kBAAkB,SAAS;MAC3B,SAAS,CAAC;OAAE,MAAM;OAAa,IAAI,SAAS;OAAI,MAAM,SAAS;MAAM,CAAC;MACtE,UAAU,EAAE,WAAW,QAAQ,GAAG;KACpC;IACF,CAAC;IACD,MAAM,iBACJ,GACA;KAAE,kBAAkB,SAAS;KAAkB,YAAY,SAAS;KAAI,OAAO,SAAS;IAAM,GAC9F,QAAQ,IACR,QAAQ,SAAS,KAAI,YAAW,QAAQ,EAAE,CAAC,CAAC,QAAO,OAAM,OAAO,QAAQ,OAAO,EAAE,CACnF;IACA,OAAO,EAAE,KAAK,EAAE,SAAS,cAAc,SAAS,OAAO,MAAM,EAAE,GAAG,GAAG;GACvE;EACF,CAAC;EAED,iBAAiB,2DAA2D;GAC1E,QAAQ;GACR,SAAS,OAAM,OAAM;IACnB,MAAM,IAAI,MAAM,EAAE;IAClB,MAAM,SAAS,MAAM,cAAc,CAAC;IACpC,IAAI,cAAc,QAAQ,OAAO,OAAO;IAExC,MAAM,aAAa,EAAE,IAAI,MAAM,YAAY;IAC3C,MAAM,YAAY,EAAE,IAAI,MAAM,WAAW;IACzC,IAAI,CAAC,cAAc,CAAC,QAAQ,KAAK,UAAU,KAAK,CAAC,aAAa,CAAC,QAAQ,KAAK,SAAS,GACnF,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAGnD,MAAM,SAAS,qBAAqB,MAAM,SAAS,CAAC,CAAC;IACrD,IAAI,CAAC,QAAQ,OAAO,EAAE,KAAK,EAAE,OAAO,kBAAkB,GAAG,GAAG;IAE5D,MAAM,SAAS,MAAM,OAAO,YAAY;KACtC,OAAO,OAAO;KACd;KACA;KACA,QAAQ,UAAU,GAAG,MAAM;KAC3B,MAAM,OAAO;KACb,GAAI,OAAO,WAAW,EAAE,UAAU,OAAO,SAAS,IAAI,CAAC;KACvD,GAAI,OAAO,qBAAqB,KAAA,IAAY,EAAE,kBAAkB,OAAO,iBAAiB,IAAI,CAAC;IAC/F,CAAC;IACD,IAAI,OAAO,WAAW,aAAa,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IACpF,IAAI,OAAO,WAAW,aAAa,OAAO,EAAE,KAAK,EAAE,OAAO,qBAAqB,GAAG,GAAG;IACrF,IAAI,OAAO,WAAW,gBAAgB,OAAO,EAAE,KAAK,EAAE,OAAO,uBAAuB,GAAG,GAAG;IAC1F,IAAI,OAAO,WAAW,YAAY,OAAO,EAAE,KAAK,EAAE,OAAO,mBAAmB,GAAG,GAAG;IAClF,IAAI,OAAO,WAAW,WAAW,OAAO,EAAE,KAAK;KAAE,OAAO;KAAmB,SAAS,OAAO;IAAQ,GAAG,GAAG;IAEzG,MAAM,EAAE,YAAY;IACpB,MAAM,OAAO,KAAK;KAChB,SAAS;KACT,OAAO;MACL,QAAQ;MACR,kBAAkB,QAAQ;MAC1B,SAAS,CAAC;OAAE,MAAM;OAAa,IAAI,QAAQ;MAAW,CAAC;MACvD,UAAU;OACR,WAAW,QAAQ;OACnB,cAAc,OAAO,aAAa,MAAM,GAAG,uBAAuB;MACpE;KACF;IACF,CAAC;IACD,MAAM,iBACJ,GACA;KAAE,kBAAkB,QAAQ;KAAkB,YAAY,QAAQ;IAAW,GAC7E,QAAQ,IACR,OAAO,cAAc,KAAI,YAAW,QAAQ,EAAE,CAChD;IACA,OAAO,EAAE,KAAK,EAAE,SAAS,cAAc,SAAS,OAAO,MAAM,EAAE,CAAC;GAClE;EACF,CAAC;EAED,iBAAiB,2DAA2D;GAC1E,QAAQ;GACR,SAAS,OAAM,OAAM;IACnB,MAAM,IAAI,MAAM,EAAE;IAClB,MAAM,SAAS,MAAM,cAAc,CAAC;IACpC,IAAI,cAAc,QAAQ,OAAO,OAAO;IAExC,MAAM,aAAa,EAAE,IAAI,MAAM,YAAY;IAC3C,MAAM,YAAY,EAAE,IAAI,MAAM,WAAW;IACzC,IAAI,CAAC,cAAc,CAAC,QAAQ,KAAK,UAAU,KAAK,CAAC,aAAa,CAAC,QAAQ,KAAK,SAAS,GACnF,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAGnD,MAAM,SAAS,MAAM,OAAO,cAAc;KACxC,OAAO,OAAO;KACd;KACA;KACA,QAAQ,UAAU,GAAG,MAAM;IAC7B,CAAC;IACD,IAAI,OAAO,WAAW,aAAa,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IACpF,IAAI,OAAO,WAAW,aAAa,OAAO,EAAE,KAAK,EAAE,OAAO,qBAAqB,GAAG,GAAG;IACrF,IAAI,OAAO,WAAW,gBAAgB,OAAO,EAAE,KAAK,EAAE,OAAO,uBAAuB,GAAG,GAAG;IAE1F,MAAM,OAAO,KAAK;KAChB,SAAS;KACT,OAAO;MACL,QAAQ;MACR,kBAAkB,OAAO,QAAQ;MACjC,SAAS,CAAC;OAAE,MAAM;OAAa,IAAI,OAAO,QAAQ;MAAW,CAAC;MAC9D,UAAU,EAAE,WAAW,OAAO,QAAQ,GAAG;KAC3C;IACF,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,SAAS,cAAc,OAAO,SAAS,OAAO,MAAM,EAAE,CAAC;GACzE;EACF,CAAC;EAED,iBAAiB,4CAA4C;GAC3D,QAAQ;GACR,SAAS,OAAM,OAAM;IACnB,MAAM,IAAI,MAAM,EAAE;IAClB,MAAM,SAAS,MAAM,cAAc,CAAC;IACpC,IAAI,cAAc,QAAQ,OAAO,OAAO;IAExC,MAAM,YAAY,EAAE,IAAI,MAAM,IAAI;IAClC,IAAI,CAAC,aAAa,CAAC,QAAQ,KAAK,SAAS,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAC7F,MAAM,SAAS,YAAY;IAE3B,IAAI,CAAC,MADiB,SAAS,IAAI;KAAE,OAAO,OAAO;KAAO,IAAI;IAAU,CAAC,GAC3D,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAE/D,MAAM,SAAS,YAAY;IAC3B,MAAM,SAAS,MAAM,OAAO,cAAc;KAAE,OAAO,OAAO;KAAO,kBAAkB;IAAU,CAAC;IAC9F,MAAM,QAAQ,EAAE,IAAI,MAAM,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY;IACnD,MAAM,UAAU,QACZ,OAAO,QAAO,YAAW,OAAO,QAAQ,OAAO,GAAA,CAAI,YAAY,CAAC,CAAC,WAAW,KAAK,CAAC,IAClF;IACJ,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC;GAC3B;EACF,CAAC;EAED,iBAAiB,yCAAyC;GACxD,QAAQ;GACR,SAAS,OAAM,OAAM;IACnB,MAAM,IAAI,MAAM,EAAE;IAClB,MAAM,SAAS,MAAM,cAAc,CAAC;IACpC,IAAI,cAAc,QAAQ,OAAO,OAAO;IAExC,MAAM,YAAY,EAAE,IAAI,MAAM,IAAI;IAClC,IAAI,CAAC,aAAa,CAAC,QAAQ,KAAK,SAAS,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAC7F,MAAM,SAAS,YAAY;IAE3B,IAAI,CAAC,MADiB,SAAS,IAAI;KAAE,OAAO,OAAO;KAAO,IAAI;IAAU,CAAC,GAC3D,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAE/D,MAAM,QAAQ,UAAU,OAAO,OAAO,SAAS;IAC/C,OAAO,UAAU,GAAG,OAAM,WAAU;KAOlC,MAAM,UAAyB,QAAQ,OAAM,UAAS;MACpD,IAAI,OAAO,SAAS;MACpB,MAAM,OAAO,MAAM;MACnB,MAAM,aAAa,SAAS,IAAI,KAAK,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa,KAAA;MAC7F,IAAI;OACF,MAAM,OAAO,SAAS;QAAE,OAAO;QAAQ,MAAM,KAAK,UAAU,aAAa,EAAE,WAAW,IAAI,CAAC,CAAC;OAAE,CAAC;MACjG,QAAQ,CAIR;KACF,CAAC;KAGD,MAAM,SAAS,IAAI,SAAc,YAAW,OAAO,QAAQ,OAAO,CAAC;KACnE,IAAI;KACJ,IAAI;MAGF,MAAM,OAAO,UAAU,OAAO,SAAS,EAAE,WAAW,SAAS,CAAC;MAC9D,IAAI,CAAC,OAAO,SAAS;OACnB,YAAY,kBAAkB;QAC5B,IAAI,OAAO,SAAS;QACpB,OAAY,MAAM,YAAY;OAChC,GAAG,iBAAiB;OAEpB,MAAM;MACR;KACF,UAAU;MACR,cAAc,SAAS;MACvB,MAAM,OAAO,YAAY,OAAO,OAAO;KACzC;IACF,CAAC;GACH;EACF,CAAC;CACH;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/factory",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.1-alpha.1",
|
|
4
4
|
"description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"hono": "^4.13.7",
|
|
58
58
|
"posthog-node": "^5.46.1",
|
|
59
59
|
"zod": "^4.6.4",
|
|
60
|
-
"@mastra/auth-workos": "1.6.5",
|
|
61
60
|
"@mastra/auth-studio": "1.3.7",
|
|
62
|
-
"@mastra/
|
|
63
|
-
"@mastra/
|
|
61
|
+
"@mastra/auth-workos": "1.6.5",
|
|
62
|
+
"@mastra/code-sdk": "1.8.2-alpha.1",
|
|
63
|
+
"@mastra/core": "1.70.0-alpha.1",
|
|
64
64
|
"@mastra/slack": "1.6.4"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
"typescript-eslint": "^8.57.0",
|
|
72
72
|
"vitest": "4.1.11",
|
|
73
73
|
"@internal/lint": "0.0.135",
|
|
74
|
-
"@mastra/
|
|
75
|
-
"@mastra/
|
|
74
|
+
"@mastra/pg": "1.27.0-alpha.1",
|
|
75
|
+
"@mastra/libsql": "1.23.2-alpha.0",
|
|
76
76
|
"@internal/types-builder": "0.0.110",
|
|
77
77
|
"@internal/workspace": "0.0.7"
|
|
78
78
|
},
|