@shipfox/api-integration-slack 7.1.0 → 9.0.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.
Files changed (59) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +43 -0
  3. package/dist/core/agent-tools-provider.d.ts +9 -9
  4. package/dist/core/agent-tools-provider.d.ts.map +1 -1
  5. package/dist/core/agent-tools-provider.js.map +1 -1
  6. package/dist/core/agent-tools.d.ts +1 -1
  7. package/dist/core/agent-tools.d.ts.map +1 -1
  8. package/dist/core/agent-tools.js.map +1 -1
  9. package/dist/core/errors.d.ts +1 -1
  10. package/dist/core/errors.d.ts.map +1 -1
  11. package/dist/core/errors.js +1 -1
  12. package/dist/core/errors.js.map +1 -1
  13. package/dist/core/install.d.ts +1 -1
  14. package/dist/core/install.d.ts.map +1 -1
  15. package/dist/core/install.js.map +1 -1
  16. package/dist/core/webhook-processor.d.ts +1 -1
  17. package/dist/core/webhook-processor.d.ts.map +1 -1
  18. package/dist/core/webhook-processor.js +1 -1
  19. package/dist/core/webhook-processor.js.map +1 -1
  20. package/dist/core/webhook.d.ts +1 -1
  21. package/dist/core/webhook.d.ts.map +1 -1
  22. package/dist/core/webhook.js.map +1 -1
  23. package/dist/presentation/dto/integrations.d.ts +15 -1
  24. package/dist/presentation/dto/integrations.d.ts.map +1 -1
  25. package/dist/presentation/dto/integrations.js +14 -1
  26. package/dist/presentation/dto/integrations.js.map +1 -1
  27. package/dist/presentation/e2eRoutes/create-connection.d.ts +1 -1
  28. package/dist/presentation/e2eRoutes/create-connection.d.ts.map +1 -1
  29. package/dist/presentation/e2eRoutes/create-connection.js.map +1 -1
  30. package/dist/presentation/routes/errors.js +1 -1
  31. package/dist/presentation/routes/errors.js.map +1 -1
  32. package/dist/presentation/routes/install.d.ts +1 -1
  33. package/dist/presentation/routes/install.d.ts.map +1 -1
  34. package/dist/presentation/routes/install.js.map +1 -1
  35. package/dist/presentation/routes/webhooks.d.ts +1 -1
  36. package/dist/presentation/routes/webhooks.d.ts.map +1 -1
  37. package/dist/presentation/routes/webhooks.js +1 -1
  38. package/dist/presentation/routes/webhooks.js.map +1 -1
  39. package/dist/tsconfig.test.tsbuildinfo +1 -1
  40. package/package.json +14 -15
  41. package/src/core/agent-tools-provider.test.ts +1 -1
  42. package/src/core/agent-tools-provider.ts +1 -1
  43. package/src/core/agent-tools.ts +1 -1
  44. package/src/core/errors.ts +1 -1
  45. package/src/core/install.test.ts +1 -1
  46. package/src/core/install.ts +1 -1
  47. package/src/core/webhook-processor.test.ts +2 -2
  48. package/src/core/webhook-processor.ts +6 -6
  49. package/src/core/webhook.test.ts +1 -1
  50. package/src/core/webhook.ts +8 -8
  51. package/src/presentation/dto/integrations.ts +19 -1
  52. package/src/presentation/e2eRoutes/create-connection.ts +1 -1
  53. package/src/presentation/e2eRoutes/index.test.ts +1 -1
  54. package/src/presentation/routes/errors.ts +1 -1
  55. package/src/presentation/routes/install.test.ts +1 -1
  56. package/src/presentation/routes/install.ts +1 -1
  57. package/src/presentation/routes/webhooks.test.ts +2 -2
  58. package/src/presentation/routes/webhooks.ts +2 -5
  59. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 25 files with swc (332.68ms)
2
+ Successfully compiled: 25 files with swc (326.56ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # @shipfox/api-integration-slack
2
2
 
3
+ ## 9.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 475ce59: Republishes all public packages after restoring release authorization.
8
+ - Updated dependencies [475ce59]
9
+ - @shipfox/api-auth-context@9.0.1
10
+ - @shipfox/api-integration-slack-dto@9.0.1
11
+ - @shipfox/api-integration-spi@0.2.1
12
+ - @shipfox/api-workspaces-dto@9.0.1
13
+ - @shipfox/config@1.2.3
14
+ - @shipfox/inter-module@0.2.1
15
+ - @shipfox/node-drizzle@0.3.3
16
+ - @shipfox/node-fastify@0.3.1
17
+ - @shipfox/node-opentelemetry@0.6.1
18
+ - @shipfox/node-postgres@0.4.3
19
+
20
+ ## 9.0.0
21
+
22
+ ### Patch Changes
23
+
24
+ - 4a6d124: Separates Integrations provider SPI contracts from the public DTO surface.
25
+ - Updated dependencies [9c9d266]
26
+ - Updated dependencies [4a6d124]
27
+ - @shipfox/api-workspaces-dto@9.0.0
28
+ - @shipfox/api-integration-spi@0.2.0
29
+ - @shipfox/api-auth-context@9.0.0
30
+ - @shipfox/api-integration-slack-dto@9.0.0
31
+ - @shipfox/config@1.2.2
32
+ - @shipfox/inter-module@0.2.0
33
+ - @shipfox/node-drizzle@0.3.2
34
+ - @shipfox/node-fastify@0.3.0
35
+ - @shipfox/node-opentelemetry@0.6.0
36
+ - @shipfox/node-postgres@0.4.2
37
+
38
+ ## 8.0.0
39
+
40
+ ### Patch Changes
41
+
42
+ - Updated dependencies [7f227c6]
43
+ - @shipfox/api-integration-core-dto@8.0.0
44
+ - @shipfox/api-integration-slack-dto@8.0.0
45
+
3
46
  ## 7.1.0
4
47
 
5
48
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import type { AgentToolSession, AgentToolsProvider, IntegrationConnection, OpenAgentToolsSessionInput } from '@shipfox/api-integration-core-dto';
1
+ import type { AgentToolSession, AgentToolsProvider, IntegrationConnection, OpenAgentToolsSessionInput } from '@shipfox/api-integration-spi';
2
2
  import type { SlackApiClient } from '#api/client.js';
3
3
  import { type SlackAgentToolRequiredScope } from '#core/agent-tools.js';
4
4
  import type { SlackTokenStore } from '#core/tokens.js';
@@ -24,51 +24,51 @@ export declare class SlackAgentToolsProvider implements AgentToolsProvider<Slack
24
24
  readonly sensitivity: "read";
25
25
  readonly sensitive: false;
26
26
  readonly requiredScope: "read";
27
- readonly inputSchema: import("@shipfox/api-integration-core-dto").AgentToolJsonSchema;
27
+ readonly inputSchema: import("@shipfox/api-integration-spi").AgentToolJsonSchema;
28
28
  }, {
29
29
  readonly id: "conversations_replies";
30
30
  readonly description: "List replies in a Slack message thread.";
31
31
  readonly sensitivity: "read";
32
32
  readonly sensitive: false;
33
33
  readonly requiredScope: "read";
34
- readonly inputSchema: import("@shipfox/api-integration-core-dto").AgentToolJsonSchema;
34
+ readonly inputSchema: import("@shipfox/api-integration-spi").AgentToolJsonSchema;
35
35
  }, {
36
36
  readonly id: "conversations_list";
37
37
  readonly description: "List Slack channels visible to the bot.";
38
38
  readonly sensitivity: "read";
39
39
  readonly sensitive: false;
40
40
  readonly requiredScope: "read";
41
- readonly inputSchema: import("@shipfox/api-integration-core-dto").AgentToolJsonSchema;
41
+ readonly inputSchema: import("@shipfox/api-integration-spi").AgentToolJsonSchema;
42
42
  }, {
43
43
  readonly id: "users_info";
44
44
  readonly description: "Look up a Slack user and profile by user ID.";
45
45
  readonly sensitivity: "read";
46
46
  readonly sensitive: false;
47
47
  readonly requiredScope: "read";
48
- readonly inputSchema: import("@shipfox/api-integration-core-dto").AgentToolJsonSchema;
48
+ readonly inputSchema: import("@shipfox/api-integration-spi").AgentToolJsonSchema;
49
49
  }, {
50
50
  readonly id: "chat_postMessage";
51
51
  readonly description: "Post a Slack message to a channel or thread. Provide text, blocks, or both.";
52
52
  readonly sensitivity: "write";
53
53
  readonly sensitive: false;
54
54
  readonly requiredScope: "write";
55
- readonly inputSchema: import("@shipfox/api-integration-core-dto").AgentToolJsonSchema;
55
+ readonly inputSchema: import("@shipfox/api-integration-spi").AgentToolJsonSchema;
56
56
  }, {
57
57
  readonly id: "chat_update";
58
58
  readonly description: "Update a Slack message with text, blocks, or both.";
59
59
  readonly sensitivity: "write";
60
60
  readonly sensitive: false;
61
61
  readonly requiredScope: "write";
62
- readonly inputSchema: import("@shipfox/api-integration-core-dto").AgentToolJsonSchema;
62
+ readonly inputSchema: import("@shipfox/api-integration-spi").AgentToolJsonSchema;
63
63
  }, {
64
64
  readonly id: "reactions_add";
65
65
  readonly description: "Add an emoji reaction to a Slack message.";
66
66
  readonly sensitivity: "write";
67
67
  readonly sensitive: false;
68
68
  readonly requiredScope: "write";
69
- readonly inputSchema: import("@shipfox/api-integration-core-dto").AgentToolJsonSchema;
69
+ readonly inputSchema: import("@shipfox/api-integration-spi").AgentToolJsonSchema;
70
70
  }];
71
- selectionCatalog(): import("@shipfox/api-integration-core-dto").AgentToolSelectionCatalog;
71
+ selectionCatalog(): import("@shipfox/api-integration-spi").AgentToolSelectionCatalog;
72
72
  openSession(input: OpenAgentToolsSessionInput<SlackIntegrationConnection, SlackAgentToolRequiredScope, unknown>): Promise<AgentToolSession<SlackToolCallResult>>;
73
73
  }
74
74
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"agent-tools-provider.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,mCAAmC,CAAC;AAE3C,OAAO,KAAK,EAAC,cAAc,EAAsB,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAIL,KAAK,2BAA2B,EAGjC,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AAErD,KAAK,0BAA0B,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAEjE,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,SAAS;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;IAC1C,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAC;CACrD;AAED,qBAAa,uBACX,YACE,kBAAkB,CAChB,0BAA0B,EAC1B,2BAA2B,EAC3B,OAAO,EACP,mBAAmB,CACpB;IAES,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,8BAA8B;IAEpE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAIP,gBAAgB;IAIV,WAAW,CACf,KAAK,EAAE,0BAA0B,CAC/B,0BAA0B,EAC1B,2BAA2B,EAC3B,OAAO,CACR,GACA,OAAO,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;CAgDlD"}
1
+ {"version":3,"file":"agent-tools-provider.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAC,cAAc,EAAsB,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAIL,KAAK,2BAA2B,EAGjC,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AAErD,KAAK,0BAA0B,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAEjE,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,SAAS;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;IAC1C,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAC;CACrD;AAED,qBAAa,uBACX,YACE,kBAAkB,CAChB,0BAA0B,EAC1B,2BAA2B,EAC3B,OAAO,EACP,mBAAmB,CACpB;IAES,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,8BAA8B;IAEpE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAIP,gBAAgB;IAIV,WAAW,CACf,KAAK,EAAE,0BAA0B,CAC/B,0BAA0B,EAC1B,2BAA2B,EAC3B,OAAO,CACR,GACA,OAAO,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;CAgDlD"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/agent-tools-provider.ts"],"sourcesContent":["import type {\n AgentToolSession,\n AgentToolsProvider,\n IntegrationConnection,\n OpenAgentToolsSessionInput,\n} from '@shipfox/api-integration-core-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {SlackApiClient, SlackWebApiResponse} from '#api/client.js';\nimport {\n SLACK_TOOL_METHODS,\n type SlackAgentToolCatalogEntry,\n type SlackAgentToolId,\n type SlackAgentToolRequiredScope,\n slackAgentToolCatalog,\n slackAgentToolSelectionCatalog,\n} from '#core/agent-tools.js';\nimport {SlackIntegrationProviderError} from '#core/errors.js';\nimport type {SlackTokenStore} from '#core/tokens.js';\n\ntype SlackIntegrationConnection = IntegrationConnection<'slack'>;\n\nexport type SlackToolCallResult = {\n isError?: boolean | undefined;\n content: readonly {type: 'text'; text: string}[];\n structuredContent?: Record<string, unknown> | undefined;\n};\n\nexport interface SlackAgentToolsProviderOptions {\n slack: Pick<SlackApiClient, 'callMethod'>;\n tokenStore: Pick<SlackTokenStore, 'getAccessToken'>;\n}\n\nexport class SlackAgentToolsProvider\n implements\n AgentToolsProvider<\n SlackIntegrationConnection,\n SlackAgentToolRequiredScope,\n unknown,\n SlackToolCallResult\n >\n{\n constructor(private readonly options: SlackAgentToolsProviderOptions) {}\n\n catalog() {\n return slackAgentToolCatalog;\n }\n\n selectionCatalog() {\n return slackAgentToolSelectionCatalog;\n }\n\n async openSession(\n input: OpenAgentToolsSessionInput<\n SlackIntegrationConnection,\n SlackAgentToolRequiredScope,\n unknown\n >,\n ): Promise<AgentToolSession<SlackToolCallResult>> {\n const token = await this.options.tokenStore.getAccessToken({connectionId: input.connection.id});\n\n return {\n call: async (call) => {\n const tool = input.tools.find((candidate) => candidate.id === call.toolId);\n if (!tool) return slackToolError(`Unknown Slack tool: ${call.toolId}`);\n const method = slackToolMethod(tool.id);\n if (!method) return slackToolError(`Unknown Slack tool method: ${tool.id}`);\n const missingParameter = missingRequiredParameter(tool, call.arguments);\n if (missingParameter) {\n return slackToolError(`Missing required parameter: ${missingParameter}`);\n }\n\n let body: SlackWebApiResponse;\n try {\n body = await this.options.slack.callMethod({\n method,\n token,\n arguments: call.arguments,\n });\n } catch (error) {\n if (error instanceof SlackIntegrationProviderError) {\n return slackToolError(error.message, {\n code: error.reason,\n retryAfterSeconds: error.retryAfterSeconds,\n });\n }\n throw error;\n }\n\n if (body.ok) return slackToolResult(body);\n const slackError = typeof body.error === 'string' ? body.error : 'Slack request failed';\n if (isSlackAccessError(slackError)) {\n logger().warn(\n {connectionId: input.connection.id, slackError},\n 'Slack API rejected integration credentials',\n );\n return slackToolError(slackError, {code: 'access-denied'});\n }\n if (slackError === 'ratelimited') {\n return slackToolError(slackError, {code: 'rate-limited'});\n }\n return slackToolError(slackError);\n },\n close: () => Promise.resolve(),\n };\n }\n}\n\nfunction slackToolMethod(toolId: string): string | undefined {\n return SLACK_TOOL_METHODS[toolId as SlackAgentToolId];\n}\n\nfunction missingRequiredParameter(\n tool: SlackAgentToolCatalogEntry,\n args: Record<string, unknown>,\n): string | undefined {\n const required = tool.inputSchema.required;\n if (!Array.isArray(required)) return undefined;\n return required.find((parameter) => typeof parameter === 'string' && !(parameter in args));\n}\n\nfunction isSlackAccessError(error: string): boolean {\n return error === 'invalid_auth' || error === 'token_revoked' || error === 'account_inactive';\n}\n\nfunction slackToolResult(body: SlackWebApiResponse): SlackToolCallResult {\n return {\n content: [{type: 'text', text: JSON.stringify(body)}],\n structuredContent: body,\n };\n}\n\nfunction slackToolError(\n message: string,\n options: {code?: string | undefined; retryAfterSeconds?: number | undefined} = {},\n): SlackToolCallResult {\n const structuredContent = {\n ...(options.code === undefined ? {} : {code: options.code}),\n ...(options.retryAfterSeconds === undefined\n ? {}\n : {retryAfterSeconds: options.retryAfterSeconds}),\n };\n return {\n isError: true,\n content: [{type: 'text', text: message}],\n ...(Object.keys(structuredContent).length === 0 ? {} : {structuredContent}),\n };\n}\n"],"names":["logger","SLACK_TOOL_METHODS","slackAgentToolCatalog","slackAgentToolSelectionCatalog","SlackIntegrationProviderError","SlackAgentToolsProvider","options","catalog","selectionCatalog","openSession","input","token","tokenStore","getAccessToken","connectionId","connection","id","call","tool","tools","find","candidate","toolId","slackToolError","method","slackToolMethod","missingParameter","missingRequiredParameter","arguments","body","slack","callMethod","error","message","code","reason","retryAfterSeconds","ok","slackToolResult","slackError","isSlackAccessError","warn","close","Promise","resolve","args","required","inputSchema","Array","isArray","undefined","parameter","content","type","text","JSON","stringify","structuredContent","isError","Object","keys","length"],"mappings":"AAMA,SAAQA,MAAM,QAAO,8BAA8B;AAEnD,SACEC,kBAAkB,EAIlBC,qBAAqB,EACrBC,8BAA8B,QACzB,uBAAuB;AAC9B,SAAQC,6BAA6B,QAAO,kBAAkB;AAgB9D,OAAO,MAAMC;IASX,YAAY,AAAiBC,OAAuC,CAAE;aAAzCA,UAAAA;IAA0C;IAEvEC,UAAU;QACR,OAAOL;IACT;IAEAM,mBAAmB;QACjB,OAAOL;IACT;IAEA,MAAMM,YACJC,KAIC,EAC+C;QAChD,MAAMC,QAAQ,MAAM,IAAI,CAACL,OAAO,CAACM,UAAU,CAACC,cAAc,CAAC;YAACC,cAAcJ,MAAMK,UAAU,CAACC,EAAE;QAAA;QAE7F,OAAO;YACLC,MAAM,OAAOA;gBACX,MAAMC,OAAOR,MAAMS,KAAK,CAACC,IAAI,CAAC,CAACC,YAAcA,UAAUL,EAAE,KAAKC,KAAKK,MAAM;gBACzE,IAAI,CAACJ,MAAM,OAAOK,eAAe,CAAC,oBAAoB,EAAEN,KAAKK,MAAM,EAAE;gBACrE,MAAME,SAASC,gBAAgBP,KAAKF,EAAE;gBACtC,IAAI,CAACQ,QAAQ,OAAOD,eAAe,CAAC,2BAA2B,EAAEL,KAAKF,EAAE,EAAE;gBAC1E,MAAMU,mBAAmBC,yBAAyBT,MAAMD,KAAKW,SAAS;gBACtE,IAAIF,kBAAkB;oBACpB,OAAOH,eAAe,CAAC,4BAA4B,EAAEG,kBAAkB;gBACzE;gBAEA,IAAIG;gBACJ,IAAI;oBACFA,OAAO,MAAM,IAAI,CAACvB,OAAO,CAACwB,KAAK,CAACC,UAAU,CAAC;wBACzCP;wBACAb;wBACAiB,WAAWX,KAAKW,SAAS;oBAC3B;gBACF,EAAE,OAAOI,OAAO;oBACd,IAAIA,iBAAiB5B,+BAA+B;wBAClD,OAAOmB,eAAeS,MAAMC,OAAO,EAAE;4BACnCC,MAAMF,MAAMG,MAAM;4BAClBC,mBAAmBJ,MAAMI,iBAAiB;wBAC5C;oBACF;oBACA,MAAMJ;gBACR;gBAEA,IAAIH,KAAKQ,EAAE,EAAE,OAAOC,gBAAgBT;gBACpC,MAAMU,aAAa,OAAOV,KAAKG,KAAK,KAAK,WAAWH,KAAKG,KAAK,GAAG;gBACjE,IAAIQ,mBAAmBD,aAAa;oBAClCvC,SAASyC,IAAI,CACX;wBAAC3B,cAAcJ,MAAMK,UAAU,CAACC,EAAE;wBAAEuB;oBAAU,GAC9C;oBAEF,OAAOhB,eAAegB,YAAY;wBAACL,MAAM;oBAAe;gBAC1D;gBACA,IAAIK,eAAe,eAAe;oBAChC,OAAOhB,eAAegB,YAAY;wBAACL,MAAM;oBAAc;gBACzD;gBACA,OAAOX,eAAegB;YACxB;YACAG,OAAO,IAAMC,QAAQC,OAAO;QAC9B;IACF;AACF;AAEA,SAASnB,gBAAgBH,MAAc;IACrC,OAAOrB,kBAAkB,CAACqB,OAA2B;AACvD;AAEA,SAASK,yBACPT,IAAgC,EAChC2B,IAA6B;IAE7B,MAAMC,WAAW5B,KAAK6B,WAAW,CAACD,QAAQ;IAC1C,IAAI,CAACE,MAAMC,OAAO,CAACH,WAAW,OAAOI;IACrC,OAAOJ,SAAS1B,IAAI,CAAC,CAAC+B,YAAc,OAAOA,cAAc,YAAY,CAAEA,CAAAA,aAAaN,IAAG;AACzF;AAEA,SAASL,mBAAmBR,KAAa;IACvC,OAAOA,UAAU,kBAAkBA,UAAU,mBAAmBA,UAAU;AAC5E;AAEA,SAASM,gBAAgBT,IAAyB;IAChD,OAAO;QACLuB,SAAS;YAAC;gBAACC,MAAM;gBAAQC,MAAMC,KAAKC,SAAS,CAAC3B;YAAK;SAAE;QACrD4B,mBAAmB5B;IACrB;AACF;AAEA,SAASN,eACPU,OAAe,EACf3B,UAA+E,CAAC,CAAC;IAEjF,MAAMmD,oBAAoB;QACxB,GAAInD,QAAQ4B,IAAI,KAAKgB,YAAY,CAAC,IAAI;YAAChB,MAAM5B,QAAQ4B,IAAI;QAAA,CAAC;QAC1D,GAAI5B,QAAQ8B,iBAAiB,KAAKc,YAC9B,CAAC,IACD;YAACd,mBAAmB9B,QAAQ8B,iBAAiB;QAAA,CAAC;IACpD;IACA,OAAO;QACLsB,SAAS;QACTN,SAAS;YAAC;gBAACC,MAAM;gBAAQC,MAAMrB;YAAO;SAAE;QACxC,GAAI0B,OAAOC,IAAI,CAACH,mBAAmBI,MAAM,KAAK,IAAI,CAAC,IAAI;YAACJ;QAAiB,CAAC;IAC5E;AACF"}
1
+ {"version":3,"sources":["../../src/core/agent-tools-provider.ts"],"sourcesContent":["import type {\n AgentToolSession,\n AgentToolsProvider,\n IntegrationConnection,\n OpenAgentToolsSessionInput,\n} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {SlackApiClient, SlackWebApiResponse} from '#api/client.js';\nimport {\n SLACK_TOOL_METHODS,\n type SlackAgentToolCatalogEntry,\n type SlackAgentToolId,\n type SlackAgentToolRequiredScope,\n slackAgentToolCatalog,\n slackAgentToolSelectionCatalog,\n} from '#core/agent-tools.js';\nimport {SlackIntegrationProviderError} from '#core/errors.js';\nimport type {SlackTokenStore} from '#core/tokens.js';\n\ntype SlackIntegrationConnection = IntegrationConnection<'slack'>;\n\nexport type SlackToolCallResult = {\n isError?: boolean | undefined;\n content: readonly {type: 'text'; text: string}[];\n structuredContent?: Record<string, unknown> | undefined;\n};\n\nexport interface SlackAgentToolsProviderOptions {\n slack: Pick<SlackApiClient, 'callMethod'>;\n tokenStore: Pick<SlackTokenStore, 'getAccessToken'>;\n}\n\nexport class SlackAgentToolsProvider\n implements\n AgentToolsProvider<\n SlackIntegrationConnection,\n SlackAgentToolRequiredScope,\n unknown,\n SlackToolCallResult\n >\n{\n constructor(private readonly options: SlackAgentToolsProviderOptions) {}\n\n catalog() {\n return slackAgentToolCatalog;\n }\n\n selectionCatalog() {\n return slackAgentToolSelectionCatalog;\n }\n\n async openSession(\n input: OpenAgentToolsSessionInput<\n SlackIntegrationConnection,\n SlackAgentToolRequiredScope,\n unknown\n >,\n ): Promise<AgentToolSession<SlackToolCallResult>> {\n const token = await this.options.tokenStore.getAccessToken({connectionId: input.connection.id});\n\n return {\n call: async (call) => {\n const tool = input.tools.find((candidate) => candidate.id === call.toolId);\n if (!tool) return slackToolError(`Unknown Slack tool: ${call.toolId}`);\n const method = slackToolMethod(tool.id);\n if (!method) return slackToolError(`Unknown Slack tool method: ${tool.id}`);\n const missingParameter = missingRequiredParameter(tool, call.arguments);\n if (missingParameter) {\n return slackToolError(`Missing required parameter: ${missingParameter}`);\n }\n\n let body: SlackWebApiResponse;\n try {\n body = await this.options.slack.callMethod({\n method,\n token,\n arguments: call.arguments,\n });\n } catch (error) {\n if (error instanceof SlackIntegrationProviderError) {\n return slackToolError(error.message, {\n code: error.reason,\n retryAfterSeconds: error.retryAfterSeconds,\n });\n }\n throw error;\n }\n\n if (body.ok) return slackToolResult(body);\n const slackError = typeof body.error === 'string' ? body.error : 'Slack request failed';\n if (isSlackAccessError(slackError)) {\n logger().warn(\n {connectionId: input.connection.id, slackError},\n 'Slack API rejected integration credentials',\n );\n return slackToolError(slackError, {code: 'access-denied'});\n }\n if (slackError === 'ratelimited') {\n return slackToolError(slackError, {code: 'rate-limited'});\n }\n return slackToolError(slackError);\n },\n close: () => Promise.resolve(),\n };\n }\n}\n\nfunction slackToolMethod(toolId: string): string | undefined {\n return SLACK_TOOL_METHODS[toolId as SlackAgentToolId];\n}\n\nfunction missingRequiredParameter(\n tool: SlackAgentToolCatalogEntry,\n args: Record<string, unknown>,\n): string | undefined {\n const required = tool.inputSchema.required;\n if (!Array.isArray(required)) return undefined;\n return required.find((parameter) => typeof parameter === 'string' && !(parameter in args));\n}\n\nfunction isSlackAccessError(error: string): boolean {\n return error === 'invalid_auth' || error === 'token_revoked' || error === 'account_inactive';\n}\n\nfunction slackToolResult(body: SlackWebApiResponse): SlackToolCallResult {\n return {\n content: [{type: 'text', text: JSON.stringify(body)}],\n structuredContent: body,\n };\n}\n\nfunction slackToolError(\n message: string,\n options: {code?: string | undefined; retryAfterSeconds?: number | undefined} = {},\n): SlackToolCallResult {\n const structuredContent = {\n ...(options.code === undefined ? {} : {code: options.code}),\n ...(options.retryAfterSeconds === undefined\n ? {}\n : {retryAfterSeconds: options.retryAfterSeconds}),\n };\n return {\n isError: true,\n content: [{type: 'text', text: message}],\n ...(Object.keys(structuredContent).length === 0 ? {} : {structuredContent}),\n };\n}\n"],"names":["logger","SLACK_TOOL_METHODS","slackAgentToolCatalog","slackAgentToolSelectionCatalog","SlackIntegrationProviderError","SlackAgentToolsProvider","options","catalog","selectionCatalog","openSession","input","token","tokenStore","getAccessToken","connectionId","connection","id","call","tool","tools","find","candidate","toolId","slackToolError","method","slackToolMethod","missingParameter","missingRequiredParameter","arguments","body","slack","callMethod","error","message","code","reason","retryAfterSeconds","ok","slackToolResult","slackError","isSlackAccessError","warn","close","Promise","resolve","args","required","inputSchema","Array","isArray","undefined","parameter","content","type","text","JSON","stringify","structuredContent","isError","Object","keys","length"],"mappings":"AAMA,SAAQA,MAAM,QAAO,8BAA8B;AAEnD,SACEC,kBAAkB,EAIlBC,qBAAqB,EACrBC,8BAA8B,QACzB,uBAAuB;AAC9B,SAAQC,6BAA6B,QAAO,kBAAkB;AAgB9D,OAAO,MAAMC;IASX,YAAY,AAAiBC,OAAuC,CAAE;aAAzCA,UAAAA;IAA0C;IAEvEC,UAAU;QACR,OAAOL;IACT;IAEAM,mBAAmB;QACjB,OAAOL;IACT;IAEA,MAAMM,YACJC,KAIC,EAC+C;QAChD,MAAMC,QAAQ,MAAM,IAAI,CAACL,OAAO,CAACM,UAAU,CAACC,cAAc,CAAC;YAACC,cAAcJ,MAAMK,UAAU,CAACC,EAAE;QAAA;QAE7F,OAAO;YACLC,MAAM,OAAOA;gBACX,MAAMC,OAAOR,MAAMS,KAAK,CAACC,IAAI,CAAC,CAACC,YAAcA,UAAUL,EAAE,KAAKC,KAAKK,MAAM;gBACzE,IAAI,CAACJ,MAAM,OAAOK,eAAe,CAAC,oBAAoB,EAAEN,KAAKK,MAAM,EAAE;gBACrE,MAAME,SAASC,gBAAgBP,KAAKF,EAAE;gBACtC,IAAI,CAACQ,QAAQ,OAAOD,eAAe,CAAC,2BAA2B,EAAEL,KAAKF,EAAE,EAAE;gBAC1E,MAAMU,mBAAmBC,yBAAyBT,MAAMD,KAAKW,SAAS;gBACtE,IAAIF,kBAAkB;oBACpB,OAAOH,eAAe,CAAC,4BAA4B,EAAEG,kBAAkB;gBACzE;gBAEA,IAAIG;gBACJ,IAAI;oBACFA,OAAO,MAAM,IAAI,CAACvB,OAAO,CAACwB,KAAK,CAACC,UAAU,CAAC;wBACzCP;wBACAb;wBACAiB,WAAWX,KAAKW,SAAS;oBAC3B;gBACF,EAAE,OAAOI,OAAO;oBACd,IAAIA,iBAAiB5B,+BAA+B;wBAClD,OAAOmB,eAAeS,MAAMC,OAAO,EAAE;4BACnCC,MAAMF,MAAMG,MAAM;4BAClBC,mBAAmBJ,MAAMI,iBAAiB;wBAC5C;oBACF;oBACA,MAAMJ;gBACR;gBAEA,IAAIH,KAAKQ,EAAE,EAAE,OAAOC,gBAAgBT;gBACpC,MAAMU,aAAa,OAAOV,KAAKG,KAAK,KAAK,WAAWH,KAAKG,KAAK,GAAG;gBACjE,IAAIQ,mBAAmBD,aAAa;oBAClCvC,SAASyC,IAAI,CACX;wBAAC3B,cAAcJ,MAAMK,UAAU,CAACC,EAAE;wBAAEuB;oBAAU,GAC9C;oBAEF,OAAOhB,eAAegB,YAAY;wBAACL,MAAM;oBAAe;gBAC1D;gBACA,IAAIK,eAAe,eAAe;oBAChC,OAAOhB,eAAegB,YAAY;wBAACL,MAAM;oBAAc;gBACzD;gBACA,OAAOX,eAAegB;YACxB;YACAG,OAAO,IAAMC,QAAQC,OAAO;QAC9B;IACF;AACF;AAEA,SAASnB,gBAAgBH,MAAc;IACrC,OAAOrB,kBAAkB,CAACqB,OAA2B;AACvD;AAEA,SAASK,yBACPT,IAAgC,EAChC2B,IAA6B;IAE7B,MAAMC,WAAW5B,KAAK6B,WAAW,CAACD,QAAQ;IAC1C,IAAI,CAACE,MAAMC,OAAO,CAACH,WAAW,OAAOI;IACrC,OAAOJ,SAAS1B,IAAI,CAAC,CAAC+B,YAAc,OAAOA,cAAc,YAAY,CAAEA,CAAAA,aAAaN,IAAG;AACzF;AAEA,SAASL,mBAAmBR,KAAa;IACvC,OAAOA,UAAU,kBAAkBA,UAAU,mBAAmBA,UAAU;AAC5E;AAEA,SAASM,gBAAgBT,IAAyB;IAChD,OAAO;QACLuB,SAAS;YAAC;gBAACC,MAAM;gBAAQC,MAAMC,KAAKC,SAAS,CAAC3B;YAAK;SAAE;QACrD4B,mBAAmB5B;IACrB;AACF;AAEA,SAASN,eACPU,OAAe,EACf3B,UAA+E,CAAC,CAAC;IAEjF,MAAMmD,oBAAoB;QACxB,GAAInD,QAAQ4B,IAAI,KAAKgB,YAAY,CAAC,IAAI;YAAChB,MAAM5B,QAAQ4B,IAAI;QAAA,CAAC;QAC1D,GAAI5B,QAAQ8B,iBAAiB,KAAKc,YAC9B,CAAC,IACD;YAACd,mBAAmB9B,QAAQ8B,iBAAiB;QAAA,CAAC;IACpD;IACA,OAAO;QACLsB,SAAS;QACTN,SAAS;YAAC;gBAACC,MAAM;gBAAQC,MAAMrB;YAAO;SAAE;QACxC,GAAI0B,OAAOC,IAAI,CAACH,mBAAmBI,MAAM,KAAK,IAAI,CAAC,IAAI;YAACJ;QAAiB,CAAC;IAC5E;AACF"}
@@ -1,4 +1,4 @@
1
- import type { AgentToolCatalogEntry, AgentToolJsonSchema, AgentToolSelectionCatalog } from '@shipfox/api-integration-core-dto';
1
+ import type { AgentToolCatalogEntry, AgentToolJsonSchema, AgentToolSelectionCatalog } from '@shipfox/api-integration-spi';
2
2
  export type SlackAgentToolRequiredScope = 'read' | 'write';
3
3
  export type SlackAgentToolCatalogEntry = AgentToolCatalogEntry<SlackAgentToolRequiredScope>;
4
4
  export declare const slackAgentToolCatalog: readonly [{
@@ -1 +1 @@
1
- {"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,yBAAyB,EAE1B,MAAM,mCAAmC,CAAC;AAE3C,MAAM,MAAM,2BAA2B,GAAG,MAAM,GAAG,OAAO,CAAC;AAC3D,MAAM,MAAM,0BAA0B,GAAG,qBAAqB,CAAC,2BAA2B,CAAC,CAAC;AAe5F,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuGwB,CAAC;AAE3D,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAE5E,eAAO,MAAM,kBAAkB;;;;;;;;CAQsB,CAAC;AAEtD,eAAO,MAAM,8BAA8B,2BACiB,CAAC"}
1
+ {"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,yBAAyB,EAE1B,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,2BAA2B,GAAG,MAAM,GAAG,OAAO,CAAC;AAC3D,MAAM,MAAM,0BAA0B,GAAG,qBAAqB,CAAC,2BAA2B,CAAC,CAAC;AAe5F,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuGwB,CAAC;AAE3D,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAE5E,eAAO,MAAM,kBAAkB;;;;;;;;CAQsB,CAAC;AAEtD,eAAO,MAAM,8BAA8B,2BACiB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/agent-tools.ts"],"sourcesContent":["import type {\n AgentToolCatalogEntry,\n AgentToolJsonSchema,\n AgentToolSelectionCatalog,\n AgentToolSelector,\n} from '@shipfox/api-integration-core-dto';\n\nexport type SlackAgentToolRequiredScope = 'read' | 'write';\nexport type SlackAgentToolCatalogEntry = AgentToolCatalogEntry<SlackAgentToolRequiredScope>;\n\ninterface SlackAgentToolCatalogInput {\n id: string;\n description: string;\n sensitivity: 'read' | 'write';\n sensitive: boolean;\n requiredScope: SlackAgentToolRequiredScope;\n inputSchema: AgentToolJsonSchema;\n}\n\nconst channelSchema = stringSchema('Slack channel ID');\nconst cursorSchema = stringSchema('Next page cursor');\nconst limitSchema = numberSchema('Maximum number of results to return');\n\nexport const slackAgentToolCatalog = [\n tool({\n id: 'conversations_history',\n description: 'List messages in a Slack channel or direct message.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n cursor: cursorSchema,\n limit: limitSchema,\n oldest: stringSchema('Only messages after this Slack timestamp'),\n latest: stringSchema('Only messages before this Slack timestamp'),\n inclusive: booleanSchema('Include messages at the oldest or latest timestamp'),\n },\n ['channel'],\n ),\n }),\n tool({\n id: 'conversations_replies',\n description: 'List replies in a Slack message thread.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n ts: stringSchema('Slack timestamp of the parent message'),\n cursor: cursorSchema,\n limit: limitSchema,\n },\n ['channel', 'ts'],\n ),\n }),\n tool({\n id: 'conversations_list',\n description: 'List Slack channels visible to the bot.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema({\n cursor: cursorSchema,\n limit: limitSchema,\n types: stringSchema('Comma-separated channel types, such as public_channel,private_channel'),\n exclude_archived: booleanSchema('Exclude archived channels'),\n }),\n }),\n tool({\n id: 'users_info',\n description: 'Look up a Slack user and profile by user ID.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema({user: stringSchema('Slack user ID')}, ['user']),\n }),\n tool({\n id: 'chat_postMessage',\n description: 'Post a Slack message to a channel or thread. Provide text, blocks, or both.',\n sensitivity: 'write',\n sensitive: false,\n requiredScope: 'write',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n text: stringSchema('Message text'),\n thread_ts: stringSchema('Slack timestamp of the parent message when replying in a thread'),\n blocks: arraySchema({type: 'object'}),\n },\n ['channel'],\n ),\n }),\n tool({\n id: 'chat_update',\n description: 'Update a Slack message with text, blocks, or both.',\n sensitivity: 'write',\n sensitive: false,\n requiredScope: 'write',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n ts: stringSchema('Slack timestamp of the message to update'),\n text: stringSchema('Updated message text'),\n blocks: arraySchema({type: 'object'}),\n },\n ['channel', 'ts'],\n ),\n }),\n tool({\n id: 'reactions_add',\n description: 'Add an emoji reaction to a Slack message.',\n sensitivity: 'write',\n sensitive: false,\n requiredScope: 'write',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n timestamp: stringSchema('Slack timestamp of the message'),\n name: stringSchema('Emoji name without surrounding colons'),\n },\n ['channel', 'timestamp', 'name'],\n ),\n }),\n] as const satisfies readonly SlackAgentToolCatalogEntry[];\n\nexport type SlackAgentToolId = (typeof slackAgentToolCatalog)[number]['id'];\n\nexport const SLACK_TOOL_METHODS = {\n conversations_history: 'conversations.history',\n conversations_replies: 'conversations.replies',\n conversations_list: 'conversations.list',\n users_info: 'users.info',\n chat_postMessage: 'chat.postMessage',\n chat_update: 'chat.update',\n reactions_add: 'reactions.add',\n} as const satisfies Record<SlackAgentToolId, string>;\n\nexport const slackAgentToolSelectionCatalog =\n buildSlackAgentToolSelectionCatalog(slackAgentToolCatalog);\n\nfunction buildSlackAgentToolSelectionCatalog(\n catalog: readonly SlackAgentToolCatalogEntry[],\n): AgentToolSelectionCatalog {\n return {\n selectors: catalog.map(\n (entry): AgentToolSelector => ({\n token: entry.id,\n kind: 'standalone',\n sensitivity: entry.sensitivity,\n sensitive: entry.sensitive,\n }),\n ),\n };\n}\n\nfunction tool<const Entry extends SlackAgentToolCatalogInput>(input: Entry): Entry {\n return input;\n}\n\nfunction objectSchema(\n properties: Record<string, AgentToolJsonSchema>,\n required: string[] = [],\n): AgentToolJsonSchema {\n return {\n type: 'object',\n additionalProperties: false,\n properties,\n ...(required.length > 0 ? {required} : {}),\n };\n}\n\nfunction stringSchema(description?: string): AgentToolJsonSchema {\n return {type: 'string', ...(description ? {description} : {})};\n}\n\nfunction numberSchema(description?: string): AgentToolJsonSchema {\n return {type: 'number', ...(description ? {description} : {})};\n}\n\nfunction booleanSchema(description: string): AgentToolJsonSchema {\n return {type: 'boolean', description};\n}\n\nfunction arraySchema(items: AgentToolJsonSchema): AgentToolJsonSchema {\n return {type: 'array', items};\n}\n"],"names":["channelSchema","stringSchema","cursorSchema","limitSchema","numberSchema","slackAgentToolCatalog","tool","id","description","sensitivity","sensitive","requiredScope","inputSchema","objectSchema","channel","cursor","limit","oldest","latest","inclusive","booleanSchema","ts","types","exclude_archived","user","text","thread_ts","blocks","arraySchema","type","timestamp","name","SLACK_TOOL_METHODS","conversations_history","conversations_replies","conversations_list","users_info","chat_postMessage","chat_update","reactions_add","slackAgentToolSelectionCatalog","buildSlackAgentToolSelectionCatalog","catalog","selectors","map","entry","token","kind","input","properties","required","additionalProperties","length","items"],"mappings":"AAmBA,MAAMA,gBAAgBC,aAAa;AACnC,MAAMC,eAAeD,aAAa;AAClC,MAAME,cAAcC,aAAa;AAEjC,OAAO,MAAMC,wBAAwB;IACnCC,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACTe,QAAQb;YACRc,OAAOb;YACPc,QAAQhB,aAAa;YACrBiB,QAAQjB,aAAa;YACrBkB,WAAWC,cAAc;QAC3B,GACA;YAAC;SAAU;IAEf;IACAd,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACTqB,IAAIpB,aAAa;YACjBc,QAAQb;YACRc,OAAOb;QACT,GACA;YAAC;YAAW;SAAK;IAErB;IACAG,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aAAa;YACxBE,QAAQb;YACRc,OAAOb;YACPmB,OAAOrB,aAAa;YACpBsB,kBAAkBH,cAAc;QAClC;IACF;IACAd,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aAAa;YAACW,MAAMvB,aAAa;QAAgB,GAAG;YAAC;SAAO;IAC3E;IACAK,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACTyB,MAAMxB,aAAa;YACnByB,WAAWzB,aAAa;YACxB0B,QAAQC,YAAY;gBAACC,MAAM;YAAQ;QACrC,GACA;YAAC;SAAU;IAEf;IACAvB,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACTqB,IAAIpB,aAAa;YACjBwB,MAAMxB,aAAa;YACnB0B,QAAQC,YAAY;gBAACC,MAAM;YAAQ;QACrC,GACA;YAAC;YAAW;SAAK;IAErB;IACAvB,KAAK;QACHC,IAAI;QACJC,aAAa;QACbC,aAAa;QACbC,WAAW;QACXC,eAAe;QACfC,aAAaC,aACX;YACEC,SAASd;YACT8B,WAAW7B,aAAa;YACxB8B,MAAM9B,aAAa;QACrB,GACA;YAAC;YAAW;YAAa;SAAO;IAEpC;CACD,CAA0D;AAI3D,OAAO,MAAM+B,qBAAqB;IAChCC,uBAAuB;IACvBC,uBAAuB;IACvBC,oBAAoB;IACpBC,YAAY;IACZC,kBAAkB;IAClBC,aAAa;IACbC,eAAe;AACjB,EAAsD;AAEtD,OAAO,MAAMC,iCACXC,oCAAoCpC,uBAAuB;AAE7D,SAASoC,oCACPC,OAA8C;IAE9C,OAAO;QACLC,WAAWD,QAAQE,GAAG,CACpB,CAACC,QAA8B,CAAA;gBAC7BC,OAAOD,MAAMtC,EAAE;gBACfwC,MAAM;gBACNtC,aAAaoC,MAAMpC,WAAW;gBAC9BC,WAAWmC,MAAMnC,SAAS;YAC5B,CAAA;IAEJ;AACF;AAEA,SAASJ,KAAqD0C,KAAY;IACxE,OAAOA;AACT;AAEA,SAASnC,aACPoC,UAA+C,EAC/CC,WAAqB,EAAE;IAEvB,OAAO;QACLrB,MAAM;QACNsB,sBAAsB;QACtBF;QACA,GAAIC,SAASE,MAAM,GAAG,IAAI;YAACF;QAAQ,IAAI,CAAC,CAAC;IAC3C;AACF;AAEA,SAASjD,aAAaO,WAAoB;IACxC,OAAO;QAACqB,MAAM;QAAU,GAAIrB,cAAc;YAACA;QAAW,IAAI,CAAC,CAAC;IAAC;AAC/D;AAEA,SAASJ,aAAaI,WAAoB;IACxC,OAAO;QAACqB,MAAM;QAAU,GAAIrB,cAAc;YAACA;QAAW,IAAI,CAAC,CAAC;IAAC;AAC/D;AAEA,SAASY,cAAcZ,WAAmB;IACxC,OAAO;QAACqB,MAAM;QAAWrB;IAAW;AACtC;AAEA,SAASoB,YAAYyB,KAA0B;IAC7C,OAAO;QAACxB,MAAM;QAASwB;IAAK;AAC9B"}
1
+ {"version":3,"sources":["../../src/core/agent-tools.ts"],"sourcesContent":["import type {\n AgentToolCatalogEntry,\n AgentToolJsonSchema,\n AgentToolSelectionCatalog,\n AgentToolSelector,\n} from '@shipfox/api-integration-spi';\n\nexport type SlackAgentToolRequiredScope = 'read' | 'write';\nexport type SlackAgentToolCatalogEntry = AgentToolCatalogEntry<SlackAgentToolRequiredScope>;\n\ninterface SlackAgentToolCatalogInput {\n id: string;\n description: string;\n sensitivity: 'read' | 'write';\n sensitive: boolean;\n requiredScope: SlackAgentToolRequiredScope;\n inputSchema: AgentToolJsonSchema;\n}\n\nconst channelSchema = stringSchema('Slack channel ID');\nconst cursorSchema = stringSchema('Next page cursor');\nconst limitSchema = numberSchema('Maximum number of results to return');\n\nexport const slackAgentToolCatalog = [\n tool({\n id: 'conversations_history',\n description: 'List messages in a Slack channel or direct message.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n cursor: cursorSchema,\n limit: limitSchema,\n oldest: stringSchema('Only messages after this Slack timestamp'),\n latest: stringSchema('Only messages before this Slack timestamp'),\n inclusive: booleanSchema('Include messages at the oldest or latest timestamp'),\n },\n ['channel'],\n ),\n }),\n tool({\n id: 'conversations_replies',\n description: 'List replies in a Slack message thread.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n ts: stringSchema('Slack timestamp of the parent message'),\n cursor: cursorSchema,\n limit: limitSchema,\n },\n ['channel', 'ts'],\n ),\n }),\n tool({\n id: 'conversations_list',\n description: 'List Slack channels visible to the bot.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema({\n cursor: cursorSchema,\n limit: limitSchema,\n types: stringSchema('Comma-separated channel types, such as public_channel,private_channel'),\n exclude_archived: booleanSchema('Exclude archived channels'),\n }),\n }),\n tool({\n id: 'users_info',\n description: 'Look up a Slack user and profile by user ID.',\n sensitivity: 'read',\n sensitive: false,\n requiredScope: 'read',\n inputSchema: objectSchema({user: stringSchema('Slack user ID')}, ['user']),\n }),\n tool({\n id: 'chat_postMessage',\n description: 'Post a Slack message to a channel or thread. Provide text, blocks, or both.',\n sensitivity: 'write',\n sensitive: false,\n requiredScope: 'write',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n text: stringSchema('Message text'),\n thread_ts: stringSchema('Slack timestamp of the parent message when replying in a thread'),\n blocks: arraySchema({type: 'object'}),\n },\n ['channel'],\n ),\n }),\n tool({\n id: 'chat_update',\n description: 'Update a Slack message with text, blocks, or both.',\n sensitivity: 'write',\n sensitive: false,\n requiredScope: 'write',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n ts: stringSchema('Slack timestamp of the message to update'),\n text: stringSchema('Updated message text'),\n blocks: arraySchema({type: 'object'}),\n },\n ['channel', 'ts'],\n ),\n }),\n tool({\n id: 'reactions_add',\n description: 'Add an emoji reaction to a Slack message.',\n sensitivity: 'write',\n sensitive: false,\n requiredScope: 'write',\n inputSchema: objectSchema(\n {\n channel: channelSchema,\n timestamp: stringSchema('Slack timestamp of the message'),\n name: stringSchema('Emoji name without surrounding colons'),\n },\n ['channel', 'timestamp', 'name'],\n ),\n }),\n] as const satisfies readonly SlackAgentToolCatalogEntry[];\n\nexport type SlackAgentToolId = (typeof slackAgentToolCatalog)[number]['id'];\n\nexport const SLACK_TOOL_METHODS = {\n conversations_history: 'conversations.history',\n conversations_replies: 'conversations.replies',\n conversations_list: 'conversations.list',\n users_info: 'users.info',\n chat_postMessage: 'chat.postMessage',\n chat_update: 'chat.update',\n reactions_add: 'reactions.add',\n} as const satisfies Record<SlackAgentToolId, string>;\n\nexport const slackAgentToolSelectionCatalog =\n buildSlackAgentToolSelectionCatalog(slackAgentToolCatalog);\n\nfunction buildSlackAgentToolSelectionCatalog(\n catalog: readonly SlackAgentToolCatalogEntry[],\n): AgentToolSelectionCatalog {\n return {\n selectors: catalog.map(\n (entry): AgentToolSelector => ({\n token: entry.id,\n kind: 'standalone',\n sensitivity: entry.sensitivity,\n sensitive: entry.sensitive,\n }),\n ),\n };\n}\n\nfunction tool<const Entry extends SlackAgentToolCatalogInput>(input: Entry): Entry {\n return input;\n}\n\nfunction objectSchema(\n properties: Record<string, AgentToolJsonSchema>,\n required: string[] = [],\n): AgentToolJsonSchema {\n return {\n type: 'object',\n additionalProperties: false,\n properties,\n ...(required.length > 0 ? {required} : {}),\n };\n}\n\nfunction stringSchema(description?: string): AgentToolJsonSchema {\n return {type: 'string', ...(description ? {description} : {})};\n}\n\nfunction numberSchema(description?: string): AgentToolJsonSchema {\n return {type: 'number', ...(description ? {description} : {})};\n}\n\nfunction booleanSchema(description: string): AgentToolJsonSchema {\n return {type: 'boolean', description};\n}\n\nfunction arraySchema(items: AgentToolJsonSchema): AgentToolJsonSchema {\n return {type: 'array', items};\n}\n"],"names":["channelSchema","stringSchema","cursorSchema","limitSchema","numberSchema","slackAgentToolCatalog","tool","id","description","sensitivity","sensitive","requiredScope","inputSchema","objectSchema","channel","cursor","limit","oldest","latest","inclusive","booleanSchema","ts","types","exclude_archived","user","text","thread_ts","blocks","arraySchema","type","timestamp","name","SLACK_TOOL_METHODS","conversations_history","conversations_replies","conversations_list","users_info","chat_postMessage","chat_update","reactions_add","slackAgentToolSelectionCatalog","buildSlackAgentToolSelectionCatalog","catalog","selectors","map","entry","token","kind","input","properties","required","additionalProperties","length","items"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { IntegrationProviderError } from '@shipfox/api-integration-core-dto';
1
+ import { IntegrationProviderError } from '@shipfox/api-integration-spi';
2
2
  export declare class SlackIntegrationProviderError extends IntegrationProviderError {
3
3
  }
4
4
  export declare class SlackInstallStateError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,mCAAmC,CAAC;AAE3E,qBAAa,6BAA8B,SAAQ,wBAAwB;CAAG;AAE9E,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,SAAgC;CAIpD;AAED,qBAAa,mCAAoC,SAAQ,KAAK;;CAK7D;AAED,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,aAAa,EAAE,MAAM;aACrB,mBAAmB,EAAE,MAAM,GAAG,SAAS;gBADvC,aAAa,EAAE,MAAM,EACrB,mBAAmB,EAAE,MAAM,GAAG,SAAS;CAK1D;AAED,qBAAa,oCAAqC,SAAQ,KAAK;aACjC,aAAa,EAAE,MAAM,EAAE;gBAAvB,aAAa,EAAE,MAAM,EAAE;CAIpD;AAED,qBAAa,sCAAuC,SAAQ,KAAK;;CAKhE;AAED,qBAAa,kCAAmC,SAAQ,KAAK;;CAK5D;AAED,qBAAa,mCAAoC,SAAQ,KAAK;gBAChD,MAAM,EAAE,MAAM;CAI3B;AAED,qBAAa,iCAAkC,SAAQ,KAAK;gBAC9C,YAAY,EAAE,MAAM;CAIjC;AAED,qBAAa,4BAA6B,SAAQ,KAAK;gBACzC,YAAY,EAAE,MAAM;CAIjC;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,YAAY,EAAE,MAAM;CAIjC;AAED,MAAM,MAAM,iCAAiC,GACzC,wBAAwB,GACxB,eAAe,GACf,SAAS,CAAC;AAEd,qBAAa,gCAAiC,SAAQ,KAAK;aAEvC,YAAY,EAAE,MAAM;aACpB,MAAM,EAAE,iCAAiC;aACzC,SAAS,CAAC,EAAE,IAAI;gBAFhB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,iCAAiC,EACzC,SAAS,CAAC,EAAE,IAAI,YAAA;CAKnC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,8BAA8B,CAAC;AAEtE,qBAAa,6BAA8B,SAAQ,wBAAwB;CAAG;AAE9E,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,SAAgC;CAIpD;AAED,qBAAa,mCAAoC,SAAQ,KAAK;;CAK7D;AAED,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,aAAa,EAAE,MAAM;aACrB,mBAAmB,EAAE,MAAM,GAAG,SAAS;gBADvC,aAAa,EAAE,MAAM,EACrB,mBAAmB,EAAE,MAAM,GAAG,SAAS;CAK1D;AAED,qBAAa,oCAAqC,SAAQ,KAAK;aACjC,aAAa,EAAE,MAAM,EAAE;gBAAvB,aAAa,EAAE,MAAM,EAAE;CAIpD;AAED,qBAAa,sCAAuC,SAAQ,KAAK;;CAKhE;AAED,qBAAa,kCAAmC,SAAQ,KAAK;;CAK5D;AAED,qBAAa,mCAAoC,SAAQ,KAAK;gBAChD,MAAM,EAAE,MAAM;CAI3B;AAED,qBAAa,iCAAkC,SAAQ,KAAK;gBAC9C,YAAY,EAAE,MAAM;CAIjC;AAED,qBAAa,4BAA6B,SAAQ,KAAK;gBACzC,YAAY,EAAE,MAAM;CAIjC;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,YAAY,EAAE,MAAM;CAIjC;AAED,MAAM,MAAM,iCAAiC,GACzC,wBAAwB,GACxB,eAAe,GACf,SAAS,CAAC;AAEd,qBAAa,gCAAiC,SAAQ,KAAK;aAEvC,YAAY,EAAE,MAAM;aACpB,MAAM,EAAE,iCAAiC;aACzC,SAAS,CAAC,EAAE,IAAI;gBAFhB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,iCAAiC,EACzC,SAAS,CAAC,EAAE,IAAI,YAAA;CAKnC"}
@@ -1,4 +1,4 @@
1
- import { IntegrationProviderError } from '@shipfox/api-integration-core-dto';
1
+ import { IntegrationProviderError } from '@shipfox/api-integration-spi';
2
2
  export class SlackIntegrationProviderError extends IntegrationProviderError {
3
3
  }
4
4
  export class SlackInstallStateError extends Error {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/errors.ts"],"sourcesContent":["import {IntegrationProviderError} from '@shipfox/api-integration-core-dto';\n\nexport class SlackIntegrationProviderError extends IntegrationProviderError {}\n\nexport class SlackInstallStateError extends Error {\n constructor(message = 'Invalid Slack install state') {\n super(message);\n this.name = 'SlackInstallStateError';\n }\n}\n\nexport class SlackInstallStateActorMismatchError extends Error {\n constructor() {\n super('Slack install state was created by a different user');\n this.name = 'SlackInstallStateActorMismatchError';\n }\n}\n\nexport class SlackOAuthCallbackError extends Error {\n constructor(\n public readonly providerError: string,\n public readonly providerDescription: string | undefined,\n ) {\n super(providerDescription ?? `Slack OAuth callback failed: ${providerError}`);\n this.name = 'SlackOAuthCallbackError';\n }\n}\n\nexport class SlackAuthorizationScopeMismatchError extends Error {\n constructor(public readonly missingScopes: string[]) {\n super(`Slack authorization is missing required scopes: ${missingScopes.join(', ')}`);\n this.name = 'SlackAuthorizationScopeMismatchError';\n }\n}\n\nexport class SlackEnterpriseInstallUnsupportedError extends Error {\n constructor() {\n super('Slack Enterprise Grid installations are not supported');\n this.name = 'SlackEnterpriseInstallUnsupportedError';\n }\n}\n\nexport class SlackTokenRotationUnsupportedError extends Error {\n constructor() {\n super('Slack token rotation is not supported');\n this.name = 'SlackTokenRotationUnsupportedError';\n }\n}\n\nexport class SlackInstallationAlreadyLinkedError extends Error {\n constructor(teamId: string) {\n super(`Slack team is already linked to another Shipfox workspace: ${teamId}`);\n this.name = 'SlackInstallationAlreadyLinkedError';\n }\n}\n\nexport class SlackConnectionAlreadyLinkedError extends Error {\n constructor(connectionId: string) {\n super(`Integration connection is already linked to another Slack team: ${connectionId}`);\n this.name = 'SlackConnectionAlreadyLinkedError';\n }\n}\n\nexport class SlackConnectionNotFoundError extends Error {\n constructor(connectionId: string) {\n super(`Slack integration connection was not found: ${connectionId}`);\n this.name = 'SlackConnectionNotFoundError';\n }\n}\n\nexport class SlackBotTokenMissingError extends Error {\n constructor(connectionId: string) {\n super(`Slack bot token is missing for connection: ${connectionId}`);\n this.name = 'SlackBotTokenMissingError';\n }\n}\n\nexport type SlackAccessTokenUnavailableReason =\n | 'installation-not-found'\n | 'not-installed'\n | 'expired';\n\nexport class SlackAccessTokenUnavailableError extends Error {\n constructor(\n public readonly connectionId: string,\n public readonly reason: SlackAccessTokenUnavailableReason,\n public readonly expiresAt?: Date,\n ) {\n super(`Slack access token is unavailable for connection: ${connectionId} (${reason})`);\n this.name = 'SlackAccessTokenUnavailableError';\n }\n}\n"],"names":["IntegrationProviderError","SlackIntegrationProviderError","SlackInstallStateError","Error","message","name","SlackInstallStateActorMismatchError","SlackOAuthCallbackError","providerError","providerDescription","SlackAuthorizationScopeMismatchError","missingScopes","join","SlackEnterpriseInstallUnsupportedError","SlackTokenRotationUnsupportedError","SlackInstallationAlreadyLinkedError","teamId","SlackConnectionAlreadyLinkedError","connectionId","SlackConnectionNotFoundError","SlackBotTokenMissingError","SlackAccessTokenUnavailableError","reason","expiresAt"],"mappings":"AAAA,SAAQA,wBAAwB,QAAO,oCAAoC;AAE3E,OAAO,MAAMC,sCAAsCD;AAA0B;AAE7E,OAAO,MAAME,+BAA+BC;IAC1C,YAAYC,UAAU,6BAA6B,CAAE;QACnD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMC,4CAA4CH;IACvD,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAME,gCAAgCJ;IAC3C,YACE,AAAgBK,aAAqB,EACrC,AAAgBC,mBAAuC,CACvD;QACA,KAAK,CAACA,uBAAuB,CAAC,6BAA6B,EAAED,eAAe,QAH5DA,gBAAAA,oBACAC,sBAAAA;QAGhB,IAAI,CAACJ,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMK,6CAA6CP;IACxD,YAAY,AAAgBQ,aAAuB,CAAE;QACnD,KAAK,CAAC,CAAC,gDAAgD,EAAEA,cAAcC,IAAI,CAAC,OAAO,QADzDD,gBAAAA;QAE1B,IAAI,CAACN,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMQ,+CAA+CV;IAC1D,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMS,2CAA2CX;IACtD,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMU,4CAA4CZ;IACvD,YAAYa,MAAc,CAAE;QAC1B,KAAK,CAAC,CAAC,2DAA2D,EAAEA,QAAQ;QAC5E,IAAI,CAACX,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMY,0CAA0Cd;IACrD,YAAYe,YAAoB,CAAE;QAChC,KAAK,CAAC,CAAC,gEAAgE,EAAEA,cAAc;QACvF,IAAI,CAACb,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMc,qCAAqChB;IAChD,YAAYe,YAAoB,CAAE;QAChC,KAAK,CAAC,CAAC,4CAA4C,EAAEA,cAAc;QACnE,IAAI,CAACb,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMe,kCAAkCjB;IAC7C,YAAYe,YAAoB,CAAE;QAChC,KAAK,CAAC,CAAC,2CAA2C,EAAEA,cAAc;QAClE,IAAI,CAACb,IAAI,GAAG;IACd;AACF;AAOA,OAAO,MAAMgB,yCAAyClB;IACpD,YACE,AAAgBe,YAAoB,EACpC,AAAgBI,MAAyC,EACzD,AAAgBC,SAAgB,CAChC;QACA,KAAK,CAAC,CAAC,kDAAkD,EAAEL,aAAa,EAAE,EAAEI,OAAO,CAAC,CAAC,QAJrEJ,eAAAA,mBACAI,SAAAA,aACAC,YAAAA;QAGhB,IAAI,CAAClB,IAAI,GAAG;IACd;AACF"}
1
+ {"version":3,"sources":["../../src/core/errors.ts"],"sourcesContent":["import {IntegrationProviderError} from '@shipfox/api-integration-spi';\n\nexport class SlackIntegrationProviderError extends IntegrationProviderError {}\n\nexport class SlackInstallStateError extends Error {\n constructor(message = 'Invalid Slack install state') {\n super(message);\n this.name = 'SlackInstallStateError';\n }\n}\n\nexport class SlackInstallStateActorMismatchError extends Error {\n constructor() {\n super('Slack install state was created by a different user');\n this.name = 'SlackInstallStateActorMismatchError';\n }\n}\n\nexport class SlackOAuthCallbackError extends Error {\n constructor(\n public readonly providerError: string,\n public readonly providerDescription: string | undefined,\n ) {\n super(providerDescription ?? `Slack OAuth callback failed: ${providerError}`);\n this.name = 'SlackOAuthCallbackError';\n }\n}\n\nexport class SlackAuthorizationScopeMismatchError extends Error {\n constructor(public readonly missingScopes: string[]) {\n super(`Slack authorization is missing required scopes: ${missingScopes.join(', ')}`);\n this.name = 'SlackAuthorizationScopeMismatchError';\n }\n}\n\nexport class SlackEnterpriseInstallUnsupportedError extends Error {\n constructor() {\n super('Slack Enterprise Grid installations are not supported');\n this.name = 'SlackEnterpriseInstallUnsupportedError';\n }\n}\n\nexport class SlackTokenRotationUnsupportedError extends Error {\n constructor() {\n super('Slack token rotation is not supported');\n this.name = 'SlackTokenRotationUnsupportedError';\n }\n}\n\nexport class SlackInstallationAlreadyLinkedError extends Error {\n constructor(teamId: string) {\n super(`Slack team is already linked to another Shipfox workspace: ${teamId}`);\n this.name = 'SlackInstallationAlreadyLinkedError';\n }\n}\n\nexport class SlackConnectionAlreadyLinkedError extends Error {\n constructor(connectionId: string) {\n super(`Integration connection is already linked to another Slack team: ${connectionId}`);\n this.name = 'SlackConnectionAlreadyLinkedError';\n }\n}\n\nexport class SlackConnectionNotFoundError extends Error {\n constructor(connectionId: string) {\n super(`Slack integration connection was not found: ${connectionId}`);\n this.name = 'SlackConnectionNotFoundError';\n }\n}\n\nexport class SlackBotTokenMissingError extends Error {\n constructor(connectionId: string) {\n super(`Slack bot token is missing for connection: ${connectionId}`);\n this.name = 'SlackBotTokenMissingError';\n }\n}\n\nexport type SlackAccessTokenUnavailableReason =\n | 'installation-not-found'\n | 'not-installed'\n | 'expired';\n\nexport class SlackAccessTokenUnavailableError extends Error {\n constructor(\n public readonly connectionId: string,\n public readonly reason: SlackAccessTokenUnavailableReason,\n public readonly expiresAt?: Date,\n ) {\n super(`Slack access token is unavailable for connection: ${connectionId} (${reason})`);\n this.name = 'SlackAccessTokenUnavailableError';\n }\n}\n"],"names":["IntegrationProviderError","SlackIntegrationProviderError","SlackInstallStateError","Error","message","name","SlackInstallStateActorMismatchError","SlackOAuthCallbackError","providerError","providerDescription","SlackAuthorizationScopeMismatchError","missingScopes","join","SlackEnterpriseInstallUnsupportedError","SlackTokenRotationUnsupportedError","SlackInstallationAlreadyLinkedError","teamId","SlackConnectionAlreadyLinkedError","connectionId","SlackConnectionNotFoundError","SlackBotTokenMissingError","SlackAccessTokenUnavailableError","reason","expiresAt"],"mappings":"AAAA,SAAQA,wBAAwB,QAAO,+BAA+B;AAEtE,OAAO,MAAMC,sCAAsCD;AAA0B;AAE7E,OAAO,MAAME,+BAA+BC;IAC1C,YAAYC,UAAU,6BAA6B,CAAE;QACnD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMC,4CAA4CH;IACvD,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAME,gCAAgCJ;IAC3C,YACE,AAAgBK,aAAqB,EACrC,AAAgBC,mBAAuC,CACvD;QACA,KAAK,CAACA,uBAAuB,CAAC,6BAA6B,EAAED,eAAe,QAH5DA,gBAAAA,oBACAC,sBAAAA;QAGhB,IAAI,CAACJ,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMK,6CAA6CP;IACxD,YAAY,AAAgBQ,aAAuB,CAAE;QACnD,KAAK,CAAC,CAAC,gDAAgD,EAAEA,cAAcC,IAAI,CAAC,OAAO,QADzDD,gBAAAA;QAE1B,IAAI,CAACN,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMQ,+CAA+CV;IAC1D,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMS,2CAA2CX;IACtD,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMU,4CAA4CZ;IACvD,YAAYa,MAAc,CAAE;QAC1B,KAAK,CAAC,CAAC,2DAA2D,EAAEA,QAAQ;QAC5E,IAAI,CAACX,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMY,0CAA0Cd;IACrD,YAAYe,YAAoB,CAAE;QAChC,KAAK,CAAC,CAAC,gEAAgE,EAAEA,cAAc;QACvF,IAAI,CAACb,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMc,qCAAqChB;IAChD,YAAYe,YAAoB,CAAE;QAChC,KAAK,CAAC,CAAC,4CAA4C,EAAEA,cAAc;QACnE,IAAI,CAACb,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMe,kCAAkCjB;IAC7C,YAAYe,YAAoB,CAAE;QAChC,KAAK,CAAC,CAAC,2CAA2C,EAAEA,cAAc;QAClE,IAAI,CAACb,IAAI,GAAG;IACd;AACF;AAOA,OAAO,MAAMgB,yCAAyClB;IACpD,YACE,AAAgBe,YAAoB,EACpC,AAAgBI,MAAyC,EACzD,AAAgBC,SAAgB,CAChC;QACA,KAAK,CAAC,CAAC,kDAAkD,EAAEL,aAAa,EAAE,EAAEI,OAAO,CAAC,CAAC,QAJrEJ,eAAAA,mBACAI,SAAAA,aACAC,YAAAA;QAGhB,IAAI,CAAClB,IAAI,GAAG;IACd;AACF"}
@@ -1,5 +1,5 @@
1
1
  import type { UserContextMembership } from '@shipfox/api-auth-context';
2
- import type { IntegrationConnection } from '@shipfox/api-integration-core-dto';
2
+ import type { IntegrationConnection } from '@shipfox/api-integration-spi';
3
3
  import type { SlackApiClient } from '#api/client.js';
4
4
  import type { SlackTokenStore } from '#core/tokens.js';
5
5
  export interface ConnectSlackInstallationInput {
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/core/install.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,mCAAmC,CAAC;AAE7E,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AASrD,MAAM,WAAW,6BAA6B;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACzD,0BAA0B,EAAE,CAAC,MAAM,EAAE;QACnC,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;KACnD,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,wBAAwB,EAAE,CACxB,KAAK,EAAE,6BAA6B,KACjC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,2BAA2B,EAAE,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CA6CzC;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACzD,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;KACnD,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxB,GAAG,OAAO,CAAC,KAAK,CAAC,CASjB"}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/core/install.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,8BAA8B,CAAC;AAExE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AASrD,MAAM,WAAW,6BAA6B;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACzD,0BAA0B,EAAE,CAAC,MAAM,EAAE;QACnC,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;KACnD,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1D,wBAAwB,EAAE,CACxB,KAAK,EAAE,6BAA6B,KACjC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,2BAA2B,EAAE,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CA6CzC;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACzD,0BAA0B,EAAE,CAAC,KAAK,EAAE;QAClC,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;KACnD,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxB,GAAG,OAAO,CAAC,KAAK,CAAC,CASjB"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/install.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport type {IntegrationConnection} from '@shipfox/api-integration-core-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {SlackApiClient} from '#api/client.js';\nimport type {SlackTokenStore} from '#core/tokens.js';\nimport {\n SlackInstallationAlreadyLinkedError,\n SlackInstallStateActorMismatchError,\n SlackOAuthCallbackError,\n} from './errors.js';\nimport {assertSlackAuthorizationScopes} from './scopes.js';\nimport {verifySlackInstallState} from './state.js';\n\nexport interface ConnectSlackInstallationInput {\n workspaceId: string;\n teamId: string;\n teamName: string;\n appId: string;\n botUserId: string;\n scopes: string[];\n tokenExpiresAt: Date | null;\n displayName: string;\n}\n\nexport interface HandleSlackCallbackParams {\n slack: SlackApiClient;\n tokenStore: Pick<SlackTokenStore, 'storeTokens'>;\n code: string;\n state: string;\n sessionUserId: string;\n sessionMemberships: ReadonlyArray<UserContextMembership>;\n requireWorkspaceMembership: (params: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n }) => Promise<unknown>;\n getExistingSlackConnection: (input: {\n teamId: string;\n }) => Promise<IntegrationConnection<'slack'> | undefined>;\n connectSlackInstallation: (\n input: ConnectSlackInstallationInput,\n ) => Promise<IntegrationConnection<'slack'>>;\n disconnectSlackInstallation: (input: {connectionId: string}) => Promise<void>;\n}\n\nexport async function handleSlackCallback(\n params: HandleSlackCallbackParams,\n): Promise<IntegrationConnection<'slack'>> {\n const claims = verifySlackInstallState(params.state);\n if (claims.userId !== params.sessionUserId) throw new SlackInstallStateActorMismatchError();\n await params.requireWorkspaceMembership({\n workspaceId: claims.workspaceId,\n userId: claims.userId,\n memberships: params.sessionMemberships,\n });\n\n const authorization = await params.slack.exchangeAuthorizationCode({code: params.code});\n let connectedConnectionId: string | undefined;\n let shouldDisconnectConnection = false;\n let shouldRevokeToken = false;\n try {\n const existing = await params.getExistingSlackConnection({teamId: authorization.teamId});\n if (existing && existing.workspaceId !== claims.workspaceId) {\n throw new SlackInstallationAlreadyLinkedError(authorization.teamId);\n }\n shouldRevokeToken = !existing;\n assertSlackAuthorizationScopes(authorization.scopes);\n const connection = await params.connectSlackInstallation({\n workspaceId: claims.workspaceId,\n teamId: authorization.teamId,\n teamName: authorization.teamName,\n appId: authorization.appId,\n botUserId: authorization.botUserId,\n scopes: authorization.scopes,\n tokenExpiresAt: null,\n displayName: `Slack ${authorization.teamName}`,\n });\n connectedConnectionId = connection.id;\n shouldDisconnectConnection = !existing;\n await params.tokenStore.storeTokens({\n connectionId: connection.id,\n botToken: authorization.accessToken,\n editedBy: claims.userId,\n });\n return connection;\n } catch (error) {\n if (shouldRevokeToken) await bestEffortRevokeToken(params.slack, authorization.accessToken);\n if (connectedConnectionId && shouldDisconnectConnection) {\n await bestEffortDisconnectSlackInstallation(params, connectedConnectionId);\n }\n throw error;\n }\n}\n\nexport async function handleSlackOAuthCallbackError(params: {\n state: string;\n error: string;\n errorDescription?: string | undefined;\n sessionUserId: string;\n sessionMemberships: ReadonlyArray<UserContextMembership>;\n requireWorkspaceMembership: (input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n }) => Promise<unknown>;\n}): Promise<never> {\n const claims = verifySlackInstallState(params.state);\n if (claims.userId !== params.sessionUserId) throw new SlackInstallStateActorMismatchError();\n await params.requireWorkspaceMembership({\n workspaceId: claims.workspaceId,\n userId: claims.userId,\n memberships: params.sessionMemberships,\n });\n throw new SlackOAuthCallbackError(params.error, params.errorDescription);\n}\n\nasync function bestEffortRevokeToken(slack: SlackApiClient, token: string): Promise<void> {\n try {\n await slack.revokeToken({token});\n } catch (error) {\n logger().warn({err: error}, 'Slack OAuth token revocation failed');\n }\n}\n\nasync function bestEffortDisconnectSlackInstallation(\n params: HandleSlackCallbackParams,\n connectionId: string,\n): Promise<void> {\n try {\n await params.disconnectSlackInstallation({connectionId});\n } catch (error) {\n logger().warn(\n {err: error, connectionId},\n 'Slack connect compensation failed after token storage rejection',\n );\n }\n}\n"],"names":["logger","SlackInstallationAlreadyLinkedError","SlackInstallStateActorMismatchError","SlackOAuthCallbackError","assertSlackAuthorizationScopes","verifySlackInstallState","handleSlackCallback","params","claims","state","userId","sessionUserId","requireWorkspaceMembership","workspaceId","memberships","sessionMemberships","authorization","slack","exchangeAuthorizationCode","code","connectedConnectionId","shouldDisconnectConnection","shouldRevokeToken","existing","getExistingSlackConnection","teamId","scopes","connection","connectSlackInstallation","teamName","appId","botUserId","tokenExpiresAt","displayName","id","tokenStore","storeTokens","connectionId","botToken","accessToken","editedBy","error","bestEffortRevokeToken","bestEffortDisconnectSlackInstallation","handleSlackOAuthCallbackError","errorDescription","token","revokeToken","warn","err","disconnectSlackInstallation"],"mappings":"AAEA,SAAQA,MAAM,QAAO,8BAA8B;AAGnD,SACEC,mCAAmC,EACnCC,mCAAmC,EACnCC,uBAAuB,QAClB,cAAc;AACrB,SAAQC,8BAA8B,QAAO,cAAc;AAC3D,SAAQC,uBAAuB,QAAO,aAAa;AAkCnD,OAAO,eAAeC,oBACpBC,MAAiC;IAEjC,MAAMC,SAASH,wBAAwBE,OAAOE,KAAK;IACnD,IAAID,OAAOE,MAAM,KAAKH,OAAOI,aAAa,EAAE,MAAM,IAAIT;IACtD,MAAMK,OAAOK,0BAA0B,CAAC;QACtCC,aAAaL,OAAOK,WAAW;QAC/BH,QAAQF,OAAOE,MAAM;QACrBI,aAAaP,OAAOQ,kBAAkB;IACxC;IAEA,MAAMC,gBAAgB,MAAMT,OAAOU,KAAK,CAACC,yBAAyB,CAAC;QAACC,MAAMZ,OAAOY,IAAI;IAAA;IACrF,IAAIC;IACJ,IAAIC,6BAA6B;IACjC,IAAIC,oBAAoB;IACxB,IAAI;QACF,MAAMC,WAAW,MAAMhB,OAAOiB,0BAA0B,CAAC;YAACC,QAAQT,cAAcS,MAAM;QAAA;QACtF,IAAIF,YAAYA,SAASV,WAAW,KAAKL,OAAOK,WAAW,EAAE;YAC3D,MAAM,IAAIZ,oCAAoCe,cAAcS,MAAM;QACpE;QACAH,oBAAoB,CAACC;QACrBnB,+BAA+BY,cAAcU,MAAM;QACnD,MAAMC,aAAa,MAAMpB,OAAOqB,wBAAwB,CAAC;YACvDf,aAAaL,OAAOK,WAAW;YAC/BY,QAAQT,cAAcS,MAAM;YAC5BI,UAAUb,cAAca,QAAQ;YAChCC,OAAOd,cAAcc,KAAK;YAC1BC,WAAWf,cAAce,SAAS;YAClCL,QAAQV,cAAcU,MAAM;YAC5BM,gBAAgB;YAChBC,aAAa,CAAC,MAAM,EAAEjB,cAAca,QAAQ,EAAE;QAChD;QACAT,wBAAwBO,WAAWO,EAAE;QACrCb,6BAA6B,CAACE;QAC9B,MAAMhB,OAAO4B,UAAU,CAACC,WAAW,CAAC;YAClCC,cAAcV,WAAWO,EAAE;YAC3BI,UAAUtB,cAAcuB,WAAW;YACnCC,UAAUhC,OAAOE,MAAM;QACzB;QACA,OAAOiB;IACT,EAAE,OAAOc,OAAO;QACd,IAAInB,mBAAmB,MAAMoB,sBAAsBnC,OAAOU,KAAK,EAAED,cAAcuB,WAAW;QAC1F,IAAInB,yBAAyBC,4BAA4B;YACvD,MAAMsB,sCAAsCpC,QAAQa;QACtD;QACA,MAAMqB;IACR;AACF;AAEA,OAAO,eAAeG,8BAA8BrC,MAWnD;IACC,MAAMC,SAASH,wBAAwBE,OAAOE,KAAK;IACnD,IAAID,OAAOE,MAAM,KAAKH,OAAOI,aAAa,EAAE,MAAM,IAAIT;IACtD,MAAMK,OAAOK,0BAA0B,CAAC;QACtCC,aAAaL,OAAOK,WAAW;QAC/BH,QAAQF,OAAOE,MAAM;QACrBI,aAAaP,OAAOQ,kBAAkB;IACxC;IACA,MAAM,IAAIZ,wBAAwBI,OAAOkC,KAAK,EAAElC,OAAOsC,gBAAgB;AACzE;AAEA,eAAeH,sBAAsBzB,KAAqB,EAAE6B,KAAa;IACvE,IAAI;QACF,MAAM7B,MAAM8B,WAAW,CAAC;YAACD;QAAK;IAChC,EAAE,OAAOL,OAAO;QACdzC,SAASgD,IAAI,CAAC;YAACC,KAAKR;QAAK,GAAG;IAC9B;AACF;AAEA,eAAeE,sCACbpC,MAAiC,EACjC8B,YAAoB;IAEpB,IAAI;QACF,MAAM9B,OAAO2C,2BAA2B,CAAC;YAACb;QAAY;IACxD,EAAE,OAAOI,OAAO;QACdzC,SAASgD,IAAI,CACX;YAACC,KAAKR;YAAOJ;QAAY,GACzB;IAEJ;AACF"}
1
+ {"version":3,"sources":["../../src/core/install.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport type {IntegrationConnection} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {SlackApiClient} from '#api/client.js';\nimport type {SlackTokenStore} from '#core/tokens.js';\nimport {\n SlackInstallationAlreadyLinkedError,\n SlackInstallStateActorMismatchError,\n SlackOAuthCallbackError,\n} from './errors.js';\nimport {assertSlackAuthorizationScopes} from './scopes.js';\nimport {verifySlackInstallState} from './state.js';\n\nexport interface ConnectSlackInstallationInput {\n workspaceId: string;\n teamId: string;\n teamName: string;\n appId: string;\n botUserId: string;\n scopes: string[];\n tokenExpiresAt: Date | null;\n displayName: string;\n}\n\nexport interface HandleSlackCallbackParams {\n slack: SlackApiClient;\n tokenStore: Pick<SlackTokenStore, 'storeTokens'>;\n code: string;\n state: string;\n sessionUserId: string;\n sessionMemberships: ReadonlyArray<UserContextMembership>;\n requireWorkspaceMembership: (params: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n }) => Promise<unknown>;\n getExistingSlackConnection: (input: {\n teamId: string;\n }) => Promise<IntegrationConnection<'slack'> | undefined>;\n connectSlackInstallation: (\n input: ConnectSlackInstallationInput,\n ) => Promise<IntegrationConnection<'slack'>>;\n disconnectSlackInstallation: (input: {connectionId: string}) => Promise<void>;\n}\n\nexport async function handleSlackCallback(\n params: HandleSlackCallbackParams,\n): Promise<IntegrationConnection<'slack'>> {\n const claims = verifySlackInstallState(params.state);\n if (claims.userId !== params.sessionUserId) throw new SlackInstallStateActorMismatchError();\n await params.requireWorkspaceMembership({\n workspaceId: claims.workspaceId,\n userId: claims.userId,\n memberships: params.sessionMemberships,\n });\n\n const authorization = await params.slack.exchangeAuthorizationCode({code: params.code});\n let connectedConnectionId: string | undefined;\n let shouldDisconnectConnection = false;\n let shouldRevokeToken = false;\n try {\n const existing = await params.getExistingSlackConnection({teamId: authorization.teamId});\n if (existing && existing.workspaceId !== claims.workspaceId) {\n throw new SlackInstallationAlreadyLinkedError(authorization.teamId);\n }\n shouldRevokeToken = !existing;\n assertSlackAuthorizationScopes(authorization.scopes);\n const connection = await params.connectSlackInstallation({\n workspaceId: claims.workspaceId,\n teamId: authorization.teamId,\n teamName: authorization.teamName,\n appId: authorization.appId,\n botUserId: authorization.botUserId,\n scopes: authorization.scopes,\n tokenExpiresAt: null,\n displayName: `Slack ${authorization.teamName}`,\n });\n connectedConnectionId = connection.id;\n shouldDisconnectConnection = !existing;\n await params.tokenStore.storeTokens({\n connectionId: connection.id,\n botToken: authorization.accessToken,\n editedBy: claims.userId,\n });\n return connection;\n } catch (error) {\n if (shouldRevokeToken) await bestEffortRevokeToken(params.slack, authorization.accessToken);\n if (connectedConnectionId && shouldDisconnectConnection) {\n await bestEffortDisconnectSlackInstallation(params, connectedConnectionId);\n }\n throw error;\n }\n}\n\nexport async function handleSlackOAuthCallbackError(params: {\n state: string;\n error: string;\n errorDescription?: string | undefined;\n sessionUserId: string;\n sessionMemberships: ReadonlyArray<UserContextMembership>;\n requireWorkspaceMembership: (input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n }) => Promise<unknown>;\n}): Promise<never> {\n const claims = verifySlackInstallState(params.state);\n if (claims.userId !== params.sessionUserId) throw new SlackInstallStateActorMismatchError();\n await params.requireWorkspaceMembership({\n workspaceId: claims.workspaceId,\n userId: claims.userId,\n memberships: params.sessionMemberships,\n });\n throw new SlackOAuthCallbackError(params.error, params.errorDescription);\n}\n\nasync function bestEffortRevokeToken(slack: SlackApiClient, token: string): Promise<void> {\n try {\n await slack.revokeToken({token});\n } catch (error) {\n logger().warn({err: error}, 'Slack OAuth token revocation failed');\n }\n}\n\nasync function bestEffortDisconnectSlackInstallation(\n params: HandleSlackCallbackParams,\n connectionId: string,\n): Promise<void> {\n try {\n await params.disconnectSlackInstallation({connectionId});\n } catch (error) {\n logger().warn(\n {err: error, connectionId},\n 'Slack connect compensation failed after token storage rejection',\n );\n }\n}\n"],"names":["logger","SlackInstallationAlreadyLinkedError","SlackInstallStateActorMismatchError","SlackOAuthCallbackError","assertSlackAuthorizationScopes","verifySlackInstallState","handleSlackCallback","params","claims","state","userId","sessionUserId","requireWorkspaceMembership","workspaceId","memberships","sessionMemberships","authorization","slack","exchangeAuthorizationCode","code","connectedConnectionId","shouldDisconnectConnection","shouldRevokeToken","existing","getExistingSlackConnection","teamId","scopes","connection","connectSlackInstallation","teamName","appId","botUserId","tokenExpiresAt","displayName","id","tokenStore","storeTokens","connectionId","botToken","accessToken","editedBy","error","bestEffortRevokeToken","bestEffortDisconnectSlackInstallation","handleSlackOAuthCallbackError","errorDescription","token","revokeToken","warn","err","disconnectSlackInstallation"],"mappings":"AAEA,SAAQA,MAAM,QAAO,8BAA8B;AAGnD,SACEC,mCAAmC,EACnCC,mCAAmC,EACnCC,uBAAuB,QAClB,cAAc;AACrB,SAAQC,8BAA8B,QAAO,cAAc;AAC3D,SAAQC,uBAAuB,QAAO,aAAa;AAkCnD,OAAO,eAAeC,oBACpBC,MAAiC;IAEjC,MAAMC,SAASH,wBAAwBE,OAAOE,KAAK;IACnD,IAAID,OAAOE,MAAM,KAAKH,OAAOI,aAAa,EAAE,MAAM,IAAIT;IACtD,MAAMK,OAAOK,0BAA0B,CAAC;QACtCC,aAAaL,OAAOK,WAAW;QAC/BH,QAAQF,OAAOE,MAAM;QACrBI,aAAaP,OAAOQ,kBAAkB;IACxC;IAEA,MAAMC,gBAAgB,MAAMT,OAAOU,KAAK,CAACC,yBAAyB,CAAC;QAACC,MAAMZ,OAAOY,IAAI;IAAA;IACrF,IAAIC;IACJ,IAAIC,6BAA6B;IACjC,IAAIC,oBAAoB;IACxB,IAAI;QACF,MAAMC,WAAW,MAAMhB,OAAOiB,0BAA0B,CAAC;YAACC,QAAQT,cAAcS,MAAM;QAAA;QACtF,IAAIF,YAAYA,SAASV,WAAW,KAAKL,OAAOK,WAAW,EAAE;YAC3D,MAAM,IAAIZ,oCAAoCe,cAAcS,MAAM;QACpE;QACAH,oBAAoB,CAACC;QACrBnB,+BAA+BY,cAAcU,MAAM;QACnD,MAAMC,aAAa,MAAMpB,OAAOqB,wBAAwB,CAAC;YACvDf,aAAaL,OAAOK,WAAW;YAC/BY,QAAQT,cAAcS,MAAM;YAC5BI,UAAUb,cAAca,QAAQ;YAChCC,OAAOd,cAAcc,KAAK;YAC1BC,WAAWf,cAAce,SAAS;YAClCL,QAAQV,cAAcU,MAAM;YAC5BM,gBAAgB;YAChBC,aAAa,CAAC,MAAM,EAAEjB,cAAca,QAAQ,EAAE;QAChD;QACAT,wBAAwBO,WAAWO,EAAE;QACrCb,6BAA6B,CAACE;QAC9B,MAAMhB,OAAO4B,UAAU,CAACC,WAAW,CAAC;YAClCC,cAAcV,WAAWO,EAAE;YAC3BI,UAAUtB,cAAcuB,WAAW;YACnCC,UAAUhC,OAAOE,MAAM;QACzB;QACA,OAAOiB;IACT,EAAE,OAAOc,OAAO;QACd,IAAInB,mBAAmB,MAAMoB,sBAAsBnC,OAAOU,KAAK,EAAED,cAAcuB,WAAW;QAC1F,IAAInB,yBAAyBC,4BAA4B;YACvD,MAAMsB,sCAAsCpC,QAAQa;QACtD;QACA,MAAMqB;IACR;AACF;AAEA,OAAO,eAAeG,8BAA8BrC,MAWnD;IACC,MAAMC,SAASH,wBAAwBE,OAAOE,KAAK;IACnD,IAAID,OAAOE,MAAM,KAAKH,OAAOI,aAAa,EAAE,MAAM,IAAIT;IACtD,MAAMK,OAAOK,0BAA0B,CAAC;QACtCC,aAAaL,OAAOK,WAAW;QAC/BH,QAAQF,OAAOE,MAAM;QACrBI,aAAaP,OAAOQ,kBAAkB;IACxC;IACA,MAAM,IAAIZ,wBAAwBI,OAAOkC,KAAK,EAAElC,OAAOsC,gBAAgB;AACzE;AAEA,eAAeH,sBAAsBzB,KAAqB,EAAE6B,KAAa;IACvE,IAAI;QACF,MAAM7B,MAAM8B,WAAW,CAAC;YAACD;QAAK;IAChC,EAAE,OAAOL,OAAO;QACdzC,SAASgD,IAAI,CAAC;YAACC,KAAKR;QAAK,GAAG;IAC9B;AACF;AAEA,eAAeE,sCACbpC,MAAiC,EACjC8B,YAAoB;IAEpB,IAAI;QACF,MAAM9B,OAAO2C,2BAA2B,CAAC;YAACb;QAAY;IACxD,EAAE,OAAOI,OAAO;QACdzC,SAASgD,IAAI,CACX;YAACC,KAAKR;YAAOJ;QAAY,GACzB;IAEJ;AACF"}
@@ -1,4 +1,4 @@
1
- import type { ClaimWebhookDeliveryFn, GetIntegrationConnectionByIdFn, PublishIntegrationEventReceivedFn, RecordDeliveryOnlyFn, StoredWebhookRequest, WebhookProcessingResult } from '@shipfox/api-integration-core-dto';
1
+ import type { ClaimWebhookDeliveryFn, GetIntegrationConnectionByIdFn, PublishIntegrationEventReceivedFn, RecordDeliveryOnlyFn, StoredWebhookRequest, WebhookProcessingResult } from '@shipfox/api-integration-spi';
2
2
  import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
3
3
  export interface CreateSlackWebhookProcessorOptions {
4
4
  coreDb: () => NodePgDatabase<Record<string, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-processor.d.ts","sourceRoot":"","sources":["../../src/core/webhook-processor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACtB,8BAA8B,EAC9B,iCAAiC,EACjC,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,mCAAmC,CAAC;AAO3C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAQ9D,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,oBAAoB,EAAE,sBAAsB,CAAC;IAC7C,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,MAAM,4BAA4B,GAAG,uBAAuB,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC1E;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,qBAAqB,CAEvB"}
1
+ {"version":3,"file":"webhook-processor.d.ts","sourceRoot":"","sources":["../../src/core/webhook-processor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,sBAAsB,EACtB,8BAA8B,EAC9B,iCAAiC,EACjC,oBAAoB,EACpB,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAQ9D,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,oBAAoB,EAAE,sBAAsB,CAAC;IAC7C,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,MAAM,4BAA4B,GAAG,uBAAuB,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC1E;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,qBAAqB,CAEvB"}
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer';
2
- import { decodeWebhookBody } from '@shipfox/api-integration-core-dto';
3
2
  import { slackEventsRequestSchema, slackSlashCommandSchema } from '@shipfox/api-integration-slack-dto';
3
+ import { decodeWebhookBody } from '@shipfox/api-integration-spi';
4
4
  import { logger } from '@shipfox/node-opentelemetry';
5
5
  import { config } from '#config.js';
6
6
  import { isSlackTimestampWithinReplayWindow, verifySlackSignature } from '#core/signature.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/webhook-processor.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport type {\n ClaimWebhookDeliveryFn,\n GetIntegrationConnectionByIdFn,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n StoredWebhookRequest,\n WebhookProcessingResult,\n} from '@shipfox/api-integration-core-dto';\nimport {decodeWebhookBody} from '@shipfox/api-integration-core-dto';\nimport {\n slackEventsRequestSchema,\n slackSlashCommandSchema,\n} from '@shipfox/api-integration-slack-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {isSlackTimestampWithinReplayWindow, verifySlackSignature} from '#core/signature.js';\nimport {handleSlackCommand, handleSlackEvent, type SlackWebhookOutcome} from '#core/webhook.js';\n\nconst SIGNATURE_HEADER = 'x-slack-signature';\nconst TIMESTAMP_HEADER = 'x-slack-request-timestamp';\n\nexport interface CreateSlackWebhookProcessorOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n claimWebhookDelivery: ClaimWebhookDeliveryFn;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport type SlackWebhookProcessingResult = WebhookProcessingResult;\n\nexport interface SlackWebhookProcessor {\n process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;\n}\n\nexport function createSlackWebhookProcessor(\n options: CreateSlackWebhookProcessorOptions,\n): SlackWebhookProcessor {\n return {process: (request) => processSlackWebhookRequest(options, request)};\n}\n\nfunction processSlackWebhookRequest(\n options: CreateSlackWebhookProcessorOptions,\n request: StoredWebhookRequest,\n): Promise<WebhookProcessingResult> {\n if (request.route_id !== 'slack.event' && request.route_id !== 'slack.command') {\n throw new Error(`Slack processor cannot process ${request.route_id} requests`);\n }\n\n const signature = request.headers[SIGNATURE_HEADER];\n const timestamp = request.headers[TIMESTAMP_HEADER];\n if (!signature || !timestamp) {\n return Promise.resolve({outcome: 'discarded', reason: 'missing_required_input'});\n }\n\n const rawBody = decodeWebhookBody(request.body);\n const receiptTime = new Date(request.received_at).getTime();\n if (!isSlackTimestampWithinReplayWindow(timestamp, receiptTime)) {\n return Promise.resolve({outcome: 'discarded', reason: 'stale_at_receipt'});\n }\n if (\n !verifySlackSignature({\n signingSecret: config.SLACK_SIGNING_SECRET,\n signature,\n timestamp,\n rawBody,\n now: receiptTime,\n })\n ) {\n return Promise.resolve({outcome: 'discarded', reason: 'invalid_signature'});\n }\n\n return request.route_id === 'slack.event'\n ? processSlackEvent(options, rawBody)\n : processSlackCommand(options, rawBody);\n}\n\nasync function processSlackEvent(\n options: CreateSlackWebhookProcessorOptions,\n rawBody: Uint8Array,\n): Promise<WebhookProcessingResult> {\n let rawPayload: unknown;\n try {\n rawPayload = JSON.parse(Buffer.from(rawBody).toString('utf8'));\n } catch (error) {\n logger().warn({err: error}, 'slack events payload JSON parse failed');\n return {outcome: 'discarded', reason: 'malformed_payload'};\n }\n\n const event = slackEventsRequestSchema.safeParse(rawPayload);\n if (!event.success) {\n logger().warn({issues: event.error.issues}, 'slack events envelope failed schema validation');\n return {outcome: 'discarded', reason: 'unsupported_event'};\n }\n const envelope = event.data;\n if (envelope.type === 'url_verification') {\n return {outcome: 'processed', challenge: envelope.challenge};\n }\n\n const result = await options.coreDb().transaction(async (tx) =>\n handleSlackEvent({\n tx,\n deliveryId: envelope.event_id,\n envelope,\n claimWebhookDelivery: options.claimWebhookDelivery,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n return toProcessingResult(result.outcome, envelope.event_id);\n}\n\nasync function processSlackCommand(\n options: CreateSlackWebhookProcessorOptions,\n rawBody: Uint8Array,\n): Promise<WebhookProcessingResult> {\n const command = slackSlashCommandSchema.safeParse(\n Object.fromEntries(new URLSearchParams(Buffer.from(rawBody).toString('utf8'))),\n );\n if (!command.success) {\n logger().warn({issues: command.error.issues}, 'slack command failed schema validation');\n return {outcome: 'discarded', reason: 'unsupported_event'};\n }\n\n const result = await options.coreDb().transaction(async (tx) =>\n handleSlackCommand({\n tx,\n deliveryId: command.data.trigger_id,\n command: command.data,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n return toProcessingResult(result.outcome, command.data.trigger_id);\n}\n\nfunction toProcessingResult(\n outcome: SlackWebhookOutcome,\n deliveryId: string,\n): WebhookProcessingResult {\n if (outcome === 'published') return {outcome: 'processed', deliveryId};\n if (outcome === 'duplicate') return {outcome: 'duplicate', deliveryId};\n return {\n outcome: 'discarded',\n reason: outcome === 'unsupported-event' ? 'unsupported_event' : 'connection_unavailable',\n deliveryId,\n };\n}\n"],"names":["Buffer","decodeWebhookBody","slackEventsRequestSchema","slackSlashCommandSchema","logger","config","isSlackTimestampWithinReplayWindow","verifySlackSignature","handleSlackCommand","handleSlackEvent","SIGNATURE_HEADER","TIMESTAMP_HEADER","createSlackWebhookProcessor","options","process","request","processSlackWebhookRequest","route_id","Error","signature","headers","timestamp","Promise","resolve","outcome","reason","rawBody","body","receiptTime","Date","received_at","getTime","signingSecret","SLACK_SIGNING_SECRET","now","processSlackEvent","processSlackCommand","rawPayload","JSON","parse","from","toString","error","warn","err","event","safeParse","success","issues","envelope","data","type","challenge","result","coreDb","transaction","tx","deliveryId","event_id","claimWebhookDelivery","publishIntegrationEventReceived","recordDeliveryOnly","getIntegrationConnectionById","toProcessingResult","command","Object","fromEntries","URLSearchParams","trigger_id"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/webhook-processor.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {\n slackEventsRequestSchema,\n slackSlashCommandSchema,\n} from '@shipfox/api-integration-slack-dto';\nimport type {\n ClaimWebhookDeliveryFn,\n GetIntegrationConnectionByIdFn,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n StoredWebhookRequest,\n WebhookProcessingResult,\n} from '@shipfox/api-integration-spi';\nimport {decodeWebhookBody} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {NodePgDatabase} from 'drizzle-orm/node-postgres';\nimport {config} from '#config.js';\nimport {isSlackTimestampWithinReplayWindow, verifySlackSignature} from '#core/signature.js';\nimport {handleSlackCommand, handleSlackEvent, type SlackWebhookOutcome} from '#core/webhook.js';\n\nconst SIGNATURE_HEADER = 'x-slack-signature';\nconst TIMESTAMP_HEADER = 'x-slack-request-timestamp';\n\nexport interface CreateSlackWebhookProcessorOptions {\n coreDb: () => NodePgDatabase<Record<string, unknown>>;\n claimWebhookDelivery: ClaimWebhookDeliveryFn;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport type SlackWebhookProcessingResult = WebhookProcessingResult;\n\nexport interface SlackWebhookProcessor {\n process(request: StoredWebhookRequest): Promise<WebhookProcessingResult>;\n}\n\nexport function createSlackWebhookProcessor(\n options: CreateSlackWebhookProcessorOptions,\n): SlackWebhookProcessor {\n return {process: (request) => processSlackWebhookRequest(options, request)};\n}\n\nfunction processSlackWebhookRequest(\n options: CreateSlackWebhookProcessorOptions,\n request: StoredWebhookRequest,\n): Promise<WebhookProcessingResult> {\n if (request.route_id !== 'slack.event' && request.route_id !== 'slack.command') {\n throw new Error(`Slack processor cannot process ${request.route_id} requests`);\n }\n\n const signature = request.headers[SIGNATURE_HEADER];\n const timestamp = request.headers[TIMESTAMP_HEADER];\n if (!signature || !timestamp) {\n return Promise.resolve({outcome: 'discarded', reason: 'missing_required_input'});\n }\n\n const rawBody = decodeWebhookBody(request.body);\n const receiptTime = new Date(request.received_at).getTime();\n if (!isSlackTimestampWithinReplayWindow(timestamp, receiptTime)) {\n return Promise.resolve({outcome: 'discarded', reason: 'stale_at_receipt'});\n }\n if (\n !verifySlackSignature({\n signingSecret: config.SLACK_SIGNING_SECRET,\n signature,\n timestamp,\n rawBody,\n now: receiptTime,\n })\n ) {\n return Promise.resolve({outcome: 'discarded', reason: 'invalid_signature'});\n }\n\n return request.route_id === 'slack.event'\n ? processSlackEvent(options, rawBody)\n : processSlackCommand(options, rawBody);\n}\n\nasync function processSlackEvent(\n options: CreateSlackWebhookProcessorOptions,\n rawBody: Uint8Array,\n): Promise<WebhookProcessingResult> {\n let rawPayload: unknown;\n try {\n rawPayload = JSON.parse(Buffer.from(rawBody).toString('utf8'));\n } catch (error) {\n logger().warn({err: error}, 'slack events payload JSON parse failed');\n return {outcome: 'discarded', reason: 'malformed_payload'};\n }\n\n const event = slackEventsRequestSchema.safeParse(rawPayload);\n if (!event.success) {\n logger().warn({issues: event.error.issues}, 'slack events envelope failed schema validation');\n return {outcome: 'discarded', reason: 'unsupported_event'};\n }\n const envelope = event.data;\n if (envelope.type === 'url_verification') {\n return {outcome: 'processed', challenge: envelope.challenge};\n }\n\n const result = await options.coreDb().transaction(async (tx) =>\n handleSlackEvent({\n tx,\n deliveryId: envelope.event_id,\n envelope,\n claimWebhookDelivery: options.claimWebhookDelivery,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n return toProcessingResult(result.outcome, envelope.event_id);\n}\n\nasync function processSlackCommand(\n options: CreateSlackWebhookProcessorOptions,\n rawBody: Uint8Array,\n): Promise<WebhookProcessingResult> {\n const command = slackSlashCommandSchema.safeParse(\n Object.fromEntries(new URLSearchParams(Buffer.from(rawBody).toString('utf8'))),\n );\n if (!command.success) {\n logger().warn({issues: command.error.issues}, 'slack command failed schema validation');\n return {outcome: 'discarded', reason: 'unsupported_event'};\n }\n\n const result = await options.coreDb().transaction(async (tx) =>\n handleSlackCommand({\n tx,\n deliveryId: command.data.trigger_id,\n command: command.data,\n publishIntegrationEventReceived: options.publishIntegrationEventReceived,\n recordDeliveryOnly: options.recordDeliveryOnly,\n getIntegrationConnectionById: options.getIntegrationConnectionById,\n }),\n );\n return toProcessingResult(result.outcome, command.data.trigger_id);\n}\n\nfunction toProcessingResult(\n outcome: SlackWebhookOutcome,\n deliveryId: string,\n): WebhookProcessingResult {\n if (outcome === 'published') return {outcome: 'processed', deliveryId};\n if (outcome === 'duplicate') return {outcome: 'duplicate', deliveryId};\n return {\n outcome: 'discarded',\n reason: outcome === 'unsupported-event' ? 'unsupported_event' : 'connection_unavailable',\n deliveryId,\n };\n}\n"],"names":["Buffer","slackEventsRequestSchema","slackSlashCommandSchema","decodeWebhookBody","logger","config","isSlackTimestampWithinReplayWindow","verifySlackSignature","handleSlackCommand","handleSlackEvent","SIGNATURE_HEADER","TIMESTAMP_HEADER","createSlackWebhookProcessor","options","process","request","processSlackWebhookRequest","route_id","Error","signature","headers","timestamp","Promise","resolve","outcome","reason","rawBody","body","receiptTime","Date","received_at","getTime","signingSecret","SLACK_SIGNING_SECRET","now","processSlackEvent","processSlackCommand","rawPayload","JSON","parse","from","toString","error","warn","err","event","safeParse","success","issues","envelope","data","type","challenge","result","coreDb","transaction","tx","deliveryId","event_id","claimWebhookDelivery","publishIntegrationEventReceived","recordDeliveryOnly","getIntegrationConnectionById","toProcessingResult","command","Object","fromEntries","URLSearchParams","trigger_id"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import type { ClaimWebhookDeliveryFn, GetIntegrationConnectionByIdFn, IntegrationTx, PublishIntegrationEventReceivedFn, RecordDeliveryOnlyFn } from '@shipfox/api-integration-core-dto';
2
1
  import { type SlackEventBaseEnvelopeDto, type SlackSlashCommandDto } from '@shipfox/api-integration-slack-dto';
2
+ import type { ClaimWebhookDeliveryFn, GetIntegrationConnectionByIdFn, IntegrationTx, PublishIntegrationEventReceivedFn, RecordDeliveryOnlyFn } from '@shipfox/api-integration-spi';
3
3
  export type SlackWebhookOutcome = 'published' | 'duplicate' | 'unknown-team' | 'revoked-installation' | 'revoked' | 'unaffected-revocation' | 'stale-lifecycle-event' | 'missing-connection' | 'inactive-connection' | 'unsupported-event' | 'self-message';
4
4
  interface SlackWebhookParams {
5
5
  tx: IntegrationTx;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/core/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,8BAA8B,EAE9B,aAAa,EACb,iCAAiC,EACjC,oBAAoB,EACrB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAIL,KAAK,yBAAyB,EAE9B,KAAK,oBAAoB,EAI1B,MAAM,oCAAoC,CAAC;AAuB5C,MAAM,MAAM,mBAAmB,GAC3B,WAAW,GACX,WAAW,GACX,cAAc,GACd,sBAAsB,GACtB,SAAS,GACT,uBAAuB,GACvB,uBAAuB,GACvB,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,cAAc,CAAC;AAEnB,UAAU,kBAAkB;IAC1B,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,QAAQ,EAAE,yBAAyB,CAAC;IACpC,oBAAoB,EAAE,sBAAsB,CAAC;CAC9C;AAED,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAClE,OAAO,EAAE,oBAAoB,CAAC;CAC/B;AASD,KAAK,mBAAmB,GAAG,OAAO,CAChC,mBAAmB,EACjB,mBAAmB,GACnB,cAAc,GACd,SAAS,GACT,uBAAuB,GACvB,uBAAuB,CAC1B,CAAC;AAGF,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAC,CAAC,CAmDzC;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAC,CAAC,CAwBzC;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAUnF"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/core/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,yBAAyB,EAE9B,KAAK,oBAAoB,EAI1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EACV,sBAAsB,EACtB,8BAA8B,EAE9B,aAAa,EACb,iCAAiC,EACjC,oBAAoB,EACrB,MAAM,8BAA8B,CAAC;AAuBtC,MAAM,MAAM,mBAAmB,GAC3B,WAAW,GACX,WAAW,GACX,cAAc,GACd,sBAAsB,GACtB,SAAS,GACT,uBAAuB,GACvB,uBAAuB,GACvB,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,cAAc,CAAC;AAEnB,UAAU,kBAAkB;IAC1B,EAAE,EAAE,aAAa,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B,EAAE,iCAAiC,CAAC;IACnE,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,4BAA4B,EAAE,8BAA8B,CAAC;CAC9D;AAED,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,QAAQ,EAAE,yBAAyB,CAAC;IACpC,oBAAoB,EAAE,sBAAsB,CAAC;CAC9C;AAED,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAClE,OAAO,EAAE,oBAAoB,CAAC;CAC/B;AASD,KAAK,mBAAmB,GAAG,OAAO,CAChC,mBAAmB,EACjB,mBAAmB,GACnB,cAAc,GACd,SAAS,GACT,uBAAuB,GACvB,uBAAuB,CAC1B,CAAC;AAGF,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,OAAO,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAC,CAAC,CAmDzC;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAC,CAAC,CAwBzC;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAUnF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import type {\n ClaimWebhookDeliveryFn,\n GetIntegrationConnectionByIdFn,\n IntegrationConnection,\n IntegrationTx,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-core-dto';\nimport {\n SLACK_APP_UNINSTALLED_EVENT,\n SLACK_PROVIDER,\n SLACK_SLASH_COMMAND_EVENT,\n type SlackEventBaseEnvelopeDto,\n type SlackLifecycleEventType,\n type SlackSlashCommandDto,\n slackEventEnvelopeSchema,\n slackLifecycleEventTypes,\n slackTokensRevokedEventSchema,\n} from '@shipfox/api-integration-slack-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {z} from 'zod';\nimport {\n getSlackInstallationByTeamId,\n markSlackInstallationRevoked,\n type SlackInstallation,\n} from '#db/installations.js';\n\nconst slackSelfAuthoredEventSchema = z\n .object({\n bot_id: z.string().optional(),\n user: z.string().optional(),\n message: z\n .object({\n bot_id: z.string().optional(),\n user: z.string().optional(),\n })\n .passthrough()\n .optional(),\n })\n .passthrough();\n\nexport type SlackWebhookOutcome =\n | 'published'\n | 'duplicate'\n | 'unknown-team'\n | 'revoked-installation'\n | 'revoked'\n | 'unaffected-revocation'\n | 'stale-lifecycle-event'\n | 'missing-connection'\n | 'inactive-connection'\n | 'unsupported-event'\n | 'self-message';\n\ninterface SlackWebhookParams {\n tx: IntegrationTx;\n deliveryId: string;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport interface HandleSlackEventParams extends SlackWebhookParams {\n envelope: SlackEventBaseEnvelopeDto;\n claimWebhookDelivery: ClaimWebhookDeliveryFn;\n}\n\nexport interface HandleSlackCommandParams extends SlackWebhookParams {\n command: SlackSlashCommandDto;\n}\n\ntype SlackConnectionResolution =\n | {kind: 'ok'; connection: IntegrationConnection; installation: SlackInstallation}\n | {\n kind: 'drop';\n outcome: SlackConnectionDropOutcome;\n };\n\ntype SlackCommandOutcome = Exclude<\n SlackWebhookOutcome,\n | 'unsupported-event'\n | 'self-message'\n | 'revoked'\n | 'unaffected-revocation'\n | 'stale-lifecycle-event'\n>;\ntype SlackConnectionDropOutcome = Exclude<SlackCommandOutcome, 'published' | 'duplicate'>;\n\nexport async function handleSlackEvent(\n params: HandleSlackEventParams,\n): Promise<{outcome: SlackWebhookOutcome}> {\n const lifecycleType = asSlackLifecycleEventType(params.envelope.event.type);\n if (lifecycleType) return handleSlackLifecycleEvent(params, lifecycleType);\n\n const resolution = await resolveSlackConnection({\n ...params,\n teamId: params.envelope.team_id,\n });\n if (resolution.kind === 'drop') return resolution;\n\n if (isSelfAuthoredSlackEvent(params.envelope.event, resolution.installation.botUserId)) {\n await recordSlackDeliveryOnly(params);\n return {outcome: 'self-message'};\n }\n\n const supported = slackEventEnvelopeSchema.safeParse(params.envelope);\n if (!supported.success) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n type: params.envelope.event.type,\n },\n 'slack webhook: unsupported event, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {outcome: 'unsupported-event'};\n }\n\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: SLACK_PROVIDER,\n source: resolution.connection.slug,\n event: supported.data.event.type,\n workspaceId: resolution.connection.workspaceId,\n connectionId: resolution.connection.id,\n connectionName: resolution.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload: {\n ...supported.data.event,\n team_id: supported.data.team_id,\n api_app_id: supported.data.api_app_id,\n event_id: supported.data.event_id,\n event_time: supported.data.event_time,\n },\n },\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nexport async function handleSlackCommand(\n params: HandleSlackCommandParams,\n): Promise<{outcome: SlackCommandOutcome}> {\n const resolution = await resolveSlackConnection({\n ...params,\n teamId: params.command.team_id,\n });\n if (resolution.kind === 'drop') return resolution;\n\n const {token: _token, ...payload} = params.command;\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: SLACK_PROVIDER,\n source: resolution.connection.slug,\n event: SLACK_SLASH_COMMAND_EVENT,\n workspaceId: resolution.connection.workspaceId,\n connectionId: resolution.connection.id,\n connectionName: resolution.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload,\n },\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nexport function isSelfAuthoredSlackEvent(event: unknown, botUserId: string): boolean {\n const parsed = slackSelfAuthoredEventSchema.safeParse(event);\n if (!parsed.success) return false;\n const nestedMessage = parsed.data.message;\n return (\n parsed.data.bot_id !== undefined ||\n parsed.data.user === botUserId ||\n nestedMessage?.bot_id !== undefined ||\n nestedMessage?.user === botUserId\n );\n}\n\nfunction asSlackLifecycleEventType(eventType: string): SlackLifecycleEventType | undefined {\n return slackLifecycleEventTypes.find((type) => type === eventType);\n}\n\nasync function handleSlackLifecycleEvent(\n params: HandleSlackEventParams,\n lifecycleType: SlackLifecycleEventType,\n): Promise<{outcome: SlackWebhookOutcome}> {\n const claim = await params.claimWebhookDelivery({\n tx: params.tx,\n provider: SLACK_PROVIDER,\n deliveryId: params.deliveryId,\n });\n if (!claim.claimed) {\n logger().info(\n {deliveryId: params.deliveryId, teamId: params.envelope.team_id, lifecycleType},\n 'slack lifecycle event: duplicate delivery, dropping',\n );\n return {outcome: 'duplicate'};\n }\n\n const installation = await getSlackInstallationByTeamId(params.envelope.team_id, {tx: params.tx});\n if (!installation) {\n logger().warn(\n {deliveryId: params.deliveryId, teamId: params.envelope.team_id, lifecycleType},\n 'slack lifecycle event: unknown team, dropping',\n );\n return {outcome: 'unknown-team'};\n }\n\n if (installation.status !== 'installed') {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n lifecycleType,\n },\n 'slack lifecycle event: installation is not installed, dropping',\n );\n return {outcome: 'revoked-installation'};\n }\n\n if (\n isSlackLifecycleEventOlderThanInstallation(params.envelope.event_time, installation.updatedAt)\n ) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n },\n 'slack lifecycle event: predates the current installation, dropping',\n );\n return {outcome: 'stale-lifecycle-event'};\n }\n\n const revokesInstallation =\n lifecycleType === SLACK_APP_UNINSTALLED_EVENT ||\n slackTokensRevokedAffectsBot(params.envelope.event, installation.botUserId);\n if (!revokesInstallation) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n },\n 'slack lifecycle event: token revocation does not affect installation bot, dropping',\n );\n return {outcome: 'unaffected-revocation'};\n }\n\n const revoked = await markSlackInstallationRevoked(installation.connectionId, {\n tx: params.tx,\n expectedGeneration: installation.generation,\n });\n if (!revoked) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n },\n 'slack lifecycle event: installation changed before revocation, dropping',\n );\n return {outcome: 'stale-lifecycle-event'};\n }\n\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n lifecycleType,\n },\n 'slack lifecycle event: installation revoked',\n );\n return {outcome: 'revoked'};\n}\n\nfunction slackTokensRevokedAffectsBot(event: unknown, botUserId: string): boolean {\n const parsed = slackTokensRevokedEventSchema.safeParse(event);\n // Only a named bot token proves this installation credential was revoked; OAuth-only and missing lists do not.\n return parsed.success && (parsed.data.tokens?.bot?.includes(botUserId) ?? false);\n}\n\nfunction isSlackLifecycleEventOlderThanInstallation(\n eventTime: number,\n installationUpdatedAt: Date,\n): boolean {\n // Slack event_time has second precision, so an event stamped in the installation second is not provably stale.\n return eventTime < Math.floor(installationUpdatedAt.getTime() / 1000);\n}\n\nasync function resolveSlackConnection(\n params: SlackWebhookParams & {teamId: string},\n): Promise<SlackConnectionResolution> {\n const installation = await getSlackInstallationByTeamId(params.teamId, {tx: params.tx});\n if (!installation) {\n logger().warn(\n {deliveryId: params.deliveryId, teamId: params.teamId},\n 'slack webhook: unknown team, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'unknown-team'};\n }\n\n if (installation.status !== 'installed') {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.teamId,\n connectionId: installation.connectionId,\n },\n 'slack webhook: installation is not installed, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'revoked-installation'};\n }\n\n const connection = await params.getIntegrationConnectionById(installation.connectionId, {\n tx: params.tx,\n });\n if (!connection) {\n logger().warn(\n {\n deliveryId: params.deliveryId,\n teamId: params.teamId,\n connectionId: installation.connectionId,\n },\n 'slack webhook: installation has no connection, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'missing-connection'};\n }\n\n if (connection.lifecycleStatus !== 'active') {\n logger().info(\n {deliveryId: params.deliveryId, teamId: params.teamId, connectionId: connection.id},\n 'slack webhook: inactive connection, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'inactive-connection'};\n }\n\n return {kind: 'ok', connection, installation};\n}\n\nasync function recordSlackDeliveryOnly(\n params: Pick<SlackWebhookParams, 'tx' | 'deliveryId' | 'recordDeliveryOnly'>,\n): Promise<void> {\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: SLACK_PROVIDER,\n deliveryId: params.deliveryId,\n });\n}\n"],"names":["SLACK_APP_UNINSTALLED_EVENT","SLACK_PROVIDER","SLACK_SLASH_COMMAND_EVENT","slackEventEnvelopeSchema","slackLifecycleEventTypes","slackTokensRevokedEventSchema","logger","z","getSlackInstallationByTeamId","markSlackInstallationRevoked","slackSelfAuthoredEventSchema","object","bot_id","string","optional","user","message","passthrough","handleSlackEvent","params","lifecycleType","asSlackLifecycleEventType","envelope","event","type","handleSlackLifecycleEvent","resolution","resolveSlackConnection","teamId","team_id","kind","isSelfAuthoredSlackEvent","installation","botUserId","recordSlackDeliveryOnly","outcome","supported","safeParse","success","info","deliveryId","result","publishIntegrationEventReceived","tx","provider","source","connection","slug","data","workspaceId","connectionId","id","connectionName","displayName","receivedAt","Date","toISOString","payload","api_app_id","event_id","event_time","published","handleSlackCommand","command","token","_token","parsed","nestedMessage","undefined","eventType","find","claim","claimWebhookDelivery","claimed","warn","status","isSlackLifecycleEventOlderThanInstallation","updatedAt","revokesInstallation","slackTokensRevokedAffectsBot","revoked","expectedGeneration","generation","tokens","bot","includes","eventTime","installationUpdatedAt","Math","floor","getTime","getIntegrationConnectionById","lifecycleStatus","recordDeliveryOnly"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/webhook.ts"],"sourcesContent":["import {\n SLACK_APP_UNINSTALLED_EVENT,\n SLACK_PROVIDER,\n SLACK_SLASH_COMMAND_EVENT,\n type SlackEventBaseEnvelopeDto,\n type SlackLifecycleEventType,\n type SlackSlashCommandDto,\n slackEventEnvelopeSchema,\n slackLifecycleEventTypes,\n slackTokensRevokedEventSchema,\n} from '@shipfox/api-integration-slack-dto';\nimport type {\n ClaimWebhookDeliveryFn,\n GetIntegrationConnectionByIdFn,\n IntegrationConnection,\n IntegrationTx,\n PublishIntegrationEventReceivedFn,\n RecordDeliveryOnlyFn,\n} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {z} from 'zod';\nimport {\n getSlackInstallationByTeamId,\n markSlackInstallationRevoked,\n type SlackInstallation,\n} from '#db/installations.js';\n\nconst slackSelfAuthoredEventSchema = z\n .object({\n bot_id: z.string().optional(),\n user: z.string().optional(),\n message: z\n .object({\n bot_id: z.string().optional(),\n user: z.string().optional(),\n })\n .passthrough()\n .optional(),\n })\n .passthrough();\n\nexport type SlackWebhookOutcome =\n | 'published'\n | 'duplicate'\n | 'unknown-team'\n | 'revoked-installation'\n | 'revoked'\n | 'unaffected-revocation'\n | 'stale-lifecycle-event'\n | 'missing-connection'\n | 'inactive-connection'\n | 'unsupported-event'\n | 'self-message';\n\ninterface SlackWebhookParams {\n tx: IntegrationTx;\n deliveryId: string;\n publishIntegrationEventReceived: PublishIntegrationEventReceivedFn;\n recordDeliveryOnly: RecordDeliveryOnlyFn;\n getIntegrationConnectionById: GetIntegrationConnectionByIdFn;\n}\n\nexport interface HandleSlackEventParams extends SlackWebhookParams {\n envelope: SlackEventBaseEnvelopeDto;\n claimWebhookDelivery: ClaimWebhookDeliveryFn;\n}\n\nexport interface HandleSlackCommandParams extends SlackWebhookParams {\n command: SlackSlashCommandDto;\n}\n\ntype SlackConnectionResolution =\n | {kind: 'ok'; connection: IntegrationConnection; installation: SlackInstallation}\n | {\n kind: 'drop';\n outcome: SlackConnectionDropOutcome;\n };\n\ntype SlackCommandOutcome = Exclude<\n SlackWebhookOutcome,\n | 'unsupported-event'\n | 'self-message'\n | 'revoked'\n | 'unaffected-revocation'\n | 'stale-lifecycle-event'\n>;\ntype SlackConnectionDropOutcome = Exclude<SlackCommandOutcome, 'published' | 'duplicate'>;\n\nexport async function handleSlackEvent(\n params: HandleSlackEventParams,\n): Promise<{outcome: SlackWebhookOutcome}> {\n const lifecycleType = asSlackLifecycleEventType(params.envelope.event.type);\n if (lifecycleType) return handleSlackLifecycleEvent(params, lifecycleType);\n\n const resolution = await resolveSlackConnection({\n ...params,\n teamId: params.envelope.team_id,\n });\n if (resolution.kind === 'drop') return resolution;\n\n if (isSelfAuthoredSlackEvent(params.envelope.event, resolution.installation.botUserId)) {\n await recordSlackDeliveryOnly(params);\n return {outcome: 'self-message'};\n }\n\n const supported = slackEventEnvelopeSchema.safeParse(params.envelope);\n if (!supported.success) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n type: params.envelope.event.type,\n },\n 'slack webhook: unsupported event, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {outcome: 'unsupported-event'};\n }\n\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: SLACK_PROVIDER,\n source: resolution.connection.slug,\n event: supported.data.event.type,\n workspaceId: resolution.connection.workspaceId,\n connectionId: resolution.connection.id,\n connectionName: resolution.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload: {\n ...supported.data.event,\n team_id: supported.data.team_id,\n api_app_id: supported.data.api_app_id,\n event_id: supported.data.event_id,\n event_time: supported.data.event_time,\n },\n },\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nexport async function handleSlackCommand(\n params: HandleSlackCommandParams,\n): Promise<{outcome: SlackCommandOutcome}> {\n const resolution = await resolveSlackConnection({\n ...params,\n teamId: params.command.team_id,\n });\n if (resolution.kind === 'drop') return resolution;\n\n const {token: _token, ...payload} = params.command;\n const result = await params.publishIntegrationEventReceived({\n tx: params.tx,\n event: {\n provider: SLACK_PROVIDER,\n source: resolution.connection.slug,\n event: SLACK_SLASH_COMMAND_EVENT,\n workspaceId: resolution.connection.workspaceId,\n connectionId: resolution.connection.id,\n connectionName: resolution.connection.displayName,\n deliveryId: params.deliveryId,\n receivedAt: new Date().toISOString(),\n payload,\n },\n });\n\n return {outcome: result.published ? 'published' : 'duplicate'};\n}\n\nexport function isSelfAuthoredSlackEvent(event: unknown, botUserId: string): boolean {\n const parsed = slackSelfAuthoredEventSchema.safeParse(event);\n if (!parsed.success) return false;\n const nestedMessage = parsed.data.message;\n return (\n parsed.data.bot_id !== undefined ||\n parsed.data.user === botUserId ||\n nestedMessage?.bot_id !== undefined ||\n nestedMessage?.user === botUserId\n );\n}\n\nfunction asSlackLifecycleEventType(eventType: string): SlackLifecycleEventType | undefined {\n return slackLifecycleEventTypes.find((type) => type === eventType);\n}\n\nasync function handleSlackLifecycleEvent(\n params: HandleSlackEventParams,\n lifecycleType: SlackLifecycleEventType,\n): Promise<{outcome: SlackWebhookOutcome}> {\n const claim = await params.claimWebhookDelivery({\n tx: params.tx,\n provider: SLACK_PROVIDER,\n deliveryId: params.deliveryId,\n });\n if (!claim.claimed) {\n logger().info(\n {deliveryId: params.deliveryId, teamId: params.envelope.team_id, lifecycleType},\n 'slack lifecycle event: duplicate delivery, dropping',\n );\n return {outcome: 'duplicate'};\n }\n\n const installation = await getSlackInstallationByTeamId(params.envelope.team_id, {tx: params.tx});\n if (!installation) {\n logger().warn(\n {deliveryId: params.deliveryId, teamId: params.envelope.team_id, lifecycleType},\n 'slack lifecycle event: unknown team, dropping',\n );\n return {outcome: 'unknown-team'};\n }\n\n if (installation.status !== 'installed') {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n lifecycleType,\n },\n 'slack lifecycle event: installation is not installed, dropping',\n );\n return {outcome: 'revoked-installation'};\n }\n\n if (\n isSlackLifecycleEventOlderThanInstallation(params.envelope.event_time, installation.updatedAt)\n ) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n },\n 'slack lifecycle event: predates the current installation, dropping',\n );\n return {outcome: 'stale-lifecycle-event'};\n }\n\n const revokesInstallation =\n lifecycleType === SLACK_APP_UNINSTALLED_EVENT ||\n slackTokensRevokedAffectsBot(params.envelope.event, installation.botUserId);\n if (!revokesInstallation) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n },\n 'slack lifecycle event: token revocation does not affect installation bot, dropping',\n );\n return {outcome: 'unaffected-revocation'};\n }\n\n const revoked = await markSlackInstallationRevoked(installation.connectionId, {\n tx: params.tx,\n expectedGeneration: installation.generation,\n });\n if (!revoked) {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n },\n 'slack lifecycle event: installation changed before revocation, dropping',\n );\n return {outcome: 'stale-lifecycle-event'};\n }\n\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.envelope.team_id,\n connectionId: installation.connectionId,\n lifecycleType,\n },\n 'slack lifecycle event: installation revoked',\n );\n return {outcome: 'revoked'};\n}\n\nfunction slackTokensRevokedAffectsBot(event: unknown, botUserId: string): boolean {\n const parsed = slackTokensRevokedEventSchema.safeParse(event);\n // Only a named bot token proves this installation credential was revoked; OAuth-only and missing lists do not.\n return parsed.success && (parsed.data.tokens?.bot?.includes(botUserId) ?? false);\n}\n\nfunction isSlackLifecycleEventOlderThanInstallation(\n eventTime: number,\n installationUpdatedAt: Date,\n): boolean {\n // Slack event_time has second precision, so an event stamped in the installation second is not provably stale.\n return eventTime < Math.floor(installationUpdatedAt.getTime() / 1000);\n}\n\nasync function resolveSlackConnection(\n params: SlackWebhookParams & {teamId: string},\n): Promise<SlackConnectionResolution> {\n const installation = await getSlackInstallationByTeamId(params.teamId, {tx: params.tx});\n if (!installation) {\n logger().warn(\n {deliveryId: params.deliveryId, teamId: params.teamId},\n 'slack webhook: unknown team, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'unknown-team'};\n }\n\n if (installation.status !== 'installed') {\n logger().info(\n {\n deliveryId: params.deliveryId,\n teamId: params.teamId,\n connectionId: installation.connectionId,\n },\n 'slack webhook: installation is not installed, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'revoked-installation'};\n }\n\n const connection = await params.getIntegrationConnectionById(installation.connectionId, {\n tx: params.tx,\n });\n if (!connection) {\n logger().warn(\n {\n deliveryId: params.deliveryId,\n teamId: params.teamId,\n connectionId: installation.connectionId,\n },\n 'slack webhook: installation has no connection, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'missing-connection'};\n }\n\n if (connection.lifecycleStatus !== 'active') {\n logger().info(\n {deliveryId: params.deliveryId, teamId: params.teamId, connectionId: connection.id},\n 'slack webhook: inactive connection, dropping',\n );\n await recordSlackDeliveryOnly(params);\n return {kind: 'drop', outcome: 'inactive-connection'};\n }\n\n return {kind: 'ok', connection, installation};\n}\n\nasync function recordSlackDeliveryOnly(\n params: Pick<SlackWebhookParams, 'tx' | 'deliveryId' | 'recordDeliveryOnly'>,\n): Promise<void> {\n await params.recordDeliveryOnly({\n tx: params.tx,\n provider: SLACK_PROVIDER,\n deliveryId: params.deliveryId,\n });\n}\n"],"names":["SLACK_APP_UNINSTALLED_EVENT","SLACK_PROVIDER","SLACK_SLASH_COMMAND_EVENT","slackEventEnvelopeSchema","slackLifecycleEventTypes","slackTokensRevokedEventSchema","logger","z","getSlackInstallationByTeamId","markSlackInstallationRevoked","slackSelfAuthoredEventSchema","object","bot_id","string","optional","user","message","passthrough","handleSlackEvent","params","lifecycleType","asSlackLifecycleEventType","envelope","event","type","handleSlackLifecycleEvent","resolution","resolveSlackConnection","teamId","team_id","kind","isSelfAuthoredSlackEvent","installation","botUserId","recordSlackDeliveryOnly","outcome","supported","safeParse","success","info","deliveryId","result","publishIntegrationEventReceived","tx","provider","source","connection","slug","data","workspaceId","connectionId","id","connectionName","displayName","receivedAt","Date","toISOString","payload","api_app_id","event_id","event_time","published","handleSlackCommand","command","token","_token","parsed","nestedMessage","undefined","eventType","find","claim","claimWebhookDelivery","claimed","warn","status","isSlackLifecycleEventOlderThanInstallation","updatedAt","revokesInstallation","slackTokensRevokedAffectsBot","revoked","expectedGeneration","generation","tokens","bot","includes","eventTime","installationUpdatedAt","Math","floor","getTime","getIntegrationConnectionById","lifecycleStatus","recordDeliveryOnly"],"mappings":"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"}
@@ -1,2 +1,16 @@
1
- export { toIntegrationConnectionDto } from '@shipfox/api-integration-core-dto';
1
+ import type { IntegrationCapability, IntegrationConnection } from '@shipfox/api-integration-spi';
2
+ export declare function toIntegrationConnectionDto(connection: IntegrationConnection, params: {
3
+ capabilities: IntegrationCapability[];
4
+ }): {
5
+ id: string;
6
+ workspace_id: string;
7
+ provider: string;
8
+ external_account_id: string;
9
+ slug: string;
10
+ display_name: string;
11
+ lifecycle_status: import("@shipfox/api-integration-spi").IntegrationConnectionLifecycleStatus;
12
+ capabilities: IntegrationCapability[];
13
+ created_at: string;
14
+ updated_at: string;
15
+ };
2
16
  //# sourceMappingURL=integrations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,0BAA0B,EAAC,MAAM,mCAAmC,CAAC"}
1
+ {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,8BAA8B,CAAC;AAE/F,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE;IAAC,YAAY,EAAE,qBAAqB,EAAE,CAAA;CAAC;;;;;;;;;;;EAchD"}
@@ -1,3 +1,16 @@
1
- export { toIntegrationConnectionDto } from '@shipfox/api-integration-core-dto';
1
+ export function toIntegrationConnectionDto(connection, params) {
2
+ return {
3
+ id: connection.id,
4
+ workspace_id: connection.workspaceId,
5
+ provider: connection.provider,
6
+ external_account_id: connection.externalAccountId,
7
+ slug: connection.slug,
8
+ display_name: connection.displayName,
9
+ lifecycle_status: connection.lifecycleStatus,
10
+ capabilities: params.capabilities,
11
+ created_at: connection.createdAt.toISOString(),
12
+ updated_at: connection.updatedAt.toISOString()
13
+ };
14
+ }
2
15
 
3
16
  //# sourceMappingURL=integrations.js.map