@mastra/factory 0.0.0-esbuild-bundle-worker-20260807173433
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1008 -0
- package/LICENSE.md +30 -0
- package/README.md +27 -0
- package/dist/auth.d.ts +183 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +507 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities/connection.d.ts +9 -0
- package/dist/capabilities/connection.d.ts.map +1 -0
- package/dist/capabilities/connection.js +1 -0
- package/dist/capabilities/intake.d.ts +141 -0
- package/dist/capabilities/intake.d.ts.map +1 -0
- package/dist/capabilities/intake.js +1 -0
- package/dist/capabilities/version-control.d.ts +247 -0
- package/dist/capabilities/version-control.d.ts.map +1 -0
- package/dist/capabilities/version-control.js +1 -0
- package/dist/factory.d.ts +159 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +491 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/integrations/base.d.ts +254 -0
- package/dist/integrations/base.d.ts.map +1 -0
- package/dist/integrations/base.js +1 -0
- package/dist/integrations/github/config.d.ts +60 -0
- package/dist/integrations/github/config.d.ts.map +1 -0
- package/dist/integrations/github/config.js +42 -0
- package/dist/integrations/github/config.js.map +1 -0
- package/dist/integrations/github/integration.d.ts +220 -0
- package/dist/integrations/github/integration.d.ts.map +1 -0
- package/dist/integrations/github/integration.js +1162 -0
- package/dist/integrations/github/integration.js.map +1 -0
- package/dist/integrations/github/issue-triage.d.ts +27 -0
- package/dist/integrations/github/issue-triage.d.ts.map +1 -0
- package/dist/integrations/github/issue-triage.js +82 -0
- package/dist/integrations/github/issue-triage.js.map +1 -0
- package/dist/integrations/github/pat.d.ts +38 -0
- package/dist/integrations/github/pat.d.ts.map +1 -0
- package/dist/integrations/github/pat.js +59 -0
- package/dist/integrations/github/pat.js.map +1 -0
- package/dist/integrations/github/provenance.d.ts +27 -0
- package/dist/integrations/github/provenance.d.ts.map +1 -0
- package/dist/integrations/github/provenance.js +75 -0
- package/dist/integrations/github/provenance.js.map +1 -0
- package/dist/integrations/github/reconcile-worker.d.ts +41 -0
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -0
- package/dist/integrations/github/reconcile-worker.js +125 -0
- package/dist/integrations/github/reconcile-worker.js.map +1 -0
- package/dist/integrations/github/routes.d.ts +69 -0
- package/dist/integrations/github/routes.d.ts.map +1 -0
- package/dist/integrations/github/routes.js +1335 -0
- package/dist/integrations/github/routes.js.map +1 -0
- package/dist/integrations/github/rules.d.ts +105 -0
- package/dist/integrations/github/rules.d.ts.map +1 -0
- package/dist/integrations/github/rules.js +551 -0
- package/dist/integrations/github/rules.js.map +1 -0
- package/dist/integrations/github/sandbox-release.d.ts +53 -0
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -0
- package/dist/integrations/github/sandbox-release.js +115 -0
- package/dist/integrations/github/sandbox-release.js.map +1 -0
- package/dist/integrations/github/sandbox.d.ts +281 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -0
- package/dist/integrations/github/sandbox.js +547 -0
- package/dist/integrations/github/sandbox.js.map +1 -0
- package/dist/integrations/github/session-subscriptions.d.ts +26 -0
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/session-subscriptions.js +192 -0
- package/dist/integrations/github/session-subscriptions.js.map +1 -0
- package/dist/integrations/github/subscriptions.d.ts +54 -0
- package/dist/integrations/github/subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/subscriptions.js +62 -0
- package/dist/integrations/github/subscriptions.js.map +1 -0
- package/dist/integrations/github/token-refresh.d.ts +12 -0
- package/dist/integrations/github/token-refresh.d.ts.map +1 -0
- package/dist/integrations/github/token-refresh.js +24 -0
- package/dist/integrations/github/token-refresh.js.map +1 -0
- package/dist/integrations/github/webhook.d.ts +107 -0
- package/dist/integrations/github/webhook.d.ts.map +1 -0
- package/dist/integrations/github/webhook.js +371 -0
- package/dist/integrations/github/webhook.js.map +1 -0
- package/dist/integrations/linear/agent-tools.d.ts +34 -0
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
- package/dist/integrations/linear/agent-tools.js +85 -0
- package/dist/integrations/linear/agent-tools.js.map +1 -0
- package/dist/integrations/linear/integration.d.ts +235 -0
- package/dist/integrations/linear/integration.d.ts.map +1 -0
- package/dist/integrations/linear/integration.js +676 -0
- package/dist/integrations/linear/integration.js.map +1 -0
- package/dist/integrations/linear/routes.d.ts +61 -0
- package/dist/integrations/linear/routes.d.ts.map +1 -0
- package/dist/integrations/linear/routes.js +240 -0
- package/dist/integrations/linear/routes.js.map +1 -0
- package/dist/integrations/linear/rules.d.ts +44 -0
- package/dist/integrations/linear/rules.d.ts.map +1 -0
- package/dist/integrations/linear/rules.js +133 -0
- package/dist/integrations/linear/rules.js.map +1 -0
- package/dist/integrations/linear/storage.d.ts +65 -0
- package/dist/integrations/linear/storage.d.ts.map +1 -0
- package/dist/integrations/linear/storage.js +1 -0
- package/dist/integrations/platform/api-client.d.ts +26 -0
- package/dist/integrations/platform/api-client.d.ts.map +1 -0
- package/dist/integrations/platform/api-client.js +156 -0
- package/dist/integrations/platform/api-client.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts +44 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/github/event-worker.js +349 -0
- package/dist/integrations/platform/github/event-worker.js.map +1 -0
- package/dist/integrations/platform/github/integration.d.ts +64 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -0
- package/dist/integrations/platform/github/integration.js +1006 -0
- package/dist/integrations/platform/github/integration.js.map +1 -0
- package/dist/integrations/platform/linear/integration.d.ts +46 -0
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
- package/dist/integrations/platform/linear/integration.js +483 -0
- package/dist/integrations/platform/linear/integration.js.map +1 -0
- package/dist/integrations/slack/connect-route.d.ts +46 -0
- package/dist/integrations/slack/connect-route.d.ts.map +1 -0
- package/dist/integrations/slack/connect-route.js +186 -0
- package/dist/integrations/slack/connect-route.js.map +1 -0
- package/dist/integrations/slack/integration.d.ts +62 -0
- package/dist/integrations/slack/integration.d.ts.map +1 -0
- package/dist/integrations/slack/integration.js +68 -0
- package/dist/integrations/slack/integration.js.map +1 -0
- package/dist/integrations/slack/slack.d.ts +200 -0
- package/dist/integrations/slack/slack.d.ts.map +1 -0
- package/dist/integrations/slack/slack.js +411 -0
- package/dist/integrations/slack/slack.js.map +1 -0
- package/dist/integrations/workos/integration.d.ts +39 -0
- package/dist/integrations/workos/integration.d.ts.map +1 -0
- package/dist/integrations/workos/integration.js +91 -0
- package/dist/integrations/workos/integration.js.map +1 -0
- package/dist/routes/config.d.ts +235 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +972 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/custom-provider-source.d.ts +52 -0
- package/dist/routes/custom-provider-source.d.ts.map +1 -0
- package/dist/routes/custom-provider-source.js +124 -0
- package/dist/routes/custom-provider-source.js.map +1 -0
- package/dist/routes/fs.d.ts +151 -0
- package/dist/routes/fs.d.ts.map +1 -0
- package/dist/routes/fs.js +708 -0
- package/dist/routes/fs.js.map +1 -0
- package/dist/routes/intake.d.ts +23 -0
- package/dist/routes/intake.d.ts.map +1 -0
- package/dist/routes/intake.js +179 -0
- package/dist/routes/intake.js.map +1 -0
- package/dist/routes/oauth.d.ts +43 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +463 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/projects.d.ts +19 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +461 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/provider-credentials.d.ts +81 -0
- package/dist/routes/provider-credentials.d.ts.map +1 -0
- package/dist/routes/provider-credentials.js +86 -0
- package/dist/routes/provider-credentials.js.map +1 -0
- package/dist/routes/route.d.ts +48 -0
- package/dist/routes/route.d.ts.map +1 -0
- package/dist/routes/route.js +15 -0
- package/dist/routes/route.js.map +1 -0
- package/dist/routes/skills.d.ts +30 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +168 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/surface.d.ts +107 -0
- package/dist/routes/surface.d.ts.map +1 -0
- package/dist/routes/surface.js +297 -0
- package/dist/routes/surface.js.map +1 -0
- package/dist/routes/tenant-credentials.d.ts +74 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -0
- package/dist/routes/tenant-credentials.js +156 -0
- package/dist/routes/tenant-credentials.js.map +1 -0
- package/dist/routes/work-items.d.ts +40 -0
- package/dist/routes/work-items.d.ts.map +1 -0
- package/dist/routes/work-items.js +695 -0
- package/dist/routes/work-items.js.map +1 -0
- package/dist/rules/binding-context.d.ts +5 -0
- package/dist/rules/binding-context.d.ts.map +1 -0
- package/dist/rules/binding-context.js +28 -0
- package/dist/rules/binding-context.js.map +1 -0
- package/dist/rules/defaults.d.ts +9 -0
- package/dist/rules/defaults.d.ts.map +1 -0
- package/dist/rules/defaults.js +322 -0
- package/dist/rules/defaults.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +43 -0
- package/dist/rules/dispatcher.d.ts.map +1 -0
- package/dist/rules/dispatcher.js +547 -0
- package/dist/rules/dispatcher.js.map +1 -0
- package/dist/rules/index.d.ts +7 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +5 -0
- package/dist/rules/processor.d.ts +54 -0
- package/dist/rules/processor.d.ts.map +1 -0
- package/dist/rules/processor.js +378 -0
- package/dist/rules/processor.js.map +1 -0
- package/dist/rules/resolve.d.ts +17 -0
- package/dist/rules/resolve.d.ts.map +1 -0
- package/dist/rules/resolve.js +30 -0
- package/dist/rules/resolve.js.map +1 -0
- package/dist/rules/start-coordinator.d.ts +60 -0
- package/dist/rules/start-coordinator.d.ts.map +1 -0
- package/dist/rules/start-coordinator.js +175 -0
- package/dist/rules/start-coordinator.js.map +1 -0
- package/dist/rules/tools.d.ts +10 -0
- package/dist/rules/tools.d.ts.map +1 -0
- package/dist/rules/tools.js +59 -0
- package/dist/rules/tools.js.map +1 -0
- package/dist/rules/transition-service.d.ts +49 -0
- package/dist/rules/transition-service.d.ts.map +1 -0
- package/dist/rules/transition-service.js +232 -0
- package/dist/rules/transition-service.js.map +1 -0
- package/dist/rules/types.d.ts +261 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +43 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/validation.d.ts +11 -0
- package/dist/rules/validation.d.ts.map +1 -0
- package/dist/rules/validation.js +308 -0
- package/dist/rules/validation.js.map +1 -0
- package/dist/sandbox/fleet.d.ts +222 -0
- package/dist/sandbox/fleet.d.ts.map +1 -0
- package/dist/sandbox/fleet.js +322 -0
- package/dist/sandbox/fleet.js.map +1 -0
- package/dist/sandbox/reattach.d.ts +3 -0
- package/dist/sandbox/reattach.d.ts.map +1 -0
- package/dist/sandbox/reattach.js +16 -0
- package/dist/sandbox/reattach.js.map +1 -0
- package/dist/server-error.d.ts +12 -0
- package/dist/server-error.d.ts.map +1 -0
- package/dist/server-error.js +16 -0
- package/dist/server-error.js.map +1 -0
- package/dist/session/factory-session.d.ts +116 -0
- package/dist/session/factory-session.d.ts.map +1 -0
- package/dist/session/factory-session.js +162 -0
- package/dist/session/factory-session.js.map +1 -0
- package/dist/skills/service.d.ts +48 -0
- package/dist/skills/service.d.ts.map +1 -0
- package/dist/skills/service.js +40 -0
- package/dist/skills/service.js.map +1 -0
- package/dist/spa-static.d.ts +33 -0
- package/dist/spa-static.d.ts.map +1 -0
- package/dist/spa-static.js +104 -0
- package/dist/spa-static.js.map +1 -0
- package/dist/state-signing.d.ts +59 -0
- package/dist/state-signing.d.ts.map +1 -0
- package/dist/state-signing.js +83 -0
- package/dist/state-signing.js.map +1 -0
- package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
- package/dist/storage/domains/audit/agent-audit.js +66 -0
- package/dist/storage/domains/audit/agent-audit.js.map +1 -0
- package/dist/storage/domains/audit/base.d.ts +140 -0
- package/dist/storage/domains/audit/base.d.ts.map +1 -0
- package/dist/storage/domains/audit/base.js +184 -0
- package/dist/storage/domains/audit/base.js.map +1 -0
- package/dist/storage/domains/audit/domain.d.ts +76 -0
- package/dist/storage/domains/audit/domain.d.ts.map +1 -0
- package/dist/storage/domains/audit/domain.js +269 -0
- package/dist/storage/domains/audit/domain.js.map +1 -0
- package/dist/storage/domains/channel-identity/base.d.ts +96 -0
- package/dist/storage/domains/channel-identity/base.d.ts.map +1 -0
- package/dist/storage/domains/channel-identity/base.js +149 -0
- package/dist/storage/domains/channel-identity/base.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +134 -0
- package/dist/storage/domains/credentials/base.d.ts.map +1 -0
- package/dist/storage/domains/credentials/base.js +273 -0
- package/dist/storage/domains/credentials/base.js.map +1 -0
- package/dist/storage/domains/custom-providers/base.d.ts +57 -0
- package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
- package/dist/storage/domains/custom-providers/base.js +145 -0
- package/dist/storage/domains/custom-providers/base.js.map +1 -0
- package/dist/storage/domains/intake/base.d.ts +38 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -0
- package/dist/storage/domains/intake/base.js +80 -0
- package/dist/storage/domains/intake/base.js.map +1 -0
- package/dist/storage/domains/integrations/base.d.ts +126 -0
- package/dist/storage/domains/integrations/base.d.ts.map +1 -0
- package/dist/storage/domains/integrations/base.js +317 -0
- package/dist/storage/domains/integrations/base.js.map +1 -0
- package/dist/storage/domains/memory-settings/base.d.ts +62 -0
- package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
- package/dist/storage/domains/memory-settings/base.js +126 -0
- package/dist/storage/domains/memory-settings/base.js.map +1 -0
- package/dist/storage/domains/model-packs/base.d.ts +49 -0
- package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
- package/dist/storage/domains/model-packs/base.js +96 -0
- package/dist/storage/domains/model-packs/base.js.map +1 -0
- package/dist/storage/domains/projects/base.d.ts +58 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -0
- package/dist/storage/domains/projects/base.js +111 -0
- package/dist/storage/domains/projects/base.js.map +1 -0
- package/dist/storage/domains/queue-health/base.d.ts +52 -0
- package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
- package/dist/storage/domains/queue-health/base.js +122 -0
- package/dist/storage/domains/queue-health/base.js.map +1 -0
- package/dist/storage/domains/source-control/base.d.ts +360 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -0
- package/dist/storage/domains/source-control/base.js +914 -0
- package/dist/storage/domains/source-control/base.js.map +1 -0
- package/dist/storage/domains/source-control/inmemory.d.ts +175 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
- package/dist/storage/domains/source-control/inmemory.js +380 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -0
- package/dist/storage/domains/work-items/base.d.ts +412 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -0
- package/dist/storage/domains/work-items/base.js +1320 -0
- package/dist/storage/domains/work-items/base.js.map +1 -0
- package/dist/storage/domains/work-items/metrics.d.ts +101 -0
- package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
- package/dist/storage/domains/work-items/metrics.js +221 -0
- package/dist/storage/domains/work-items/metrics.js.map +1 -0
- package/dist/timing.d.ts +15 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +34 -0
- package/dist/timing.js.map +1 -0
- package/dist/workspace.d.ts +24 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +287 -0
- package/dist/workspace.js.map +1 -0
- package/factory-skills/configure-factory-rules/SKILL.md +67 -0
- package/factory-skills/factory-plan/SKILL.md +61 -0
- package/factory-skills/factory-review/SKILL.md +157 -0
- package/factory-skills/factory-triage/SKILL.md +99 -0
- package/package.json +83 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { registerApiRoute } from "@mastra/core/server";
|
|
2
|
+
//#region src/integrations/slack/connect-route.ts
|
|
3
|
+
const SLACK_AUTHORIZE_URL = "https://slack.com/openid/connect/authorize";
|
|
4
|
+
const SLACK_TOKEN_URL = "https://slack.com/api/openid.connect.token";
|
|
5
|
+
const OIDC_CALLBACK_PATH = "/connect/slack/oidc/callback";
|
|
6
|
+
const SLACK_TOKEN_TIMEOUT_MS = 1e4;
|
|
7
|
+
/** Matches the signer's own `state` lifetime — past it, `verify` rejects anyway. */
|
|
8
|
+
const STATE_REPLAY_WINDOW_MS = 600 * 1e3;
|
|
9
|
+
/**
|
|
10
|
+
* Decode a JWT's payload WITHOUT signature verification. Safe here because the
|
|
11
|
+
* `id_token` arrives directly from Slack's token endpoint over TLS in a
|
|
12
|
+
* confidential-client code exchange — the transport authenticates the issuer,
|
|
13
|
+
* which is the standard OIDC allowance for this flow. `iss`/`aud`/`exp` are
|
|
14
|
+
* still checked by the caller.
|
|
15
|
+
*/
|
|
16
|
+
function decodeJwtPayload(jwt) {
|
|
17
|
+
const payload = jwt.split(".")[1];
|
|
18
|
+
if (!payload) return null;
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Slack account-linking routes.
|
|
27
|
+
*
|
|
28
|
+
* A link is only ever written by the OIDC flow below, where Slack itself
|
|
29
|
+
* asserts the `(team, user)` pair in the id_token — so the web user has to
|
|
30
|
+
* actually control the Slack account they bind. `/connect/slack` is just the
|
|
31
|
+
* Slack-side entry point: it carries no identity and writes nothing, it only
|
|
32
|
+
* sends the visitor to Connections, where the flow starts.
|
|
33
|
+
*/
|
|
34
|
+
function createSlackConnectRoutes(deps) {
|
|
35
|
+
const { auth, accountLinks, tenantStateSigner, oidc, projects } = deps;
|
|
36
|
+
const oidcEnabled = Boolean(oidc && tenantStateSigner);
|
|
37
|
+
const uiOrigin = oidc?.uiOrigin?.replace(/\/$/, "") ?? "";
|
|
38
|
+
const consumedNonces = /* @__PURE__ */ new Map();
|
|
39
|
+
const consumeNonce = (nonce) => {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
for (const [seen, expiresAt] of consumedNonces) if (expiresAt <= now) consumedNonces.delete(seen);
|
|
42
|
+
if (consumedNonces.has(nonce)) return false;
|
|
43
|
+
consumedNonces.set(nonce, now + STATE_REPLAY_WINDOW_MS);
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
46
|
+
const loose = (c) => c;
|
|
47
|
+
return [
|
|
48
|
+
registerApiRoute("/connect/slack", {
|
|
49
|
+
method: "GET",
|
|
50
|
+
requiresAuth: false,
|
|
51
|
+
handler: async (c) => c.redirect(`${uiOrigin}/settings/connections`)
|
|
52
|
+
}),
|
|
53
|
+
registerApiRoute("/connect/slack/oidc/start", {
|
|
54
|
+
method: "GET",
|
|
55
|
+
requiresAuth: false,
|
|
56
|
+
handler: async (c) => {
|
|
57
|
+
if (!oidcEnabled) return c.redirect(`${uiOrigin}/?slack=error`);
|
|
58
|
+
const factoryProjectId = c.req.query("factoryId")?.trim() || void 0;
|
|
59
|
+
const startPath = factoryProjectId ? `/connect/slack/oidc/start?factoryId=${encodeURIComponent(factoryProjectId)}` : "/connect/slack/oidc/start";
|
|
60
|
+
await auth.ensureUser(loose(c));
|
|
61
|
+
const tenant = auth.tenant(loose(c));
|
|
62
|
+
if (!tenant) return c.redirect(`/auth/login?returnTo=${encodeURIComponent(startPath)}`);
|
|
63
|
+
const params = new URLSearchParams({
|
|
64
|
+
response_type: "code",
|
|
65
|
+
scope: "openid profile",
|
|
66
|
+
client_id: oidc.clientId,
|
|
67
|
+
state: tenantStateSigner.sign(tenant.orgId ?? "", tenant.userId, { factoryProjectId }),
|
|
68
|
+
redirect_uri: `${oidc.redirectBaseUrl.replace(/\/$/, "")}${OIDC_CALLBACK_PATH}`
|
|
69
|
+
});
|
|
70
|
+
return c.redirect(`${SLACK_AUTHORIZE_URL}?${params.toString()}`);
|
|
71
|
+
}
|
|
72
|
+
}),
|
|
73
|
+
registerApiRoute(OIDC_CALLBACK_PATH, {
|
|
74
|
+
method: "GET",
|
|
75
|
+
requiresAuth: false,
|
|
76
|
+
handler: async (c) => {
|
|
77
|
+
if (!oidcEnabled) return c.redirect(`${uiOrigin}/?slack=error`);
|
|
78
|
+
const tenant = tenantStateSigner.verify(c.req.query("state"));
|
|
79
|
+
const code = c.req.query("code");
|
|
80
|
+
if (!tenant || !code) return c.redirect(`${uiOrigin}/?slack=error`);
|
|
81
|
+
if (!consumeNonce(tenant.nonce)) return c.redirect(`${uiOrigin}/?slack=error`);
|
|
82
|
+
const tokenRes = await fetch(SLACK_TOKEN_URL, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
85
|
+
body: new URLSearchParams({
|
|
86
|
+
client_id: oidc.clientId,
|
|
87
|
+
client_secret: oidc.clientSecret,
|
|
88
|
+
code,
|
|
89
|
+
redirect_uri: `${oidc.redirectBaseUrl.replace(/\/$/, "")}${OIDC_CALLBACK_PATH}`
|
|
90
|
+
}),
|
|
91
|
+
signal: AbortSignal.timeout(SLACK_TOKEN_TIMEOUT_MS)
|
|
92
|
+
}).catch(() => null);
|
|
93
|
+
const token = await tokenRes?.json().catch(() => null);
|
|
94
|
+
if (!tokenRes?.ok || !token?.ok || typeof token.id_token !== "string") return c.redirect(`${uiOrigin}/?slack=error`);
|
|
95
|
+
const claims = decodeJwtPayload(token.id_token);
|
|
96
|
+
const teamId = claims?.["https://slack.com/team_id"];
|
|
97
|
+
const slackUserId = claims?.["https://slack.com/user_id"];
|
|
98
|
+
const teamName = claims?.["https://slack.com/team_name"];
|
|
99
|
+
const userName = claims?.name;
|
|
100
|
+
const expiresAt = typeof claims?.exp === "number" ? claims.exp * 1e3 : null;
|
|
101
|
+
if (!claims || claims.iss !== "https://slack.com" || claims.aud !== oidc.clientId || expiresAt === null || expiresAt <= Date.now() || typeof teamId !== "string" || typeof slackUserId !== "string") return c.redirect(`${uiOrigin}/?slack=error`);
|
|
102
|
+
await accountLinks.saveAccountLink({
|
|
103
|
+
platform: "slack",
|
|
104
|
+
externalTeamId: teamId,
|
|
105
|
+
externalUserId: slackUserId,
|
|
106
|
+
orgId: tenant.orgId || void 0,
|
|
107
|
+
userId: tenant.userId,
|
|
108
|
+
externalTeamName: typeof teamName === "string" ? teamName : void 0,
|
|
109
|
+
externalUserName: typeof userName === "string" ? userName : void 0
|
|
110
|
+
});
|
|
111
|
+
const successPath = tenant.factoryProjectId ? `/factories/${encodeURIComponent(tenant.factoryProjectId)}/settings/connections/slack` : "/settings/connections";
|
|
112
|
+
return c.redirect(`${uiOrigin}${successPath}?slack=connected`);
|
|
113
|
+
}
|
|
114
|
+
}),
|
|
115
|
+
registerApiRoute("/web/channel-accounts", {
|
|
116
|
+
method: "GET",
|
|
117
|
+
requiresAuth: false,
|
|
118
|
+
handler: async (c) => {
|
|
119
|
+
await auth.ensureUser(loose(c));
|
|
120
|
+
const tenant = auth.tenant(loose(c));
|
|
121
|
+
if (!tenant) return c.json({ error: "unauthorized" }, 401);
|
|
122
|
+
const accounts = (await accountLinks.listAccountLinksForUser(tenant.userId)).map((link) => ({
|
|
123
|
+
platform: link.platform,
|
|
124
|
+
externalTeamId: link.externalTeamId,
|
|
125
|
+
externalUserId: link.externalUserId,
|
|
126
|
+
externalTeamName: link.externalTeamName,
|
|
127
|
+
externalUserName: link.externalUserName,
|
|
128
|
+
defaultFactoryProjectId: link.defaultFactoryProjectId,
|
|
129
|
+
linkedAt: link.linkedAt.toISOString()
|
|
130
|
+
}));
|
|
131
|
+
return c.json({
|
|
132
|
+
accounts,
|
|
133
|
+
canConnect: oidcEnabled
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}),
|
|
137
|
+
registerApiRoute("/web/channel-accounts", {
|
|
138
|
+
method: "DELETE",
|
|
139
|
+
requiresAuth: false,
|
|
140
|
+
handler: async (c) => {
|
|
141
|
+
await auth.ensureUser(loose(c));
|
|
142
|
+
const tenant = auth.tenant(loose(c));
|
|
143
|
+
if (!tenant) return c.json({ error: "unauthorized" }, 401);
|
|
144
|
+
const body = await c.req.json().catch(() => null);
|
|
145
|
+
if (!body?.platform || !body.externalTeamId || !body.externalUserId) return c.json({ error: "platform, externalTeamId and externalUserId are required" }, 400);
|
|
146
|
+
const deleted = await accountLinks.deleteAccountLinkForUser({
|
|
147
|
+
userId: tenant.userId,
|
|
148
|
+
platform: body.platform,
|
|
149
|
+
externalTeamId: body.externalTeamId,
|
|
150
|
+
externalUserId: body.externalUserId
|
|
151
|
+
});
|
|
152
|
+
return c.json({ deleted });
|
|
153
|
+
}
|
|
154
|
+
}),
|
|
155
|
+
registerApiRoute("/web/channel-accounts/default-factory", {
|
|
156
|
+
method: "PATCH",
|
|
157
|
+
requiresAuth: false,
|
|
158
|
+
handler: async (c) => {
|
|
159
|
+
await auth.ensureUser(loose(c));
|
|
160
|
+
const tenant = auth.tenant(loose(c));
|
|
161
|
+
if (!tenant) return c.json({ error: "unauthorized" }, 401);
|
|
162
|
+
const body = await c.req.json().catch(() => null);
|
|
163
|
+
if (!body?.platform || !body.externalTeamId || !body.externalUserId || body.factoryProjectId === void 0) return c.json({ error: "platform, externalTeamId, externalUserId and factoryProjectId (nullable) are required" }, 400);
|
|
164
|
+
if (body.factoryProjectId !== null) {
|
|
165
|
+
if (!projects) return c.json({ error: "factory routing is not configured" }, 400);
|
|
166
|
+
if (!await projects.get({
|
|
167
|
+
orgId: tenant.orgId ?? "",
|
|
168
|
+
id: body.factoryProjectId
|
|
169
|
+
})) return c.json({ error: "unknown factory" }, 400);
|
|
170
|
+
}
|
|
171
|
+
const updated = await accountLinks.setDefaultFactory({
|
|
172
|
+
userId: tenant.userId,
|
|
173
|
+
platform: body.platform,
|
|
174
|
+
externalTeamId: body.externalTeamId,
|
|
175
|
+
externalUserId: body.externalUserId,
|
|
176
|
+
factoryProjectId: body.factoryProjectId
|
|
177
|
+
});
|
|
178
|
+
return c.json({ updated });
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
export { createSlackConnectRoutes };
|
|
185
|
+
|
|
186
|
+
//# sourceMappingURL=connect-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect-route.js","names":[],"sources":["../../../src/integrations/slack/connect-route.ts"],"sourcesContent":["import { registerApiRoute } from '@mastra/core/server';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { StateSigner } from '../../state-signing.js';\nimport type { ChannelIdentityStorage } from '../../storage/domains/channel-identity/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\n\n/**\n * Payload shape for the Connections list: the platform sender key +\n * link time, without the tenant ids (the caller IS the tenant).\n */\ninterface ConnectedChannelAccountPayload {\n platform: string;\n externalTeamId: string;\n externalUserId: string;\n /** Display names captured at link time (OIDC profile claims); ids fall back. */\n externalTeamName?: string;\n externalUserName?: string;\n /** Which Factory project this link's channel runs route to (unset = not picked yet). */\n defaultFactoryProjectId?: string;\n linkedAt: string;\n}\n\n/**\n * Config for the web-initiated \"Sign in with Slack\" (OIDC) connect flow. All\n * values come from the env-supplied Slack app. Absent → the OIDC routes\n * respond with an error redirect and the list endpoint reports\n * `canConnect: false` so the UI hides its Connect button.\n */\ninterface SlackOidcConfig {\n clientId: string;\n clientSecret: string;\n /**\n * Public HTTPS origin the OIDC `redirect_uri` is built from (Slack rejects\n * plain-http redirect URLs, so locally this is the tunnel origin). Must also\n * be registered as a redirect URL on the Slack app.\n */\n redirectBaseUrl: string;\n /** Browser-facing origin post-connect redirects land on (the SPA host). */\n uiOrigin?: string;\n}\n\nconst SLACK_AUTHORIZE_URL = 'https://slack.com/openid/connect/authorize';\nconst SLACK_TOKEN_URL = 'https://slack.com/api/openid.connect.token';\nconst OIDC_CALLBACK_PATH = '/connect/slack/oidc/callback';\nconst SLACK_TOKEN_TIMEOUT_MS = 10_000;\n/** Matches the signer's own `state` lifetime — past it, `verify` rejects anyway. */\nconst STATE_REPLAY_WINDOW_MS = 10 * 60 * 1000;\n\n/**\n * Decode a JWT's payload WITHOUT signature verification. Safe here because the\n * `id_token` arrives directly from Slack's token endpoint over TLS in a\n * confidential-client code exchange — the transport authenticates the issuer,\n * which is the standard OIDC allowance for this flow. `iss`/`aud`/`exp` are\n * still checked by the caller.\n */\nfunction decodeJwtPayload(jwt: string): Record<string, unknown> | null {\n const payload = jwt.split('.')[1];\n if (!payload) return null;\n try {\n return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Slack account-linking routes.\n *\n * A link is only ever written by the OIDC flow below, where Slack itself\n * asserts the `(team, user)` pair in the id_token — so the web user has to\n * actually control the Slack account they bind. `/connect/slack` is just the\n * Slack-side entry point: it carries no identity and writes nothing, it only\n * sends the visitor to Connections, where the flow starts.\n */\nexport function createSlackConnectRoutes(deps: {\n auth: RouteAuth;\n accountLinks: ChannelIdentityStorage;\n /** Signs the OIDC `state`, binding the round-trip to the initiating tenant. */\n tenantStateSigner?: StateSigner;\n oidc?: SlackOidcConfig;\n /**\n * Factory projects domain, for validating (and listing to) the per-link\n * default factory. Unset → the default-factory PATCH route rejects.\n */\n projects?: FactoryProjectsStorage;\n}): ApiRoute[] {\n const { auth, accountLinks, tenantStateSigner, oidc, projects } = deps;\n const oidcEnabled = Boolean(oidc && tenantStateSigner);\n const uiOrigin = oidc?.uiOrigin?.replace(/\\/$/, '') ?? '';\n\n // A signed `state` stays valid for its whole 10-minute window, so on its own\n // it authorizes the binding repeatedly: anyone who captures a callback URL\n // can re-run the exchange with their own fresh Slack `code` and bind their\n // Slack account to the tenant that started the flow. Burning the nonce makes\n // the state single-use. Scope note: this is per-process, so it does not stop\n // a replay routed to a different replica — narrowing that further needs\n // shared storage, which is out of proportion to a 10-minute window here.\n const consumedNonces = new Map<string, number>();\n const consumeNonce = (nonce: string): boolean => {\n const now = Date.now();\n for (const [seen, expiresAt] of consumedNonces) {\n if (expiresAt <= now) consumedNonces.delete(seen);\n }\n if (consumedNonces.has(nonce)) return false;\n consumedNonces.set(nonce, now + STATE_REPLAY_WINDOW_MS);\n return true;\n };\n\n // mc-web can resolve a different hono version than @mastra/factory, so the\n // registerApiRoute handler `Context` and the factory `RouteAuth` `Context`\n // are structurally incompatible (private `[GET_MATCH_RESULT]` symbol). Cast\n // through `unknown` at the seam — same `loose()` workaround the factory\n // routes use internally.\n type RouteAuthContext = Parameters<RouteAuth['ensureUser']>[0];\n const loose = (c: unknown): RouteAuthContext => c as RouteAuthContext;\n\n return [\n registerApiRoute('/connect/slack', {\n method: 'GET',\n // No auth: nothing here reads a tenant or writes a link. The SPA route\n // this lands on requires a session and bounces through login itself.\n requiresAuth: false,\n handler: async c => c.redirect(`${uiOrigin}/settings/connections`),\n }),\n // Web-initiated connect: \"Sign in with Slack\" (OIDC). The only route that\n // writes a link, because it is the only one that proves the signed-in web\n // user actually CONTROLS the Slack account: Slack itself asserts the\n // (team, user) pair in the id_token.\n registerApiRoute('/connect/slack/oidc/start', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n if (!oidcEnabled) return c.redirect(`${uiOrigin}/?slack=error`);\n\n const factoryProjectId = c.req.query('factoryId')?.trim() || undefined;\n const startPath = factoryProjectId\n ? `/connect/slack/oidc/start?factoryId=${encodeURIComponent(factoryProjectId)}`\n : '/connect/slack/oidc/start';\n\n await auth.ensureUser(loose(c));\n const tenant = auth.tenant(loose(c));\n if (!tenant) {\n return c.redirect(`/auth/login?returnTo=${encodeURIComponent(startPath)}`);\n }\n\n const params = new URLSearchParams({\n response_type: 'code',\n // `profile` adds display-name claims (user name, team name) to the\n // id_token so Connections can show names instead of ids.\n scope: 'openid profile',\n client_id: oidc!.clientId,\n // Personal accounts have no org; the signer requires a string, so an\n // empty org round-trips and is mapped back to undefined on save.\n state: tenantStateSigner!.sign(tenant.orgId ?? '', tenant.userId, { factoryProjectId }),\n redirect_uri: `${oidc!.redirectBaseUrl.replace(/\\/$/, '')}${OIDC_CALLBACK_PATH}`,\n });\n return c.redirect(`${SLACK_AUTHORIZE_URL}?${params.toString()}`);\n },\n }),\n // OIDC callback. Authenticates via the signed `state` (which carries the\n // initiating tenant) rather than the session cookie — the callback may\n // arrive on a different origin (the public tunnel) where the SPA's\n // host-scoped cookie is not sent.\n registerApiRoute(OIDC_CALLBACK_PATH, {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n if (!oidcEnabled) return c.redirect(`${uiOrigin}/?slack=error`);\n\n const tenant = tenantStateSigner!.verify(c.req.query('state'));\n const code = c.req.query('code');\n if (!tenant || !code) return c.redirect(`${uiOrigin}/?slack=error`);\n // Burn the state before spending the code, not after saving: a replay\n // must be refused even if the first attempt fails partway through.\n if (!consumeNonce(tenant.nonce)) return c.redirect(`${uiOrigin}/?slack=error`);\n\n // Bound the exchange: without a deadline a slow token endpoint parks the\n // request until the platform's own timeout, and this route is reachable\n // by anyone holding a signed state.\n const tokenRes = await fetch(SLACK_TOKEN_URL, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: oidc!.clientId,\n client_secret: oidc!.clientSecret,\n code,\n redirect_uri: `${oidc!.redirectBaseUrl.replace(/\\/$/, '')}${OIDC_CALLBACK_PATH}`,\n }),\n signal: AbortSignal.timeout(SLACK_TOKEN_TIMEOUT_MS),\n }).catch(() => null);\n const token = (await tokenRes?.json().catch(() => null)) as { ok?: boolean; id_token?: string } | null;\n if (!tokenRes?.ok || !token?.ok || typeof token.id_token !== 'string') {\n return c.redirect(`${uiOrigin}/?slack=error`);\n }\n\n const claims = decodeJwtPayload(token.id_token);\n const teamId = claims?.['https://slack.com/team_id'];\n const slackUserId = claims?.['https://slack.com/user_id'];\n // Display-only claims from the `profile` scope — absent claims just\n // mean the settings list falls back to ids.\n const teamName = claims?.['https://slack.com/team_name'];\n const userName = claims?.name;\n // `exp` is seconds since the epoch. A missing or past `exp` means the\n // assertion is not currently valid, so it cannot bind an account.\n const expiresAt = typeof claims?.exp === 'number' ? claims.exp * 1000 : null;\n if (\n !claims ||\n claims.iss !== 'https://slack.com' ||\n claims.aud !== oidc!.clientId ||\n expiresAt === null ||\n expiresAt <= Date.now() ||\n typeof teamId !== 'string' ||\n typeof slackUserId !== 'string'\n ) {\n return c.redirect(`${uiOrigin}/?slack=error`);\n }\n\n await accountLinks.saveAccountLink({\n platform: 'slack',\n externalTeamId: teamId,\n externalUserId: slackUserId,\n orgId: tenant.orgId || undefined,\n userId: tenant.userId,\n externalTeamName: typeof teamName === 'string' ? teamName : undefined,\n externalUserName: typeof userName === 'string' ? userName : undefined,\n });\n\n const successPath = tenant.factoryProjectId\n ? `/factories/${encodeURIComponent(tenant.factoryProjectId)}/settings/connections/slack`\n : '/settings/connections';\n return c.redirect(`${uiOrigin}${successPath}?slack=connected`);\n },\n }),\n // The caller's own linked channel accounts for Connections. Tenant-scoped:\n // you only ever see your own links.\n registerApiRoute('/web/channel-accounts', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n await auth.ensureUser(loose(c));\n const tenant = auth.tenant(loose(c));\n if (!tenant) return c.json({ error: 'unauthorized' }, 401);\n\n const links = await accountLinks.listAccountLinksForUser(tenant.userId);\n const accounts: ConnectedChannelAccountPayload[] = links.map(link => ({\n platform: link.platform,\n externalTeamId: link.externalTeamId,\n externalUserId: link.externalUserId,\n externalTeamName: link.externalTeamName,\n externalUserName: link.externalUserName,\n defaultFactoryProjectId: link.defaultFactoryProjectId,\n linkedAt: link.linkedAt.toISOString(),\n }));\n // `canConnect` tells the settings UI whether the web-initiated OIDC\n // connect flow is configured (vs. Slack-side Connect card only).\n return c.json({ accounts, canConnect: oidcEnabled });\n },\n }),\n // Self-service disconnect. The storage delete is guarded by the caller's\n // tenant userId, so a known sender key alone can never sever someone\n // else's link.\n registerApiRoute('/web/channel-accounts', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async c => {\n await auth.ensureUser(loose(c));\n const tenant = auth.tenant(loose(c));\n if (!tenant) return c.json({ error: 'unauthorized' }, 401);\n\n const body = (await c.req.json().catch(() => null)) as {\n platform?: string;\n externalTeamId?: string;\n externalUserId?: string;\n } | null;\n if (!body?.platform || !body.externalTeamId || !body.externalUserId) {\n return c.json({ error: 'platform, externalTeamId and externalUserId are required' }, 400);\n }\n\n const deleted = await accountLinks.deleteAccountLinkForUser({\n userId: tenant.userId,\n platform: body.platform,\n externalTeamId: body.externalTeamId,\n externalUserId: body.externalUserId,\n });\n return c.json({ deleted });\n },\n }),\n // Set (or clear, with `factoryProjectId: null`) which Factory project a\n // link's channel runs route to. The storage write is guarded by the\n // caller's tenant userId — a known sender key alone can never repoint\n // someone else's link — and a non-null factory must exist in the caller's\n // org.\n registerApiRoute('/web/channel-accounts/default-factory', {\n method: 'PATCH',\n requiresAuth: false,\n handler: async c => {\n await auth.ensureUser(loose(c));\n const tenant = auth.tenant(loose(c));\n if (!tenant) return c.json({ error: 'unauthorized' }, 401);\n\n const body = (await c.req.json().catch(() => null)) as {\n platform?: string;\n externalTeamId?: string;\n externalUserId?: string;\n factoryProjectId?: string | null;\n } | null;\n if (!body?.platform || !body.externalTeamId || !body.externalUserId || body.factoryProjectId === undefined) {\n return c.json(\n { error: 'platform, externalTeamId, externalUserId and factoryProjectId (nullable) are required' },\n 400,\n );\n }\n\n if (body.factoryProjectId !== null) {\n if (!projects) return c.json({ error: 'factory routing is not configured' }, 400);\n const factory = await projects.get({ orgId: tenant.orgId ?? '', id: body.factoryProjectId });\n if (!factory) return c.json({ error: 'unknown factory' }, 400);\n }\n\n const updated = await accountLinks.setDefaultFactory({\n userId: tenant.userId,\n platform: body.platform,\n externalTeamId: body.externalTeamId,\n externalUserId: body.externalUserId,\n factoryProjectId: body.factoryProjectId,\n });\n return c.json({ updated });\n },\n }),\n ];\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SlackIntegration` — Slack as a `FactoryIntegration`.
|
|
3
|
+
*
|
|
4
|
+
* Slack contributes two things to a factory: the chat channels that carry
|
|
5
|
+
* inbound messages into agent runs (`channels()`), and the browser-facing
|
|
6
|
+
* account-link routes that bind a Slack sender to a Mastra tenant (`routes()`).
|
|
7
|
+
* Both used to be assembled by hand in the deploy entry, which had to reach
|
|
8
|
+
* into the prepared agent controller by string key, resolve storage domains
|
|
9
|
+
* itself, mint its own state signer, and splice routes onto the factory's
|
|
10
|
+
* assembled server config. Implementing the integration interface instead means
|
|
11
|
+
* the factory does all of that the same way it already does for GitHub and
|
|
12
|
+
* Linear, and the entry's job shrinks to reading Slack's env vars once.
|
|
13
|
+
*
|
|
14
|
+
* Slack ships as a factory built-in alongside GitHub and Linear (it originally
|
|
15
|
+
* lived in `mastracode/web` to keep `@mastra/slack`/`chat` out of this package;
|
|
16
|
+
* the team reversed that so `create-factory` consumers get Slack channels out
|
|
17
|
+
* of the box). The integration interface remains open — third parties still add
|
|
18
|
+
* capabilities by implementing `FactoryIntegration` from outside the package.
|
|
19
|
+
*/
|
|
20
|
+
import type { ApiRoute } from '@mastra/core/server';
|
|
21
|
+
import type { FactoryChannelsConfig, FactoryIntegration, IntegrationContext } from '../base.js';
|
|
22
|
+
/**
|
|
23
|
+
* Slack app credentials, read from env ONCE by the deploy entry. `signingSecret`
|
|
24
|
+
* is required because the Slack adapter validates it at construction — an
|
|
25
|
+
* integration constructed without it would throw during `prepare()` rather than
|
|
26
|
+
* reporting itself unconfigured.
|
|
27
|
+
*/
|
|
28
|
+
export interface SlackIntegrationConfig {
|
|
29
|
+
/** Verifies inbound Slack request signatures. Required. */
|
|
30
|
+
signingSecret: string;
|
|
31
|
+
/** Bot token used to post replies and ephemeral cards. */
|
|
32
|
+
botToken?: string;
|
|
33
|
+
/**
|
|
34
|
+
* OAuth client credentials. Required for account linking: "Sign in with
|
|
35
|
+
* Slack" (OIDC) is the only flow that writes a link, so without these the
|
|
36
|
+
* settings surface reports `canConnect: false` and no sender can link.
|
|
37
|
+
*/
|
|
38
|
+
clientId?: string;
|
|
39
|
+
clientSecret?: string;
|
|
40
|
+
/**
|
|
41
|
+
* HTTPS origin Slack redirects back to. Slack requires HTTPS, so locally this
|
|
42
|
+
* is the tunnel origin rather than the app's own public URL.
|
|
43
|
+
*/
|
|
44
|
+
oidcRedirectBaseUrl?: string;
|
|
45
|
+
/** SPA origin the post-connect redirect returns to. */
|
|
46
|
+
uiOrigin?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare class SlackIntegration implements FactoryIntegration {
|
|
49
|
+
#private;
|
|
50
|
+
readonly id = "slack";
|
|
51
|
+
/**
|
|
52
|
+
* The OIDC connect flow round-trips a signed `state` through Slack, so the
|
|
53
|
+
* replica handling the callback must be able to verify a state a different
|
|
54
|
+
* replica signed.
|
|
55
|
+
*/
|
|
56
|
+
readonly requiresStableStateSigner = true;
|
|
57
|
+
constructor(config: SlackIntegrationConfig);
|
|
58
|
+
channels(ctx: IntegrationContext): FactoryChannelsConfig;
|
|
59
|
+
routes(ctx: IntegrationContext): ApiRoute[];
|
|
60
|
+
diagnostics(): Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=integration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAKhG;;;;;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;CACnB;AAED,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;IAoBxD,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAc3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASvC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createSlackConnectRoutes } from "./connect-route.js";
|
|
2
|
+
import { createSlackChannelsConfig } from "./slack.js";
|
|
3
|
+
//#region src/integrations/slack/integration.ts
|
|
4
|
+
var SlackIntegration = class {
|
|
5
|
+
id = "slack";
|
|
6
|
+
/**
|
|
7
|
+
* The OIDC connect flow round-trips a signed `state` through Slack, so the
|
|
8
|
+
* replica handling the callback must be able to verify a state a different
|
|
9
|
+
* replica signed.
|
|
10
|
+
*/
|
|
11
|
+
requiresStableStateSigner = true;
|
|
12
|
+
#config;
|
|
13
|
+
/**
|
|
14
|
+
* Whether `channels()` found a source-control owner on the context and wired
|
|
15
|
+
* repo-backed sessions. Set at the channels() attach path, which runs once at
|
|
16
|
+
* boot before diagnostics are served.
|
|
17
|
+
*/
|
|
18
|
+
#repoBackedSessions = false;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
if (!config.signingSecret) throw new Error("SlackIntegration: 'signingSecret' is required — Slack cannot verify inbound requests without it.");
|
|
21
|
+
this.#config = config;
|
|
22
|
+
}
|
|
23
|
+
channels(ctx) {
|
|
24
|
+
const sourceControlOwner = ctx.storage.sourceControlOwner;
|
|
25
|
+
this.#repoBackedSessions = Boolean(sourceControlOwner);
|
|
26
|
+
return createSlackChannelsConfig({
|
|
27
|
+
slack: {
|
|
28
|
+
clientId: this.#config.clientId,
|
|
29
|
+
clientSecret: this.#config.clientSecret,
|
|
30
|
+
signingSecret: this.#config.signingSecret,
|
|
31
|
+
botToken: this.#config.botToken
|
|
32
|
+
},
|
|
33
|
+
accountLinks: ctx.storage.channelIdentity,
|
|
34
|
+
projects: ctx.storage.projects,
|
|
35
|
+
sourceControl: sourceControlOwner,
|
|
36
|
+
memorySettings: ctx.storage.memorySettings,
|
|
37
|
+
workItems: ctx.rules?.workItems
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
routes(ctx) {
|
|
41
|
+
const { clientId, clientSecret, oidcRedirectBaseUrl, uiOrigin } = this.#config;
|
|
42
|
+
return createSlackConnectRoutes({
|
|
43
|
+
auth: ctx.auth,
|
|
44
|
+
accountLinks: ctx.storage.channelIdentity,
|
|
45
|
+
tenantStateSigner: ctx.stateSigner,
|
|
46
|
+
oidc: clientId && clientSecret && oidcRedirectBaseUrl ? {
|
|
47
|
+
clientId,
|
|
48
|
+
clientSecret,
|
|
49
|
+
redirectBaseUrl: oidcRedirectBaseUrl,
|
|
50
|
+
uiOrigin
|
|
51
|
+
} : void 0,
|
|
52
|
+
projects: ctx.storage.projects
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
diagnostics() {
|
|
56
|
+
const { clientId, clientSecret, botToken, oidcRedirectBaseUrl } = this.#config;
|
|
57
|
+
return {
|
|
58
|
+
configured: true,
|
|
59
|
+
botTokenConfigured: Boolean(botToken),
|
|
60
|
+
oidcConfigured: Boolean(clientId && clientSecret && oidcRedirectBaseUrl),
|
|
61
|
+
repoBackedSessions: this.#repoBackedSessions
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { SlackIntegration };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=integration.js.map
|
|
@@ -0,0 +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 { ApiRoute } from '@mastra/core/server';\n\nimport type { FactoryChannelsConfig, FactoryIntegration, IntegrationContext } from '../base.js';\n\nimport { createSlackConnectRoutes } from './connect-route.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\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 come from the factory's source-control owner\n // (GitHub, when registered) — no config-level wiring by the entry.\n const sourceControlOwner = ctx.storage.sourceControlOwner;\n this.#repoBackedSessions = Boolean(sourceControlOwner);\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 sourceControl: sourceControlOwner,\n memorySettings: ctx.storage.memorySettings,\n workItems: ctx.rules?.workItems,\n });\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":";;;AAsDA,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,qBAAqB,IAAI,QAAQ;EACvC,KAAKC,sBAAsB,QAAQ,kBAAkB;EACrD,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,eAAe;GACf,gBAAgB,IAAI,QAAQ;GAC5B,WAAW,IAAI,OAAO;EACxB,CAAC;CACH;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"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { ChannelHandler, ChannelHandlers, ChannelSessionStart, ResolveResourceId, ResolveThreadId } from '@mastra/core/channels';
|
|
2
|
+
import type { ChannelAccountLink, ChannelAccountLinkKey, ChannelIdentityStorage } from '../../storage/domains/channel-identity/base.js';
|
|
3
|
+
import type { MemorySettingsStorage } from '../../storage/domains/memory-settings/base.js';
|
|
4
|
+
import type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';
|
|
5
|
+
import type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';
|
|
6
|
+
import type { WorkItemsStorage } from '../../storage/domains/work-items/base.js';
|
|
7
|
+
import type { FactoryChannelsConfig } from '../base.js';
|
|
8
|
+
type HandlerThread = Parameters<ChannelHandler>[0];
|
|
9
|
+
type HandlerMessage = Parameters<ChannelHandler>[1];
|
|
10
|
+
/** Dependencies the Slack channel handlers close over, injected from the web entry. */
|
|
11
|
+
interface SlackChannelDeps {
|
|
12
|
+
/**
|
|
13
|
+
* The factory's reverse-index store mapping a Slack sender to a Mastra
|
|
14
|
+
* tenant. When provided, inbound messages from an unlinked sender are not
|
|
15
|
+
* dispatched — the run only proceeds (with the sender's tenant stamped on
|
|
16
|
+
* the request context) once they've linked their account. Unlinked senders
|
|
17
|
+
* get an ephemeral "connect your account" card instead.
|
|
18
|
+
*/
|
|
19
|
+
accountLinks?: ChannelIdentityStorage;
|
|
20
|
+
/**
|
|
21
|
+
* Factory projects domain. When provided (alongside `accountLinks`), a
|
|
22
|
+
* linked sender's run must also resolve to a Factory project before it
|
|
23
|
+
* dispatches: their link's default factory, else their tenant's only
|
|
24
|
+
* factory (stamped back onto the link), else an ephemeral "pick a default
|
|
25
|
+
* factory" card and no run. Unset → no factory routing (runs dispatch as
|
|
26
|
+
* before).
|
|
27
|
+
*/
|
|
28
|
+
projects?: FactoryProjectsStorage;
|
|
29
|
+
/**
|
|
30
|
+
* Storage handle of the integration that owns source control
|
|
31
|
+
* (`IntegrationContext.storage.sourceControlOwner`). Used to make new Slack
|
|
32
|
+
* threads repo-backed: when the sender is linked and their factory has a
|
|
33
|
+
* repository, the thread's resourceId becomes a Factory user-session id (repo
|
|
34
|
+
* cloned on a `slack/{threadTs}` branch) instead of the chat-only
|
|
35
|
+
* `channel:...` id. It also lets a started session read back the project it
|
|
36
|
+
* belongs to. Nothing here is provider-specific — the connection is matched
|
|
37
|
+
* by the handle's own `integrationId`. Absent (no source-control integration
|
|
38
|
+
* registered) → chat-only sessions as before.
|
|
39
|
+
*/
|
|
40
|
+
sourceControl?: SourceControlStorageHandle;
|
|
41
|
+
/**
|
|
42
|
+
* Observational-memory settings domain. When provided, a repo-backed session
|
|
43
|
+
* adopts its owner's memory settings on start, matching the web kickoff.
|
|
44
|
+
*/
|
|
45
|
+
memorySettings?: MemorySettingsStorage;
|
|
46
|
+
/**
|
|
47
|
+
* Factory work-items domain. When provided, a dispatched new-session thread
|
|
48
|
+
* (DM or mention) upserts a Work-board card in Building (`execute`) carrying
|
|
49
|
+
* the Slack thread as its external source and binding the repo-backed
|
|
50
|
+
* session. Best-effort — a failure never blocks the run. Unset → no card.
|
|
51
|
+
*/
|
|
52
|
+
workItems?: WorkItemsStorage;
|
|
53
|
+
}
|
|
54
|
+
/** Outcome of the sender-link gate for one inbound message. */
|
|
55
|
+
type LinkedSenderResult =
|
|
56
|
+
/** Gating not configured — dispatch as before account linking existed. */
|
|
57
|
+
{
|
|
58
|
+
status: 'ungated';
|
|
59
|
+
}
|
|
60
|
+
/** Sender unlinked — Connect card posted (when possible), do not dispatch. */
|
|
61
|
+
| {
|
|
62
|
+
status: 'blocked';
|
|
63
|
+
}
|
|
64
|
+
/** Sender linked — their tenant plus the sender key the link lives under. */
|
|
65
|
+
| {
|
|
66
|
+
status: 'linked';
|
|
67
|
+
link: ChannelAccountLink;
|
|
68
|
+
key: ChannelAccountLinkKey;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the sender's account link, posting an ephemeral "connect your
|
|
72
|
+
* account" card (visible only to the sender) linking into the web UI's
|
|
73
|
+
* Slack-connect flow when they're unlinked.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveLinkedSender({ thread, message, accountLinks, }: {
|
|
76
|
+
thread: HandlerThread;
|
|
77
|
+
message: HandlerMessage;
|
|
78
|
+
accountLinks?: ChannelIdentityStorage;
|
|
79
|
+
}): Promise<LinkedSenderResult>;
|
|
80
|
+
/** Outcome of factory routing for one linked sender's inbound message. */
|
|
81
|
+
type FactoryRouteResult =
|
|
82
|
+
/** Factory routing not configured — dispatch without a factory. */
|
|
83
|
+
{
|
|
84
|
+
status: 'ungated';
|
|
85
|
+
}
|
|
86
|
+
/** No factory resolved — prompt card posted (when possible), do not dispatch. */
|
|
87
|
+
| {
|
|
88
|
+
status: 'blocked';
|
|
89
|
+
}
|
|
90
|
+
/** The Factory project this sender's runs route to. */
|
|
91
|
+
| {
|
|
92
|
+
status: 'resolved';
|
|
93
|
+
factoryProjectId: string;
|
|
94
|
+
slackWorkItemsEnabled: boolean;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Decide which Factory project a linked sender's run belongs to:
|
|
98
|
+
*
|
|
99
|
+
* 1. The link's `defaultFactoryProjectId`, when it still exists (a stale id —
|
|
100
|
+
* deleted factory — falls through as if unset).
|
|
101
|
+
* 2. Else, the tenant's only factory, stamped back onto the link so it shows
|
|
102
|
+
* up (and stays editable) in Connected Accounts settings.
|
|
103
|
+
* 3. Else — zero or several factories — an ephemeral "pick a default factory"
|
|
104
|
+
* card deep-linking to settings, and the run is blocked.
|
|
105
|
+
*/
|
|
106
|
+
export declare function resolveFactoryForLink({ thread, message, link, key, accountLinks, projects, }: {
|
|
107
|
+
thread: HandlerThread;
|
|
108
|
+
message: HandlerMessage;
|
|
109
|
+
link: ChannelAccountLink;
|
|
110
|
+
key: ChannelAccountLinkKey;
|
|
111
|
+
accountLinks: ChannelIdentityStorage;
|
|
112
|
+
projects?: FactoryProjectsStorage;
|
|
113
|
+
}): Promise<FactoryRouteResult>;
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the resourceId for a NEW Slack channel thread. A linked sender whose
|
|
116
|
+
* factory has a repository gets a Factory user-session id — the controller
|
|
117
|
+
* session then materializes the repo sandbox via the factory's dynamic
|
|
118
|
+
* workspace (clone + PAT), the session shows up in the web Sessions list, and
|
|
119
|
+
* View Session deep-links land on the normal workspace route. Everything else
|
|
120
|
+
* (unlinked, unrouted, repo-less, or no source control) keeps the chat-only
|
|
121
|
+
* `defaultResourceId`.
|
|
122
|
+
*
|
|
123
|
+
* Pure lookups only — cards for unlinked/unrouted senders are the dispatch
|
|
124
|
+
* gate's job; this hook must never post.
|
|
125
|
+
*/
|
|
126
|
+
export declare function createChannelResourceIdResolver(deps: SlackChannelDeps): ResolveResourceId;
|
|
127
|
+
/**
|
|
128
|
+
* Thread id for a NEW Slack channel thread. Repo-backed threads take the
|
|
129
|
+
* user-session id AS their thread id, matching the web convention
|
|
130
|
+
* (FactoryStartCoordinator seeds threads with threadId = sessionId) so
|
|
131
|
+
* `/workspaces/{sessionId}/threads/{sessionId}` resolves Slack-created
|
|
132
|
+
* sessions exactly like web-created ones — no `?resourceId=` override needed.
|
|
133
|
+
* Chat-only threads keep the default random id: their `channel:...`
|
|
134
|
+
* resourceId is a memory key, not a unique thread id.
|
|
135
|
+
*/
|
|
136
|
+
export declare const resolveChannelThreadId: ResolveThreadId;
|
|
137
|
+
/**
|
|
138
|
+
* Apply the factory's configuration to a Slack-created session the first time
|
|
139
|
+
* its thread reaches the controller.
|
|
140
|
+
*
|
|
141
|
+
* Without this a Slack session runs on the SDK's built-in mode default
|
|
142
|
+
* (`openai/gpt-5.5`), so a factory configured for any other provider fails every
|
|
143
|
+
* message with a missing-credentials error. The web kickoff has always applied
|
|
144
|
+
* the factory default; this brings Slack to the same footing.
|
|
145
|
+
*
|
|
146
|
+
* Only repo-backed threads are configured. Their resourceId IS the Factory
|
|
147
|
+
* session id, which the source-control rows turn back into a project — a
|
|
148
|
+
* chat-only `channel:...` id names no project, so there is nothing to read.
|
|
149
|
+
*
|
|
150
|
+
* Skips a session whose mode already has a model persisted on the thread. That
|
|
151
|
+
* is the durable record of a deliberate choice — either an earlier start or a
|
|
152
|
+
* user's own switch — and re-applying the factory default over it would undo
|
|
153
|
+
* the user's selection every time the process restarts.
|
|
154
|
+
*/
|
|
155
|
+
export declare function createChannelSessionStartHook(deps: SlackChannelDeps): ChannelSessionStart;
|
|
156
|
+
/**
|
|
157
|
+
* Upsert the Work-board card for a dispatched Slack-thread run. Keyed on the
|
|
158
|
+
* thread via `externalSource` — the work-items domain's unique
|
|
159
|
+
* `(factory_project_id, source_key)` index makes repeat messages reuse the
|
|
160
|
+
* same card, and `reuseMode: 'preserve'` keeps a card a human already dragged
|
|
161
|
+
* across stages untouched. The card lands in Building (`execute`) for every
|
|
162
|
+
* dispatched thread (DM or mention) — there is deliberately no per-origin
|
|
163
|
+
* stage split; smart routing is a follow-up.
|
|
164
|
+
*
|
|
165
|
+
* The session id / branch / threadId and the workspace deep-link are resolved
|
|
166
|
+
* by the caller (which already looked up the internal thread), so this helper
|
|
167
|
+
* just shapes and writes. Best-effort: the run is already dispatched, so a
|
|
168
|
+
* failure logs instead of throwing — work-item creation must never abort a Slack run.
|
|
169
|
+
*/
|
|
170
|
+
export declare function upsertThreadWorkItem({ workItems, thread, message, link, factoryProjectId, session, url, }: {
|
|
171
|
+
workItems: WorkItemsStorage;
|
|
172
|
+
thread: HandlerThread;
|
|
173
|
+
message: HandlerMessage;
|
|
174
|
+
link: ChannelAccountLink;
|
|
175
|
+
factoryProjectId: string;
|
|
176
|
+
/**
|
|
177
|
+
* The repo-backed Factory session to bind under the `chat` role, or
|
|
178
|
+
* `undefined` for a chat-only thread (no Factory session to bind).
|
|
179
|
+
*/
|
|
180
|
+
session?: {
|
|
181
|
+
sessionId: string;
|
|
182
|
+
branch: string;
|
|
183
|
+
threadId: string;
|
|
184
|
+
};
|
|
185
|
+
/** Workspace deep-link to the running session; omitted when no public URL. */
|
|
186
|
+
url?: string;
|
|
187
|
+
}): Promise<void>;
|
|
188
|
+
export declare const createHandlers: (deps: SlackChannelDeps) => ChannelHandlers;
|
|
189
|
+
/** Slack app credentials, passed in explicitly rather than read from env here. */
|
|
190
|
+
interface SlackCredentials {
|
|
191
|
+
clientId?: string;
|
|
192
|
+
clientSecret?: string;
|
|
193
|
+
signingSecret: string;
|
|
194
|
+
botToken?: string;
|
|
195
|
+
}
|
|
196
|
+
export declare function createSlackChannelsConfig(deps: SlackChannelDeps & {
|
|
197
|
+
slack: SlackCredentials;
|
|
198
|
+
}): FactoryChannelsConfig;
|
|
199
|
+
export {};
|
|
200
|
+
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/slack.ts"],"names":[],"mappings":"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"}
|