@jobmatchme/bee-slack 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,6 +49,40 @@ POST /api/handoffs
49
49
  GET /api/handoffs/:routeId/:threadTs/replies
50
50
  ```
51
51
 
52
+ ## Route-opt-in native streaming
53
+
54
+ A normal channel mention route can opt into Slack native streaming. The stream
55
+ starts in the mention's thread with the header
56
+ `Ich bearbeite jetzt deine Anfrage...`, renders transport-neutral Bee action
57
+ updates as Slack task cards, and stops with the complete assistant markdown.
58
+ Routes without `streaming.enabled: true` retain the existing
59
+ `chat.postMessage`/`chat.update` delivery.
60
+
61
+ ```json
62
+ {
63
+ "id": "mention-pilot",
64
+ "match": { "channelIds": ["C0123456789"] },
65
+ "streaming": {
66
+ "enabled": true,
67
+ "taskDisplayMode": "timeline"
68
+ },
69
+ "worker": { "subject": "fabee.agent.pi.default" },
70
+ "session": { "strategy": "thread", "prefix": "bee-pilot" }
71
+ }
72
+ ```
73
+
74
+ `taskDisplayMode` accepts `timeline`, `plan`, or `dense` and defaults to
75
+ `timeline`. Streaming is deliberately limited to interactive app mentions;
76
+ DMs, scheduled runs, and authenticated handoffs remain on legacy delivery even
77
+ if their selected route contains this option. Final markdown is passed through
78
+ unchanged up to Slack's 12,000-character limit and visibly truncated above it.
79
+ Task titles and details are capped conservatively for Slack chunk limits.
80
+
81
+ Open stream state is process-local and is not recovered after a pod restart.
82
+ Slack API failures are surfaced to Bee Gate, which owns the per-run fallback
83
+ from streaming to legacy/degraded delivery. Generated artifacts remain separate
84
+ `files.uploadV2` uploads after the final response.
85
+
52
86
  ## Local development
53
87
 
54
88
  For local manual testing, copy `local.config.example.json` to
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,kBAAkB,CA+BlE","sourcesContent":["import { readFileSync } from \"fs\";\nimport { resolve } from \"path\";\nimport type { SlackGatewayConfig } from \"./types.js\";\n\nexport function loadConfig(configPath?: string): SlackGatewayConfig {\n\tconst path = configPath || process.env.BEE_SLACK_CONFIG;\n\tif (!path) {\n\t\tthrow new Error(\"Missing BEE_SLACK_CONFIG\");\n\t}\n\n\tconst fullPath = resolve(path);\n\tconst config = JSON.parse(readFileSync(fullPath, \"utf-8\")) as SlackGatewayConfig;\n\tconst handoffConfig = process.env.BEE_SLACK_HANDOFF_CONFIG;\n\tif (handoffConfig) {\n\t\tconfig.handoff = JSON.parse(handoffConfig) as SlackGatewayConfig[\"handoff\"];\n\t}\n\tif (!config.appToken) throw new Error(`Missing appToken in ${fullPath}`);\n\tif (!config.botToken) throw new Error(`Missing botToken in ${fullPath}`);\n\tif (!config.nats?.servers || (Array.isArray(config.nats.servers) && config.nats.servers.length === 0)) {\n\t\tthrow new Error(`Missing nats.servers in ${fullPath}`);\n\t}\n\tif (!Array.isArray(config.routes) || config.routes.length === 0) {\n\t\tthrow new Error(`Missing routes in ${fullPath}`);\n\t}\n\tif (config.handoff?.enabled) {\n\t\tif (!Array.isArray(config.handoff.routes) || config.handoff.routes.length === 0) {\n\t\t\tthrow new Error(`handoff.routes must be a non-empty array in ${fullPath}`);\n\t\t}\n\t\tfor (const route of config.handoff.routes) {\n\t\t\tif (!route.id || !route.channelId || !route.worker?.subject) {\n\t\t\t\tthrow new Error(`Every handoff route needs id, channelId and worker.subject in ${fullPath}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn config;\n}\n"]}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,kBAAkB,CA+ClE","sourcesContent":["import { readFileSync } from \"fs\";\nimport { resolve } from \"path\";\nimport type { SlackGatewayConfig } from \"./types.js\";\n\nexport function loadConfig(configPath?: string): SlackGatewayConfig {\n\tconst path = configPath || process.env.BEE_SLACK_CONFIG;\n\tif (!path) {\n\t\tthrow new Error(\"Missing BEE_SLACK_CONFIG\");\n\t}\n\n\tconst fullPath = resolve(path);\n\tconst config = JSON.parse(readFileSync(fullPath, \"utf-8\")) as SlackGatewayConfig;\n\tconst handoffConfig = process.env.BEE_SLACK_HANDOFF_CONFIG;\n\tif (handoffConfig) {\n\t\tconfig.handoff = JSON.parse(handoffConfig) as SlackGatewayConfig[\"handoff\"];\n\t}\n\tif (!config.appToken) throw new Error(`Missing appToken in ${fullPath}`);\n\tif (!config.botToken) throw new Error(`Missing botToken in ${fullPath}`);\n\tif (!config.nats?.servers || (Array.isArray(config.nats.servers) && config.nats.servers.length === 0)) {\n\t\tthrow new Error(`Missing nats.servers in ${fullPath}`);\n\t}\n\tif (!Array.isArray(config.routes) || config.routes.length === 0) {\n\t\tthrow new Error(`Missing routes in ${fullPath}`);\n\t}\n\tconst taskDisplayModes = new Set([\"timeline\", \"plan\", \"dense\"]);\n\tfor (const route of config.routes) {\n\t\tif (route.streaming === undefined) continue;\n\t\tif (!route.streaming || typeof route.streaming !== \"object\" || Array.isArray(route.streaming)) {\n\t\t\tthrow new Error(`Route ${route.id || \"<unknown>\"} streaming must be an object in ${fullPath}`);\n\t\t}\n\t\tif (typeof route.streaming.enabled !== \"boolean\") {\n\t\t\tthrow new Error(`Route ${route.id || \"<unknown>\"} streaming.enabled must be a boolean in ${fullPath}`);\n\t\t}\n\t\tif (route.streaming.taskDisplayMode !== undefined && !taskDisplayModes.has(route.streaming.taskDisplayMode)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Route ${route.id || \"<unknown>\"} streaming.taskDisplayMode must be timeline, plan or dense in ${fullPath}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (config.handoff?.enabled) {\n\t\tif (!Array.isArray(config.handoff.routes) || config.handoff.routes.length === 0) {\n\t\t\tthrow new Error(`handoff.routes must be a non-empty array in ${fullPath}`);\n\t\t}\n\t\tfor (const route of config.handoff.routes) {\n\t\t\tif (!route.id || !route.channelId || !route.worker?.subject) {\n\t\t\t\tthrow new Error(`Every handoff route needs id, channelId and worker.subject in ${fullPath}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn config;\n}\n"]}
package/dist/config.js CHANGED
@@ -21,6 +21,20 @@ export function loadConfig(configPath) {
21
21
  if (!Array.isArray(config.routes) || config.routes.length === 0) {
22
22
  throw new Error(`Missing routes in ${fullPath}`);
23
23
  }
24
+ const taskDisplayModes = new Set(["timeline", "plan", "dense"]);
25
+ for (const route of config.routes) {
26
+ if (route.streaming === undefined)
27
+ continue;
28
+ if (!route.streaming || typeof route.streaming !== "object" || Array.isArray(route.streaming)) {
29
+ throw new Error(`Route ${route.id || "<unknown>"} streaming must be an object in ${fullPath}`);
30
+ }
31
+ if (typeof route.streaming.enabled !== "boolean") {
32
+ throw new Error(`Route ${route.id || "<unknown>"} streaming.enabled must be a boolean in ${fullPath}`);
33
+ }
34
+ if (route.streaming.taskDisplayMode !== undefined && !taskDisplayModes.has(route.streaming.taskDisplayMode)) {
35
+ throw new Error(`Route ${route.id || "<unknown>"} streaming.taskDisplayMode must be timeline, plan or dense in ${fullPath}`);
36
+ }
37
+ }
24
38
  if (config.handoff?.enabled) {
25
39
  if (!Array.isArray(config.handoff.routes) || config.handoff.routes.length === 0) {
26
40
  throw new Error(`handoff.routes must be a non-empty array in ${fullPath}`);
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAG/B,MAAM,UAAU,UAAU,CAAC,UAAmB,EAAsB;IACnE,MAAM,IAAI,GAAG,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IACxD,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAuB,CAAC;IACjF,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IAC3D,IAAI,aAAa,EAAE,CAAC;QACnB,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAkC,CAAC;IAC7E,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,EAAE,CAAC,CAAC;IACzE,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,EAAE,CAAC,CAAC;IACzE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QACvG,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjF,MAAM,IAAI,KAAK,CAAC,+CAA+C,QAAQ,EAAE,CAAC,CAAC;QAC5E,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC3C,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;gBAC7D,MAAM,IAAI,KAAK,CAAC,iEAAiE,QAAQ,EAAE,CAAC,CAAC;YAC9F,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["import { readFileSync } from \"fs\";\nimport { resolve } from \"path\";\nimport type { SlackGatewayConfig } from \"./types.js\";\n\nexport function loadConfig(configPath?: string): SlackGatewayConfig {\n\tconst path = configPath || process.env.BEE_SLACK_CONFIG;\n\tif (!path) {\n\t\tthrow new Error(\"Missing BEE_SLACK_CONFIG\");\n\t}\n\n\tconst fullPath = resolve(path);\n\tconst config = JSON.parse(readFileSync(fullPath, \"utf-8\")) as SlackGatewayConfig;\n\tconst handoffConfig = process.env.BEE_SLACK_HANDOFF_CONFIG;\n\tif (handoffConfig) {\n\t\tconfig.handoff = JSON.parse(handoffConfig) as SlackGatewayConfig[\"handoff\"];\n\t}\n\tif (!config.appToken) throw new Error(`Missing appToken in ${fullPath}`);\n\tif (!config.botToken) throw new Error(`Missing botToken in ${fullPath}`);\n\tif (!config.nats?.servers || (Array.isArray(config.nats.servers) && config.nats.servers.length === 0)) {\n\t\tthrow new Error(`Missing nats.servers in ${fullPath}`);\n\t}\n\tif (!Array.isArray(config.routes) || config.routes.length === 0) {\n\t\tthrow new Error(`Missing routes in ${fullPath}`);\n\t}\n\tif (config.handoff?.enabled) {\n\t\tif (!Array.isArray(config.handoff.routes) || config.handoff.routes.length === 0) {\n\t\t\tthrow new Error(`handoff.routes must be a non-empty array in ${fullPath}`);\n\t\t}\n\t\tfor (const route of config.handoff.routes) {\n\t\t\tif (!route.id || !route.channelId || !route.worker?.subject) {\n\t\t\t\tthrow new Error(`Every handoff route needs id, channelId and worker.subject in ${fullPath}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn config;\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from \"fs\";\nimport { resolve } from \"path\";\nimport type { SlackGatewayConfig } from \"./types.js\";\n\nexport function loadConfig(configPath?: string): SlackGatewayConfig {\n\tconst path = configPath || process.env.BEE_SLACK_CONFIG;\n\tif (!path) {\n\t\tthrow new Error(\"Missing BEE_SLACK_CONFIG\");\n\t}\n\n\tconst fullPath = resolve(path);\n\tconst config = JSON.parse(readFileSync(fullPath, \"utf-8\")) as SlackGatewayConfig;\n\tconst handoffConfig = process.env.BEE_SLACK_HANDOFF_CONFIG;\n\tif (handoffConfig) {\n\t\tconfig.handoff = JSON.parse(handoffConfig) as SlackGatewayConfig[\"handoff\"];\n\t}\n\tif (!config.appToken) throw new Error(`Missing appToken in ${fullPath}`);\n\tif (!config.botToken) throw new Error(`Missing botToken in ${fullPath}`);\n\tif (!config.nats?.servers || (Array.isArray(config.nats.servers) && config.nats.servers.length === 0)) {\n\t\tthrow new Error(`Missing nats.servers in ${fullPath}`);\n\t}\n\tif (!Array.isArray(config.routes) || config.routes.length === 0) {\n\t\tthrow new Error(`Missing routes in ${fullPath}`);\n\t}\n\tconst taskDisplayModes = new Set([\"timeline\", \"plan\", \"dense\"]);\n\tfor (const route of config.routes) {\n\t\tif (route.streaming === undefined) continue;\n\t\tif (!route.streaming || typeof route.streaming !== \"object\" || Array.isArray(route.streaming)) {\n\t\t\tthrow new Error(`Route ${route.id || \"<unknown>\"} streaming must be an object in ${fullPath}`);\n\t\t}\n\t\tif (typeof route.streaming.enabled !== \"boolean\") {\n\t\t\tthrow new Error(`Route ${route.id || \"<unknown>\"} streaming.enabled must be a boolean in ${fullPath}`);\n\t\t}\n\t\tif (route.streaming.taskDisplayMode !== undefined && !taskDisplayModes.has(route.streaming.taskDisplayMode)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Route ${route.id || \"<unknown>\"} streaming.taskDisplayMode must be timeline, plan or dense in ${fullPath}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (config.handoff?.enabled) {\n\t\tif (!Array.isArray(config.handoff.routes) || config.handoff.routes.length === 0) {\n\t\t\tthrow new Error(`handoff.routes must be a non-empty array in ${fullPath}`);\n\t\t}\n\t\tfor (const route of config.handoff.routes) {\n\t\t\tif (!route.id || !route.channelId || !route.worker?.subject) {\n\t\t\t\tthrow new Error(`Every handoff route needs id, channelId and worker.subject in ${fullPath}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn config;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAiC,kBAAkB,EAAuB,MAAM,YAAY,CAAC;AAazG,qBAAa,YAAY;IAeZ,OAAO,CAAC,MAAM;IAd1B,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,MAAM,CAAyC;IACvD,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,6BAA6B,CAAS;IAE9C,YAAoB,MAAM,EAAE,kBAAkB,EAa7C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB3B;IAED,OAAO,CAAC,kBAAkB;IAqD1B,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,kBAAkB;YAkEZ,cAAc;IA0C5B,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,iBAAiB;YAOX,mBAAmB;YA+BnB,UAAU;YAsBV,aAAa;CA0B3B;AAaD,wBAAsB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5E","sourcesContent":["import {\n\ttype AttachmentRef,\n\tBeeGatewayEngine,\n\ttype BeeResolvedTurn,\n\ttype BeeWorkerClient,\n\ttype BlobStore,\n\tcreateNatsBeeClient,\n\tLocalFileBlobStore,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { SocketModeClient } from \"@slack/socket-mode\";\nimport { WebClient } from \"@slack/web-api\";\nimport { join } from \"path\";\nimport { loadConfig } from \"./config.js\";\nimport { createHandoffServer, SlackHandoffController } from \"./handoff.js\";\nimport { HandoffReplyCache } from \"./handoff-reply-cache.js\";\nimport * as log from \"./log.js\";\nimport { resolveRoute } from \"./router.js\";\nimport { SlackSink } from \"./slack-sink.js\";\nimport type { ResolvedSlackRoute, SlackFile, SlackGatewayConfig, SlackInboundMessage } from \"./types.js\";\n\ninterface SlackUser {\n\tid: string;\n\tuserName: string;\n\tdisplayName: string;\n}\n\ninterface SlackChannel {\n\tid: string;\n\tname: string;\n}\n\nexport class SlackGateway {\n\tprivate socketClient: SocketModeClient;\n\tprivate webClient: WebClient;\n\tprivate users = new Map<string, SlackUser>();\n\tprivate channels = new Map<string, SlackChannel>();\n\tprivate botUserId: string | null = null;\n\tprivate teamId: string | null = null;\n\tprivate teamName: string | null = null;\n\tprivate blobStore: BlobStore;\n\tprivate sink: SlackSink;\n\tprivate engine: BeeGatewayEngine<string> | null = null;\n\tprivate workerClient: BeeWorkerClient | null = null;\n\tprivate handoffReplyCache = new HandoffReplyCache();\n\tprivate slackThreadHistoryUnavailable = false;\n\n\tconstructor(private config: SlackGatewayConfig) {\n\t\tthis.socketClient = new SocketModeClient({ appToken: config.appToken });\n\t\tthis.webClient = new WebClient(config.botToken);\n\t\tthis.blobStore = new LocalFileBlobStore(\n\t\t\tprocess.env.BEE_SLACK_BLOB_STORE_ROOT ||\n\t\t\t\tprocess.env.BEE_BLOB_STORE_ROOT ||\n\t\t\t\tprocess.env.HUDAI_BLOB_STORE_ROOT ||\n\t\t\t\tjoin(process.cwd(), \".bee-blob-store\"),\n\t\t);\n\t\tthis.sink = new SlackSink(this.webClient, this.blobStore, {\n\t\t\tposted: (target, ref, text) => this.handoffReplyCache.recordPosted(target, ref, text, this.botDisplayName()),\n\t\t\tupdated: (target, ref, text) => this.handoffReplyCache.recordUpdated(target, ref, text, this.botDisplayName()),\n\t\t});\n\t}\n\n\tasync start(): Promise<void> {\n\t\tthis.workerClient = await createNatsBeeClient(this.config.nats);\n\t\tthis.engine = new BeeGatewayEngine({\n\t\t\tsink: this.sink,\n\t\t\tworkerClient: this.workerClient,\n\t\t\tlogger: {\n\t\t\t\tinfo: log.logInfo,\n\t\t\t\twarn: log.logWarning,\n\t\t\t\terror: log.logError,\n\t\t\t},\n\t\t});\n\t\tconst auth = await this.webClient.auth.test();\n\t\tthis.botUserId = auth.user_id as string;\n\t\tthis.teamId = auth.team_id as string;\n\t\tthis.teamName = (auth.team as string | undefined) || null;\n\t\tawait Promise.all([this.fetchUsers(), this.fetchChannels()]);\n\t\tthis.startHandoffServer();\n\t\tthis.setupEventHandlers();\n\t\tawait this.socketClient.start();\n\t\tlog.logInfo(\n\t\t\t`Connected as ${this.botUserId} in ${this.teamId}; loaded ${this.channels.size} channels and ${this.users.size} users`,\n\t\t);\n\t}\n\n\tprivate startHandoffServer(): void {\n\t\tconst handoffConfig = this.config.handoff;\n\t\tif (!handoffConfig?.enabled) return;\n\t\tif (!this.teamId || !this.engine) throw new Error(\"Gateway must be initialized before handoff server startup\");\n\t\tconst controller = new SlackHandoffController(handoffConfig, {\n\t\t\tteamId: this.teamId,\n\t\t\tpostRootMessage: async (channelId, text) => this.sink.postMessage({ channelId }, text),\n\t\t\tgetPermalink: async (channelId, messageTs) => {\n\t\t\t\tconst result = await this.webClient.chat.getPermalink({ channel: channelId, message_ts: messageTs });\n\t\t\t\tif (!result.permalink) throw new Error(\"Slack did not return a permalink\");\n\t\t\t\treturn result.permalink;\n\t\t\t},\n\t\t\tdispatch: (input) => this.engine!.dispatch(input),\n\t\t\tgetReplies: async (channelId, threadTs) => {\n\t\t\t\tif (this.slackThreadHistoryUnavailable) return this.handoffReplyCache.replies(channelId, threadTs);\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.webClient.conversations.replies({\n\t\t\t\t\t\tchannel: channelId,\n\t\t\t\t\t\tts: threadTs,\n\t\t\t\t\t\tlimit: 100,\n\t\t\t\t\t});\n\t\t\t\t\treturn (result.messages || []).map((message) => {\n\t\t\t\t\t\tconst typed = message as typeof message & {\n\t\t\t\t\t\t\tbot_id?: string;\n\t\t\t\t\t\t\tbot_profile?: { name?: string };\n\t\t\t\t\t\t\tusername?: string;\n\t\t\t\t\t\t\tthread_ts?: string;\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tts: String(typed.ts || \"\"),\n\t\t\t\t\t\t\tthreadTs: typed.thread_ts,\n\t\t\t\t\t\t\ttext: typed.text || \"\",\n\t\t\t\t\t\t\tauthor: typed.user\n\t\t\t\t\t\t\t\t? this.users.get(typed.user)?.displayName || this.users.get(typed.user)?.userName || typed.user\n\t\t\t\t\t\t\t\t: typed.bot_profile?.name || typed.username || \"Bot\",\n\t\t\t\t\t\t\tisBot: Boolean(typed.bot_id),\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst errorCode = slackErrorCode(error);\n\t\t\t\t\tif (!isSlackHistoryPermissionError(errorCode)) throw error;\n\t\t\t\t\tthis.slackThreadHistoryUnavailable = true;\n\t\t\t\t\tlog.logWarning(`Slack thread history unavailable (${errorCode}); using outbound Bee reply cache`);\n\t\t\t\t\treturn this.handoffReplyCache.replies(channelId, threadTs);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tconst server = createHandoffServer(controller);\n\t\tconst host = handoffConfig.host || \"0.0.0.0\";\n\t\tconst port = handoffConfig.port || 8080;\n\t\tserver.listen(port, host, () => log.logInfo(`bee-slack handoff listening on http://${host}:${port}`));\n\t}\n\n\tprivate botDisplayName(): string {\n\t\tif (!this.botUserId) return \"Bee\";\n\t\tconst bot = this.users.get(this.botUserId);\n\t\treturn bot?.displayName || bot?.userName || \"Bee\";\n\t}\n\n\tprivate setupEventHandlers(): void {\n\t\tthis.socketClient.on(\"app_mention\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\t\t\tconst e = event as {\n\t\t\t\ttext: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser: string;\n\t\t\t\tts: string;\n\t\t\t\tthread_ts?: string;\n\t\t\t\tfiles?: SlackFile[];\n\t\t\t};\n\n\t\t\tif (e.channel.startsWith(\"D\")) return;\n\n\t\t\tawait this.enqueueInbound({\n\t\t\t\ttype: \"mention\",\n\t\t\t\tchannelId: e.channel,\n\t\t\t\tchannelName: this.channels.get(e.channel)?.name,\n\t\t\t\tthreadTs: e.thread_ts,\n\t\t\t\tts: e.ts,\n\t\t\t\tuserId: e.user,\n\t\t\t\tuserName: this.users.get(e.user)?.userName,\n\t\t\t\tdisplayName: this.users.get(e.user)?.displayName,\n\t\t\t\ttext: e.text.replace(/<@[A-Z0-9]+>/gi, \"\").trim(),\n\t\t\t\tfiles: e.files,\n\t\t\t});\n\t\t});\n\n\t\tthis.socketClient.on(\"message\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\t\t\tconst e = event as {\n\t\t\t\ttext?: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser?: string;\n\t\t\t\tts: string;\n\t\t\t\tthread_ts?: string;\n\t\t\t\tchannel_type?: string;\n\t\t\t\tsubtype?: string;\n\t\t\t\tbot_id?: string;\n\t\t\t\tfiles?: SlackFile[];\n\t\t\t};\n\n\t\t\tif (e.bot_id || !e.user || e.user === this.botUserId) return;\n\t\t\tif (e.subtype !== undefined && e.subtype !== \"file_share\") return;\n\t\t\tif (!e.text && (!e.files || e.files.length === 0)) return;\n\n\t\t\tconst isDM = e.channel_type === \"im\";\n\t\t\tconst isBotMention = !!this.botUserId && e.text?.includes(`<@${this.botUserId}>`);\n\t\t\tif (!isDM && isBotMention) return;\n\t\t\tif (!isDM) return;\n\n\t\t\tawait this.enqueueInbound({\n\t\t\t\ttype: \"dm\",\n\t\t\t\tchannelId: e.channel,\n\t\t\t\tchannelName: this.channels.get(e.channel)?.name,\n\t\t\t\tthreadTs: e.thread_ts,\n\t\t\t\tts: e.ts,\n\t\t\t\tuserId: e.user,\n\t\t\t\tuserName: this.users.get(e.user)?.userName,\n\t\t\t\tdisplayName: this.users.get(e.user)?.displayName,\n\t\t\t\ttext: (e.text || \"\").replace(/<@[A-Z0-9]+>/gi, \"\").trim(),\n\t\t\t\tfiles: e.files,\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate async enqueueInbound(message: SlackInboundMessage): Promise<void> {\n\t\tif (!this.botUserId || !this.teamId) {\n\t\t\tthrow new Error(\"Gateway has not been initialized\");\n\t\t}\n\t\tif (!this.engine) {\n\t\t\tthrow new Error(\"Gateway engine has not been initialized\");\n\t\t}\n\n\t\ttry {\n\t\t\tconst resolved = resolveRoute(this.config.routes, message, {\n\t\t\t\tbotUserId: this.botUserId,\n\t\t\t\tteamId: this.teamId,\n\t\t\t\tteamName: this.teamName || undefined,\n\t\t\t});\n\t\t\tif (!resolved) {\n\t\t\t\tlog.logWarning(`No route for channel ${message.channelId}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst outputTarget = this.buildOutputTarget(message, resolved);\n\t\t\tif (message.text.trim().toLowerCase() === \"stop\") {\n\t\t\t\tconst stopped = await this.engine.stopActiveRun(resolved.sessionId);\n\t\t\t\tawait this.sink.postMessage(outputTarget, stopped ? \"Stopping active run.\" : \"Nothing running.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst attachments = await this.downloadAttachments(resolved, message);\n\t\t\tconst input = this.buildResolvedTurn(message, resolved, attachments, outputTarget);\n\t\t\tthis.engine.dispatch(input);\n\t\t} catch (error) {\n\t\t\tconst messageText = error instanceof Error ? error.message : String(error);\n\t\t\tlog.logError(`Failed to normalize Slack inbound message ${message.ts}`, messageText);\n\t\t\tawait this.sink.postMessage(\n\t\t\t\t{\n\t\t\t\t\tchannelId: message.channelId,\n\t\t\t\t\tthreadId: message.threadTs || (message.type === \"mention\" ? message.ts : undefined),\n\t\t\t\t},\n\t\t\t\t`_Gateway error: ${messageText}_`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate buildResolvedTurn(\n\t\tmessage: SlackInboundMessage,\n\t\tresolved: ResolvedSlackRoute,\n\t\tattachments: AttachmentRef[],\n\t\toutput: TransportOutputTarget,\n\t): BeeResolvedTurn {\n\t\tif (!this.teamId || !this.botUserId) {\n\t\t\tthrow new Error(\"Missing Slack gateway identity\");\n\t\t}\n\n\t\treturn {\n\t\t\tsessionId: resolved.sessionId,\n\t\t\tthreadId: resolved.threadTs,\n\t\t\tworker: resolved.route.worker,\n\t\t\tconversation: {\n\t\t\t\ttransport: \"slack\",\n\t\t\t\tconversationId: resolved.conversationId,\n\t\t\t},\n\t\t\tactor: {\n\t\t\t\tuserId: message.userId,\n\t\t\t\tuserName: message.userName,\n\t\t\t\tdisplayName: message.displayName,\n\t\t\t},\n\t\t\tmessage: {\n\t\t\t\ttext: message.text,\n\t\t\t},\n\t\t\tattachments,\n\t\t\toutput,\n\t\t};\n\t}\n\n\tprivate buildOutputTarget(message: SlackInboundMessage, resolved: ResolvedSlackRoute): TransportOutputTarget {\n\t\treturn {\n\t\t\tchannelId: message.channelId,\n\t\t\tthreadId: resolved.threadTs || (message.type === \"mention\" ? message.ts : undefined),\n\t\t};\n\t}\n\n\tprivate async downloadAttachments(\n\t\tresolved: ResolvedSlackRoute,\n\t\tmessage: SlackInboundMessage,\n\t): Promise<AttachmentRef[]> {\n\t\tconst files = message.files || [];\n\t\tif (files.length === 0) return [];\n\n\t\tconst attachments: AttachmentRef[] = [];\n\t\tfor (const file of files) {\n\t\t\tconst url = file.url_private_download || file.url_private;\n\t\t\tif (!url || !file.name) continue;\n\t\t\tconst response = await fetch(url, {\n\t\t\t\theaders: { authorization: `Bearer ${this.config.botToken}` },\n\t\t\t});\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Failed to download Slack attachment ${file.name}: ${response.status}`);\n\t\t\t}\n\t\t\tconst bytes = new Uint8Array(await response.arrayBuffer());\n\t\t\tattachments.push(\n\t\t\t\tawait this.blobStore.put({\n\t\t\t\t\tnamespace: `incoming/slack/${resolved.sessionId}`,\n\t\t\t\t\tname: file.name,\n\t\t\t\t\ttitle: file.name,\n\t\t\t\t\tmimeType: file.mimetype,\n\t\t\t\t\tdata: bytes,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn attachments;\n\t}\n\n\tprivate async fetchUsers(): Promise<void> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.webClient.users.list({ limit: 200, cursor });\n\t\t\tconst members = result.members as\n\t\t\t\t| Array<{ id?: string; name?: string; real_name?: string; deleted?: boolean }>\n\t\t\t\t| undefined;\n\t\t\tif (members) {\n\t\t\t\tfor (const user of members) {\n\t\t\t\t\tif (user.id && user.name && !user.deleted) {\n\t\t\t\t\t\tthis.users.set(user.id, {\n\t\t\t\t\t\t\tid: user.id,\n\t\t\t\t\t\t\tuserName: user.name,\n\t\t\t\t\t\t\tdisplayName: user.real_name || user.name,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t} while (cursor);\n\t}\n\n\tprivate async fetchChannels(): Promise<void> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.webClient.conversations.list({\n\t\t\t\ttypes: \"public_channel,private_channel,im\",\n\t\t\t\texclude_archived: true,\n\t\t\t\tlimit: 200,\n\t\t\t\tcursor,\n\t\t\t});\n\t\t\tconst channels = result.channels as Array<{ id?: string; name?: string; user?: string }> | undefined;\n\t\t\tif (channels) {\n\t\t\t\tfor (const channel of channels) {\n\t\t\t\t\tif (!channel.id) continue;\n\t\t\t\t\tif (channel.name) {\n\t\t\t\t\t\tthis.channels.set(channel.id, { id: channel.id, name: channel.name });\n\t\t\t\t\t} else if (channel.user && this.users.has(channel.user)) {\n\t\t\t\t\t\tthis.channels.set(channel.id, {\n\t\t\t\t\t\t\tid: channel.id,\n\t\t\t\t\t\t\tname: `DM:${this.users.get(channel.user)!.userName}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t} while (cursor);\n\t}\n}\n\nfunction slackErrorCode(error: unknown): string | undefined {\n\tif (!error || typeof error !== \"object\" || !(\"data\" in error)) return undefined;\n\tconst data = error.data;\n\tif (!data || typeof data !== \"object\" || !(\"error\" in data)) return undefined;\n\treturn typeof data.error === \"string\" ? data.error : undefined;\n}\n\nfunction isSlackHistoryPermissionError(errorCode: string | undefined): boolean {\n\treturn errorCode === \"missing_scope\" || errorCode === \"no_permission\" || errorCode === \"not_allowed_token_type\";\n}\n\nexport async function startGatewayFromEnv(configPath?: string): Promise<void> {\n\tconst config = loadConfig(configPath);\n\tconst gateway = new SlackGateway(config);\n\tawait gateway.start();\n}\n"]}
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAiC,kBAAkB,EAAuB,MAAM,YAAY,CAAC;AAazG,qBAAa,YAAY;IAeZ,OAAO,CAAC,MAAM;IAd1B,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,MAAM,CAAyC;IACvD,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,6BAA6B,CAAS;IAE9C,YAAoB,MAAM,EAAE,kBAAkB,EAa7C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB3B;IAED,OAAO,CAAC,kBAAkB;IAqD1B,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,kBAAkB;YAmEZ,cAAc;IA0C5B,OAAO,CAAC,iBAAiB;IAoCzB,OAAO,CAAC,iBAAiB;YAOX,mBAAmB;YA+BnB,UAAU;YAsBV,aAAa;CA0B3B;AAaD,wBAAsB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5E","sourcesContent":["import {\n\ttype AttachmentRef,\n\tBeeGatewayEngine,\n\ttype BeeResolvedTurn,\n\ttype BeeWorkerClient,\n\ttype BlobStore,\n\tcreateNatsBeeClient,\n\tLocalFileBlobStore,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { SocketModeClient } from \"@slack/socket-mode\";\nimport { WebClient } from \"@slack/web-api\";\nimport { join } from \"path\";\nimport { loadConfig } from \"./config.js\";\nimport { createHandoffServer, SlackHandoffController } from \"./handoff.js\";\nimport { HandoffReplyCache } from \"./handoff-reply-cache.js\";\nimport * as log from \"./log.js\";\nimport { resolveRoute, resolveStreamingPreference } from \"./router.js\";\nimport { SlackSink } from \"./slack-sink.js\";\nimport type { ResolvedSlackRoute, SlackFile, SlackGatewayConfig, SlackInboundMessage } from \"./types.js\";\n\ninterface SlackUser {\n\tid: string;\n\tuserName: string;\n\tdisplayName: string;\n}\n\ninterface SlackChannel {\n\tid: string;\n\tname: string;\n}\n\nexport class SlackGateway {\n\tprivate socketClient: SocketModeClient;\n\tprivate webClient: WebClient;\n\tprivate users = new Map<string, SlackUser>();\n\tprivate channels = new Map<string, SlackChannel>();\n\tprivate botUserId: string | null = null;\n\tprivate teamId: string | null = null;\n\tprivate teamName: string | null = null;\n\tprivate blobStore: BlobStore;\n\tprivate sink: SlackSink;\n\tprivate engine: BeeGatewayEngine<string> | null = null;\n\tprivate workerClient: BeeWorkerClient | null = null;\n\tprivate handoffReplyCache = new HandoffReplyCache();\n\tprivate slackThreadHistoryUnavailable = false;\n\n\tconstructor(private config: SlackGatewayConfig) {\n\t\tthis.socketClient = new SocketModeClient({ appToken: config.appToken });\n\t\tthis.webClient = new WebClient(config.botToken);\n\t\tthis.blobStore = new LocalFileBlobStore(\n\t\t\tprocess.env.BEE_SLACK_BLOB_STORE_ROOT ||\n\t\t\t\tprocess.env.BEE_BLOB_STORE_ROOT ||\n\t\t\t\tprocess.env.HUDAI_BLOB_STORE_ROOT ||\n\t\t\t\tjoin(process.cwd(), \".bee-blob-store\"),\n\t\t);\n\t\tthis.sink = new SlackSink(this.webClient, this.blobStore, {\n\t\t\tposted: (target, ref, text) => this.handoffReplyCache.recordPosted(target, ref, text, this.botDisplayName()),\n\t\t\tupdated: (target, ref, text) => this.handoffReplyCache.recordUpdated(target, ref, text, this.botDisplayName()),\n\t\t});\n\t}\n\n\tasync start(): Promise<void> {\n\t\tthis.workerClient = await createNatsBeeClient(this.config.nats);\n\t\tthis.engine = new BeeGatewayEngine({\n\t\t\tsink: this.sink,\n\t\t\tworkerClient: this.workerClient,\n\t\t\tlogger: {\n\t\t\t\tinfo: log.logInfo,\n\t\t\t\twarn: log.logWarning,\n\t\t\t\terror: log.logError,\n\t\t\t},\n\t\t});\n\t\tconst auth = await this.webClient.auth.test();\n\t\tthis.botUserId = auth.user_id as string;\n\t\tthis.teamId = auth.team_id as string;\n\t\tthis.teamName = (auth.team as string | undefined) || null;\n\t\tawait Promise.all([this.fetchUsers(), this.fetchChannels()]);\n\t\tthis.startHandoffServer();\n\t\tthis.setupEventHandlers();\n\t\tawait this.socketClient.start();\n\t\tlog.logInfo(\n\t\t\t`Connected as ${this.botUserId} in ${this.teamId}; loaded ${this.channels.size} channels and ${this.users.size} users`,\n\t\t);\n\t}\n\n\tprivate startHandoffServer(): void {\n\t\tconst handoffConfig = this.config.handoff;\n\t\tif (!handoffConfig?.enabled) return;\n\t\tif (!this.teamId || !this.engine) throw new Error(\"Gateway must be initialized before handoff server startup\");\n\t\tconst controller = new SlackHandoffController(handoffConfig, {\n\t\t\tteamId: this.teamId,\n\t\t\tpostRootMessage: async (channelId, text) => this.sink.postMessage({ channelId }, text),\n\t\t\tgetPermalink: async (channelId, messageTs) => {\n\t\t\t\tconst result = await this.webClient.chat.getPermalink({ channel: channelId, message_ts: messageTs });\n\t\t\t\tif (!result.permalink) throw new Error(\"Slack did not return a permalink\");\n\t\t\t\treturn result.permalink;\n\t\t\t},\n\t\t\tdispatch: (input) => this.engine!.dispatch(input),\n\t\t\tgetReplies: async (channelId, threadTs) => {\n\t\t\t\tif (this.slackThreadHistoryUnavailable) return this.handoffReplyCache.replies(channelId, threadTs);\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.webClient.conversations.replies({\n\t\t\t\t\t\tchannel: channelId,\n\t\t\t\t\t\tts: threadTs,\n\t\t\t\t\t\tlimit: 100,\n\t\t\t\t\t});\n\t\t\t\t\treturn (result.messages || []).map((message) => {\n\t\t\t\t\t\tconst typed = message as typeof message & {\n\t\t\t\t\t\t\tbot_id?: string;\n\t\t\t\t\t\t\tbot_profile?: { name?: string };\n\t\t\t\t\t\t\tusername?: string;\n\t\t\t\t\t\t\tthread_ts?: string;\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tts: String(typed.ts || \"\"),\n\t\t\t\t\t\t\tthreadTs: typed.thread_ts,\n\t\t\t\t\t\t\ttext: typed.text || \"\",\n\t\t\t\t\t\t\tauthor: typed.user\n\t\t\t\t\t\t\t\t? this.users.get(typed.user)?.displayName || this.users.get(typed.user)?.userName || typed.user\n\t\t\t\t\t\t\t\t: typed.bot_profile?.name || typed.username || \"Bot\",\n\t\t\t\t\t\t\tisBot: Boolean(typed.bot_id),\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst errorCode = slackErrorCode(error);\n\t\t\t\t\tif (!isSlackHistoryPermissionError(errorCode)) throw error;\n\t\t\t\t\tthis.slackThreadHistoryUnavailable = true;\n\t\t\t\t\tlog.logWarning(`Slack thread history unavailable (${errorCode}); using outbound Bee reply cache`);\n\t\t\t\t\treturn this.handoffReplyCache.replies(channelId, threadTs);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tconst server = createHandoffServer(controller);\n\t\tconst host = handoffConfig.host || \"0.0.0.0\";\n\t\tconst port = handoffConfig.port || 8080;\n\t\tserver.listen(port, host, () => log.logInfo(`bee-slack handoff listening on http://${host}:${port}`));\n\t}\n\n\tprivate botDisplayName(): string {\n\t\tif (!this.botUserId) return \"Bee\";\n\t\tconst bot = this.users.get(this.botUserId);\n\t\treturn bot?.displayName || bot?.userName || \"Bee\";\n\t}\n\n\tprivate setupEventHandlers(): void {\n\t\tthis.socketClient.on(\"app_mention\", async ({ event, ack, body }) => {\n\t\t\tawait ack();\n\t\t\tconst e = event as {\n\t\t\t\ttext: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser: string;\n\t\t\t\tts: string;\n\t\t\t\tthread_ts?: string;\n\t\t\t\tfiles?: SlackFile[];\n\t\t\t};\n\n\t\t\tif (e.channel.startsWith(\"D\")) return;\n\n\t\t\tawait this.enqueueInbound({\n\t\t\t\ttype: \"mention\",\n\t\t\t\tchannelId: e.channel,\n\t\t\t\tchannelName: this.channels.get(e.channel)?.name,\n\t\t\t\tthreadTs: e.thread_ts,\n\t\t\t\tts: e.ts,\n\t\t\t\tuserId: e.user,\n\t\t\t\tteamId: (event as typeof e & { team?: string }).team || (body as { team_id?: string }).team_id,\n\t\t\t\tuserName: this.users.get(e.user)?.userName,\n\t\t\t\tdisplayName: this.users.get(e.user)?.displayName,\n\t\t\t\ttext: e.text.replace(/<@[A-Z0-9]+>/gi, \"\").trim(),\n\t\t\t\tfiles: e.files,\n\t\t\t});\n\t\t});\n\n\t\tthis.socketClient.on(\"message\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\t\t\tconst e = event as {\n\t\t\t\ttext?: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser?: string;\n\t\t\t\tts: string;\n\t\t\t\tthread_ts?: string;\n\t\t\t\tchannel_type?: string;\n\t\t\t\tsubtype?: string;\n\t\t\t\tbot_id?: string;\n\t\t\t\tfiles?: SlackFile[];\n\t\t\t};\n\n\t\t\tif (e.bot_id || !e.user || e.user === this.botUserId) return;\n\t\t\tif (e.subtype !== undefined && e.subtype !== \"file_share\") return;\n\t\t\tif (!e.text && (!e.files || e.files.length === 0)) return;\n\n\t\t\tconst isDM = e.channel_type === \"im\";\n\t\t\tconst isBotMention = !!this.botUserId && e.text?.includes(`<@${this.botUserId}>`);\n\t\t\tif (!isDM && isBotMention) return;\n\t\t\tif (!isDM) return;\n\n\t\t\tawait this.enqueueInbound({\n\t\t\t\ttype: \"dm\",\n\t\t\t\tchannelId: e.channel,\n\t\t\t\tchannelName: this.channels.get(e.channel)?.name,\n\t\t\t\tthreadTs: e.thread_ts,\n\t\t\t\tts: e.ts,\n\t\t\t\tuserId: e.user,\n\t\t\t\tuserName: this.users.get(e.user)?.userName,\n\t\t\t\tdisplayName: this.users.get(e.user)?.displayName,\n\t\t\t\ttext: (e.text || \"\").replace(/<@[A-Z0-9]+>/gi, \"\").trim(),\n\t\t\t\tfiles: e.files,\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate async enqueueInbound(message: SlackInboundMessage): Promise<void> {\n\t\tif (!this.botUserId || !this.teamId) {\n\t\t\tthrow new Error(\"Gateway has not been initialized\");\n\t\t}\n\t\tif (!this.engine) {\n\t\t\tthrow new Error(\"Gateway engine has not been initialized\");\n\t\t}\n\n\t\ttry {\n\t\t\tconst resolved = resolveRoute(this.config.routes, message, {\n\t\t\t\tbotUserId: this.botUserId,\n\t\t\t\tteamId: this.teamId,\n\t\t\t\tteamName: this.teamName || undefined,\n\t\t\t});\n\t\t\tif (!resolved) {\n\t\t\t\tlog.logWarning(`No route for channel ${message.channelId}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst outputTarget = this.buildOutputTarget(message, resolved);\n\t\t\tif (message.text.trim().toLowerCase() === \"stop\") {\n\t\t\t\tconst stopped = await this.engine.stopActiveRun(resolved.sessionId);\n\t\t\t\tawait this.sink.postMessage(outputTarget, stopped ? \"Stopping active run.\" : \"Nothing running.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst attachments = await this.downloadAttachments(resolved, message);\n\t\t\tconst input = this.buildResolvedTurn(message, resolved, attachments, outputTarget);\n\t\t\tthis.engine.dispatch(input);\n\t\t} catch (error) {\n\t\t\tconst messageText = error instanceof Error ? error.message : String(error);\n\t\t\tlog.logError(`Failed to normalize Slack inbound message ${message.ts}`, messageText);\n\t\t\tawait this.sink.postMessage(\n\t\t\t\t{\n\t\t\t\t\tchannelId: message.channelId,\n\t\t\t\t\tthreadId: message.threadTs || (message.type === \"mention\" ? message.ts : undefined),\n\t\t\t\t},\n\t\t\t\t`_Gateway error: ${messageText}_`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate buildResolvedTurn(\n\t\tmessage: SlackInboundMessage,\n\t\tresolved: ResolvedSlackRoute,\n\t\tattachments: AttachmentRef[],\n\t\toutput: TransportOutputTarget,\n\t): BeeResolvedTurn {\n\t\tif (!this.teamId || !this.botUserId) {\n\t\t\tthrow new Error(\"Missing Slack gateway identity\");\n\t\t}\n\n\t\treturn {\n\t\t\tsessionId: resolved.sessionId,\n\t\t\tthreadId: resolved.threadTs,\n\t\t\tworker: resolved.route.worker,\n\t\t\tconversation: {\n\t\t\t\ttransport: \"slack\",\n\t\t\t\tconversationId: resolved.conversationId,\n\t\t\t},\n\t\t\tactor: {\n\t\t\t\tuserId: message.userId,\n\t\t\t\tuserName: message.userName,\n\t\t\t\tdisplayName: message.displayName,\n\t\t\t},\n\t\t\tmessage: {\n\t\t\t\ttext: message.text,\n\t\t\t},\n\t\t\tattachments,\n\t\t\tstreaming: resolveStreamingPreference(resolved.route, message, {\n\t\t\t\tbotUserId: this.botUserId,\n\t\t\t\tteamId: this.teamId,\n\t\t\t\tteamName: this.teamName || undefined,\n\t\t\t}),\n\t\t\toutput,\n\t\t};\n\t}\n\n\tprivate buildOutputTarget(message: SlackInboundMessage, resolved: ResolvedSlackRoute): TransportOutputTarget {\n\t\treturn {\n\t\t\tchannelId: message.channelId,\n\t\t\tthreadId: resolved.threadTs || (message.type === \"mention\" ? message.ts : undefined),\n\t\t};\n\t}\n\n\tprivate async downloadAttachments(\n\t\tresolved: ResolvedSlackRoute,\n\t\tmessage: SlackInboundMessage,\n\t): Promise<AttachmentRef[]> {\n\t\tconst files = message.files || [];\n\t\tif (files.length === 0) return [];\n\n\t\tconst attachments: AttachmentRef[] = [];\n\t\tfor (const file of files) {\n\t\t\tconst url = file.url_private_download || file.url_private;\n\t\t\tif (!url || !file.name) continue;\n\t\t\tconst response = await fetch(url, {\n\t\t\t\theaders: { authorization: `Bearer ${this.config.botToken}` },\n\t\t\t});\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Failed to download Slack attachment ${file.name}: ${response.status}`);\n\t\t\t}\n\t\t\tconst bytes = new Uint8Array(await response.arrayBuffer());\n\t\t\tattachments.push(\n\t\t\t\tawait this.blobStore.put({\n\t\t\t\t\tnamespace: `incoming/slack/${resolved.sessionId}`,\n\t\t\t\t\tname: file.name,\n\t\t\t\t\ttitle: file.name,\n\t\t\t\t\tmimeType: file.mimetype,\n\t\t\t\t\tdata: bytes,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn attachments;\n\t}\n\n\tprivate async fetchUsers(): Promise<void> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.webClient.users.list({ limit: 200, cursor });\n\t\t\tconst members = result.members as\n\t\t\t\t| Array<{ id?: string; name?: string; real_name?: string; deleted?: boolean }>\n\t\t\t\t| undefined;\n\t\t\tif (members) {\n\t\t\t\tfor (const user of members) {\n\t\t\t\t\tif (user.id && user.name && !user.deleted) {\n\t\t\t\t\t\tthis.users.set(user.id, {\n\t\t\t\t\t\t\tid: user.id,\n\t\t\t\t\t\t\tuserName: user.name,\n\t\t\t\t\t\t\tdisplayName: user.real_name || user.name,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t} while (cursor);\n\t}\n\n\tprivate async fetchChannels(): Promise<void> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.webClient.conversations.list({\n\t\t\t\ttypes: \"public_channel,private_channel,im\",\n\t\t\t\texclude_archived: true,\n\t\t\t\tlimit: 200,\n\t\t\t\tcursor,\n\t\t\t});\n\t\t\tconst channels = result.channels as Array<{ id?: string; name?: string; user?: string }> | undefined;\n\t\t\tif (channels) {\n\t\t\t\tfor (const channel of channels) {\n\t\t\t\t\tif (!channel.id) continue;\n\t\t\t\t\tif (channel.name) {\n\t\t\t\t\t\tthis.channels.set(channel.id, { id: channel.id, name: channel.name });\n\t\t\t\t\t} else if (channel.user && this.users.has(channel.user)) {\n\t\t\t\t\t\tthis.channels.set(channel.id, {\n\t\t\t\t\t\t\tid: channel.id,\n\t\t\t\t\t\t\tname: `DM:${this.users.get(channel.user)!.userName}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t} while (cursor);\n\t}\n}\n\nfunction slackErrorCode(error: unknown): string | undefined {\n\tif (!error || typeof error !== \"object\" || !(\"data\" in error)) return undefined;\n\tconst data = error.data;\n\tif (!data || typeof data !== \"object\" || !(\"error\" in data)) return undefined;\n\treturn typeof data.error === \"string\" ? data.error : undefined;\n}\n\nfunction isSlackHistoryPermissionError(errorCode: string | undefined): boolean {\n\treturn errorCode === \"missing_scope\" || errorCode === \"no_permission\" || errorCode === \"not_allowed_token_type\";\n}\n\nexport async function startGatewayFromEnv(configPath?: string): Promise<void> {\n\tconst config = loadConfig(configPath);\n\tconst gateway = new SlackGateway(config);\n\tawait gateway.start();\n}\n"]}
package/dist/gateway.js CHANGED
@@ -6,7 +6,7 @@ import { loadConfig } from "./config.js";
6
6
  import { createHandoffServer, SlackHandoffController } from "./handoff.js";
7
7
  import { HandoffReplyCache } from "./handoff-reply-cache.js";
8
8
  import * as log from "./log.js";
9
- import { resolveRoute } from "./router.js";
9
+ import { resolveRoute, resolveStreamingPreference } from "./router.js";
10
10
  import { SlackSink } from "./slack-sink.js";
11
11
  export class SlackGateway {
12
12
  config;
@@ -117,7 +117,7 @@ export class SlackGateway {
117
117
  return bot?.displayName || bot?.userName || "Bee";
118
118
  }
119
119
  setupEventHandlers() {
120
- this.socketClient.on("app_mention", async ({ event, ack }) => {
120
+ this.socketClient.on("app_mention", async ({ event, ack, body }) => {
121
121
  await ack();
122
122
  const e = event;
123
123
  if (e.channel.startsWith("D"))
@@ -129,6 +129,7 @@ export class SlackGateway {
129
129
  threadTs: e.thread_ts,
130
130
  ts: e.ts,
131
131
  userId: e.user,
132
+ teamId: event.team || body.team_id,
132
133
  userName: this.users.get(e.user)?.userName,
133
134
  displayName: this.users.get(e.user)?.displayName,
134
135
  text: e.text.replace(/<@[A-Z0-9]+>/gi, "").trim(),
@@ -221,6 +222,11 @@ export class SlackGateway {
221
222
  text: message.text,
222
223
  },
223
224
  attachments,
225
+ streaming: resolveStreamingPreference(resolved.route, message, {
226
+ botUserId: this.botUserId,
227
+ teamId: this.teamId,
228
+ teamName: this.teamName || undefined,
229
+ }),
224
230
  output,
225
231
  };
226
232
  }
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype AttachmentRef,\n\tBeeGatewayEngine,\n\ttype BeeResolvedTurn,\n\ttype BeeWorkerClient,\n\ttype BlobStore,\n\tcreateNatsBeeClient,\n\tLocalFileBlobStore,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { SocketModeClient } from \"@slack/socket-mode\";\nimport { WebClient } from \"@slack/web-api\";\nimport { join } from \"path\";\nimport { loadConfig } from \"./config.js\";\nimport { createHandoffServer, SlackHandoffController } from \"./handoff.js\";\nimport { HandoffReplyCache } from \"./handoff-reply-cache.js\";\nimport * as log from \"./log.js\";\nimport { resolveRoute } from \"./router.js\";\nimport { SlackSink } from \"./slack-sink.js\";\nimport type { ResolvedSlackRoute, SlackFile, SlackGatewayConfig, SlackInboundMessage } from \"./types.js\";\n\ninterface SlackUser {\n\tid: string;\n\tuserName: string;\n\tdisplayName: string;\n}\n\ninterface SlackChannel {\n\tid: string;\n\tname: string;\n}\n\nexport class SlackGateway {\n\tprivate socketClient: SocketModeClient;\n\tprivate webClient: WebClient;\n\tprivate users = new Map<string, SlackUser>();\n\tprivate channels = new Map<string, SlackChannel>();\n\tprivate botUserId: string | null = null;\n\tprivate teamId: string | null = null;\n\tprivate teamName: string | null = null;\n\tprivate blobStore: BlobStore;\n\tprivate sink: SlackSink;\n\tprivate engine: BeeGatewayEngine<string> | null = null;\n\tprivate workerClient: BeeWorkerClient | null = null;\n\tprivate handoffReplyCache = new HandoffReplyCache();\n\tprivate slackThreadHistoryUnavailable = false;\n\n\tconstructor(private config: SlackGatewayConfig) {\n\t\tthis.socketClient = new SocketModeClient({ appToken: config.appToken });\n\t\tthis.webClient = new WebClient(config.botToken);\n\t\tthis.blobStore = new LocalFileBlobStore(\n\t\t\tprocess.env.BEE_SLACK_BLOB_STORE_ROOT ||\n\t\t\t\tprocess.env.BEE_BLOB_STORE_ROOT ||\n\t\t\t\tprocess.env.HUDAI_BLOB_STORE_ROOT ||\n\t\t\t\tjoin(process.cwd(), \".bee-blob-store\"),\n\t\t);\n\t\tthis.sink = new SlackSink(this.webClient, this.blobStore, {\n\t\t\tposted: (target, ref, text) => this.handoffReplyCache.recordPosted(target, ref, text, this.botDisplayName()),\n\t\t\tupdated: (target, ref, text) => this.handoffReplyCache.recordUpdated(target, ref, text, this.botDisplayName()),\n\t\t});\n\t}\n\n\tasync start(): Promise<void> {\n\t\tthis.workerClient = await createNatsBeeClient(this.config.nats);\n\t\tthis.engine = new BeeGatewayEngine({\n\t\t\tsink: this.sink,\n\t\t\tworkerClient: this.workerClient,\n\t\t\tlogger: {\n\t\t\t\tinfo: log.logInfo,\n\t\t\t\twarn: log.logWarning,\n\t\t\t\terror: log.logError,\n\t\t\t},\n\t\t});\n\t\tconst auth = await this.webClient.auth.test();\n\t\tthis.botUserId = auth.user_id as string;\n\t\tthis.teamId = auth.team_id as string;\n\t\tthis.teamName = (auth.team as string | undefined) || null;\n\t\tawait Promise.all([this.fetchUsers(), this.fetchChannels()]);\n\t\tthis.startHandoffServer();\n\t\tthis.setupEventHandlers();\n\t\tawait this.socketClient.start();\n\t\tlog.logInfo(\n\t\t\t`Connected as ${this.botUserId} in ${this.teamId}; loaded ${this.channels.size} channels and ${this.users.size} users`,\n\t\t);\n\t}\n\n\tprivate startHandoffServer(): void {\n\t\tconst handoffConfig = this.config.handoff;\n\t\tif (!handoffConfig?.enabled) return;\n\t\tif (!this.teamId || !this.engine) throw new Error(\"Gateway must be initialized before handoff server startup\");\n\t\tconst controller = new SlackHandoffController(handoffConfig, {\n\t\t\tteamId: this.teamId,\n\t\t\tpostRootMessage: async (channelId, text) => this.sink.postMessage({ channelId }, text),\n\t\t\tgetPermalink: async (channelId, messageTs) => {\n\t\t\t\tconst result = await this.webClient.chat.getPermalink({ channel: channelId, message_ts: messageTs });\n\t\t\t\tif (!result.permalink) throw new Error(\"Slack did not return a permalink\");\n\t\t\t\treturn result.permalink;\n\t\t\t},\n\t\t\tdispatch: (input) => this.engine!.dispatch(input),\n\t\t\tgetReplies: async (channelId, threadTs) => {\n\t\t\t\tif (this.slackThreadHistoryUnavailable) return this.handoffReplyCache.replies(channelId, threadTs);\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.webClient.conversations.replies({\n\t\t\t\t\t\tchannel: channelId,\n\t\t\t\t\t\tts: threadTs,\n\t\t\t\t\t\tlimit: 100,\n\t\t\t\t\t});\n\t\t\t\t\treturn (result.messages || []).map((message) => {\n\t\t\t\t\t\tconst typed = message as typeof message & {\n\t\t\t\t\t\t\tbot_id?: string;\n\t\t\t\t\t\t\tbot_profile?: { name?: string };\n\t\t\t\t\t\t\tusername?: string;\n\t\t\t\t\t\t\tthread_ts?: string;\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tts: String(typed.ts || \"\"),\n\t\t\t\t\t\t\tthreadTs: typed.thread_ts,\n\t\t\t\t\t\t\ttext: typed.text || \"\",\n\t\t\t\t\t\t\tauthor: typed.user\n\t\t\t\t\t\t\t\t? this.users.get(typed.user)?.displayName || this.users.get(typed.user)?.userName || typed.user\n\t\t\t\t\t\t\t\t: typed.bot_profile?.name || typed.username || \"Bot\",\n\t\t\t\t\t\t\tisBot: Boolean(typed.bot_id),\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst errorCode = slackErrorCode(error);\n\t\t\t\t\tif (!isSlackHistoryPermissionError(errorCode)) throw error;\n\t\t\t\t\tthis.slackThreadHistoryUnavailable = true;\n\t\t\t\t\tlog.logWarning(`Slack thread history unavailable (${errorCode}); using outbound Bee reply cache`);\n\t\t\t\t\treturn this.handoffReplyCache.replies(channelId, threadTs);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tconst server = createHandoffServer(controller);\n\t\tconst host = handoffConfig.host || \"0.0.0.0\";\n\t\tconst port = handoffConfig.port || 8080;\n\t\tserver.listen(port, host, () => log.logInfo(`bee-slack handoff listening on http://${host}:${port}`));\n\t}\n\n\tprivate botDisplayName(): string {\n\t\tif (!this.botUserId) return \"Bee\";\n\t\tconst bot = this.users.get(this.botUserId);\n\t\treturn bot?.displayName || bot?.userName || \"Bee\";\n\t}\n\n\tprivate setupEventHandlers(): void {\n\t\tthis.socketClient.on(\"app_mention\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\t\t\tconst e = event as {\n\t\t\t\ttext: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser: string;\n\t\t\t\tts: string;\n\t\t\t\tthread_ts?: string;\n\t\t\t\tfiles?: SlackFile[];\n\t\t\t};\n\n\t\t\tif (e.channel.startsWith(\"D\")) return;\n\n\t\t\tawait this.enqueueInbound({\n\t\t\t\ttype: \"mention\",\n\t\t\t\tchannelId: e.channel,\n\t\t\t\tchannelName: this.channels.get(e.channel)?.name,\n\t\t\t\tthreadTs: e.thread_ts,\n\t\t\t\tts: e.ts,\n\t\t\t\tuserId: e.user,\n\t\t\t\tuserName: this.users.get(e.user)?.userName,\n\t\t\t\tdisplayName: this.users.get(e.user)?.displayName,\n\t\t\t\ttext: e.text.replace(/<@[A-Z0-9]+>/gi, \"\").trim(),\n\t\t\t\tfiles: e.files,\n\t\t\t});\n\t\t});\n\n\t\tthis.socketClient.on(\"message\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\t\t\tconst e = event as {\n\t\t\t\ttext?: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser?: string;\n\t\t\t\tts: string;\n\t\t\t\tthread_ts?: string;\n\t\t\t\tchannel_type?: string;\n\t\t\t\tsubtype?: string;\n\t\t\t\tbot_id?: string;\n\t\t\t\tfiles?: SlackFile[];\n\t\t\t};\n\n\t\t\tif (e.bot_id || !e.user || e.user === this.botUserId) return;\n\t\t\tif (e.subtype !== undefined && e.subtype !== \"file_share\") return;\n\t\t\tif (!e.text && (!e.files || e.files.length === 0)) return;\n\n\t\t\tconst isDM = e.channel_type === \"im\";\n\t\t\tconst isBotMention = !!this.botUserId && e.text?.includes(`<@${this.botUserId}>`);\n\t\t\tif (!isDM && isBotMention) return;\n\t\t\tif (!isDM) return;\n\n\t\t\tawait this.enqueueInbound({\n\t\t\t\ttype: \"dm\",\n\t\t\t\tchannelId: e.channel,\n\t\t\t\tchannelName: this.channels.get(e.channel)?.name,\n\t\t\t\tthreadTs: e.thread_ts,\n\t\t\t\tts: e.ts,\n\t\t\t\tuserId: e.user,\n\t\t\t\tuserName: this.users.get(e.user)?.userName,\n\t\t\t\tdisplayName: this.users.get(e.user)?.displayName,\n\t\t\t\ttext: (e.text || \"\").replace(/<@[A-Z0-9]+>/gi, \"\").trim(),\n\t\t\t\tfiles: e.files,\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate async enqueueInbound(message: SlackInboundMessage): Promise<void> {\n\t\tif (!this.botUserId || !this.teamId) {\n\t\t\tthrow new Error(\"Gateway has not been initialized\");\n\t\t}\n\t\tif (!this.engine) {\n\t\t\tthrow new Error(\"Gateway engine has not been initialized\");\n\t\t}\n\n\t\ttry {\n\t\t\tconst resolved = resolveRoute(this.config.routes, message, {\n\t\t\t\tbotUserId: this.botUserId,\n\t\t\t\tteamId: this.teamId,\n\t\t\t\tteamName: this.teamName || undefined,\n\t\t\t});\n\t\t\tif (!resolved) {\n\t\t\t\tlog.logWarning(`No route for channel ${message.channelId}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst outputTarget = this.buildOutputTarget(message, resolved);\n\t\t\tif (message.text.trim().toLowerCase() === \"stop\") {\n\t\t\t\tconst stopped = await this.engine.stopActiveRun(resolved.sessionId);\n\t\t\t\tawait this.sink.postMessage(outputTarget, stopped ? \"Stopping active run.\" : \"Nothing running.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst attachments = await this.downloadAttachments(resolved, message);\n\t\t\tconst input = this.buildResolvedTurn(message, resolved, attachments, outputTarget);\n\t\t\tthis.engine.dispatch(input);\n\t\t} catch (error) {\n\t\t\tconst messageText = error instanceof Error ? error.message : String(error);\n\t\t\tlog.logError(`Failed to normalize Slack inbound message ${message.ts}`, messageText);\n\t\t\tawait this.sink.postMessage(\n\t\t\t\t{\n\t\t\t\t\tchannelId: message.channelId,\n\t\t\t\t\tthreadId: message.threadTs || (message.type === \"mention\" ? message.ts : undefined),\n\t\t\t\t},\n\t\t\t\t`_Gateway error: ${messageText}_`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate buildResolvedTurn(\n\t\tmessage: SlackInboundMessage,\n\t\tresolved: ResolvedSlackRoute,\n\t\tattachments: AttachmentRef[],\n\t\toutput: TransportOutputTarget,\n\t): BeeResolvedTurn {\n\t\tif (!this.teamId || !this.botUserId) {\n\t\t\tthrow new Error(\"Missing Slack gateway identity\");\n\t\t}\n\n\t\treturn {\n\t\t\tsessionId: resolved.sessionId,\n\t\t\tthreadId: resolved.threadTs,\n\t\t\tworker: resolved.route.worker,\n\t\t\tconversation: {\n\t\t\t\ttransport: \"slack\",\n\t\t\t\tconversationId: resolved.conversationId,\n\t\t\t},\n\t\t\tactor: {\n\t\t\t\tuserId: message.userId,\n\t\t\t\tuserName: message.userName,\n\t\t\t\tdisplayName: message.displayName,\n\t\t\t},\n\t\t\tmessage: {\n\t\t\t\ttext: message.text,\n\t\t\t},\n\t\t\tattachments,\n\t\t\toutput,\n\t\t};\n\t}\n\n\tprivate buildOutputTarget(message: SlackInboundMessage, resolved: ResolvedSlackRoute): TransportOutputTarget {\n\t\treturn {\n\t\t\tchannelId: message.channelId,\n\t\t\tthreadId: resolved.threadTs || (message.type === \"mention\" ? message.ts : undefined),\n\t\t};\n\t}\n\n\tprivate async downloadAttachments(\n\t\tresolved: ResolvedSlackRoute,\n\t\tmessage: SlackInboundMessage,\n\t): Promise<AttachmentRef[]> {\n\t\tconst files = message.files || [];\n\t\tif (files.length === 0) return [];\n\n\t\tconst attachments: AttachmentRef[] = [];\n\t\tfor (const file of files) {\n\t\t\tconst url = file.url_private_download || file.url_private;\n\t\t\tif (!url || !file.name) continue;\n\t\t\tconst response = await fetch(url, {\n\t\t\t\theaders: { authorization: `Bearer ${this.config.botToken}` },\n\t\t\t});\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Failed to download Slack attachment ${file.name}: ${response.status}`);\n\t\t\t}\n\t\t\tconst bytes = new Uint8Array(await response.arrayBuffer());\n\t\t\tattachments.push(\n\t\t\t\tawait this.blobStore.put({\n\t\t\t\t\tnamespace: `incoming/slack/${resolved.sessionId}`,\n\t\t\t\t\tname: file.name,\n\t\t\t\t\ttitle: file.name,\n\t\t\t\t\tmimeType: file.mimetype,\n\t\t\t\t\tdata: bytes,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn attachments;\n\t}\n\n\tprivate async fetchUsers(): Promise<void> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.webClient.users.list({ limit: 200, cursor });\n\t\t\tconst members = result.members as\n\t\t\t\t| Array<{ id?: string; name?: string; real_name?: string; deleted?: boolean }>\n\t\t\t\t| undefined;\n\t\t\tif (members) {\n\t\t\t\tfor (const user of members) {\n\t\t\t\t\tif (user.id && user.name && !user.deleted) {\n\t\t\t\t\t\tthis.users.set(user.id, {\n\t\t\t\t\t\t\tid: user.id,\n\t\t\t\t\t\t\tuserName: user.name,\n\t\t\t\t\t\t\tdisplayName: user.real_name || user.name,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t} while (cursor);\n\t}\n\n\tprivate async fetchChannels(): Promise<void> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.webClient.conversations.list({\n\t\t\t\ttypes: \"public_channel,private_channel,im\",\n\t\t\t\texclude_archived: true,\n\t\t\t\tlimit: 200,\n\t\t\t\tcursor,\n\t\t\t});\n\t\t\tconst channels = result.channels as Array<{ id?: string; name?: string; user?: string }> | undefined;\n\t\t\tif (channels) {\n\t\t\t\tfor (const channel of channels) {\n\t\t\t\t\tif (!channel.id) continue;\n\t\t\t\t\tif (channel.name) {\n\t\t\t\t\t\tthis.channels.set(channel.id, { id: channel.id, name: channel.name });\n\t\t\t\t\t} else if (channel.user && this.users.has(channel.user)) {\n\t\t\t\t\t\tthis.channels.set(channel.id, {\n\t\t\t\t\t\t\tid: channel.id,\n\t\t\t\t\t\t\tname: `DM:${this.users.get(channel.user)!.userName}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t} while (cursor);\n\t}\n}\n\nfunction slackErrorCode(error: unknown): string | undefined {\n\tif (!error || typeof error !== \"object\" || !(\"data\" in error)) return undefined;\n\tconst data = error.data;\n\tif (!data || typeof data !== \"object\" || !(\"error\" in data)) return undefined;\n\treturn typeof data.error === \"string\" ? data.error : undefined;\n}\n\nfunction isSlackHistoryPermissionError(errorCode: string | undefined): boolean {\n\treturn errorCode === \"missing_scope\" || errorCode === \"no_permission\" || errorCode === \"not_allowed_token_type\";\n}\n\nexport async function startGatewayFromEnv(configPath?: string): Promise<void> {\n\tconst config = loadConfig(configPath);\n\tconst gateway = new SlackGateway(config);\n\tawait gateway.start();\n}\n"]}
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype AttachmentRef,\n\tBeeGatewayEngine,\n\ttype BeeResolvedTurn,\n\ttype BeeWorkerClient,\n\ttype BlobStore,\n\tcreateNatsBeeClient,\n\tLocalFileBlobStore,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { SocketModeClient } from \"@slack/socket-mode\";\nimport { WebClient } from \"@slack/web-api\";\nimport { join } from \"path\";\nimport { loadConfig } from \"./config.js\";\nimport { createHandoffServer, SlackHandoffController } from \"./handoff.js\";\nimport { HandoffReplyCache } from \"./handoff-reply-cache.js\";\nimport * as log from \"./log.js\";\nimport { resolveRoute, resolveStreamingPreference } from \"./router.js\";\nimport { SlackSink } from \"./slack-sink.js\";\nimport type { ResolvedSlackRoute, SlackFile, SlackGatewayConfig, SlackInboundMessage } from \"./types.js\";\n\ninterface SlackUser {\n\tid: string;\n\tuserName: string;\n\tdisplayName: string;\n}\n\ninterface SlackChannel {\n\tid: string;\n\tname: string;\n}\n\nexport class SlackGateway {\n\tprivate socketClient: SocketModeClient;\n\tprivate webClient: WebClient;\n\tprivate users = new Map<string, SlackUser>();\n\tprivate channels = new Map<string, SlackChannel>();\n\tprivate botUserId: string | null = null;\n\tprivate teamId: string | null = null;\n\tprivate teamName: string | null = null;\n\tprivate blobStore: BlobStore;\n\tprivate sink: SlackSink;\n\tprivate engine: BeeGatewayEngine<string> | null = null;\n\tprivate workerClient: BeeWorkerClient | null = null;\n\tprivate handoffReplyCache = new HandoffReplyCache();\n\tprivate slackThreadHistoryUnavailable = false;\n\n\tconstructor(private config: SlackGatewayConfig) {\n\t\tthis.socketClient = new SocketModeClient({ appToken: config.appToken });\n\t\tthis.webClient = new WebClient(config.botToken);\n\t\tthis.blobStore = new LocalFileBlobStore(\n\t\t\tprocess.env.BEE_SLACK_BLOB_STORE_ROOT ||\n\t\t\t\tprocess.env.BEE_BLOB_STORE_ROOT ||\n\t\t\t\tprocess.env.HUDAI_BLOB_STORE_ROOT ||\n\t\t\t\tjoin(process.cwd(), \".bee-blob-store\"),\n\t\t);\n\t\tthis.sink = new SlackSink(this.webClient, this.blobStore, {\n\t\t\tposted: (target, ref, text) => this.handoffReplyCache.recordPosted(target, ref, text, this.botDisplayName()),\n\t\t\tupdated: (target, ref, text) => this.handoffReplyCache.recordUpdated(target, ref, text, this.botDisplayName()),\n\t\t});\n\t}\n\n\tasync start(): Promise<void> {\n\t\tthis.workerClient = await createNatsBeeClient(this.config.nats);\n\t\tthis.engine = new BeeGatewayEngine({\n\t\t\tsink: this.sink,\n\t\t\tworkerClient: this.workerClient,\n\t\t\tlogger: {\n\t\t\t\tinfo: log.logInfo,\n\t\t\t\twarn: log.logWarning,\n\t\t\t\terror: log.logError,\n\t\t\t},\n\t\t});\n\t\tconst auth = await this.webClient.auth.test();\n\t\tthis.botUserId = auth.user_id as string;\n\t\tthis.teamId = auth.team_id as string;\n\t\tthis.teamName = (auth.team as string | undefined) || null;\n\t\tawait Promise.all([this.fetchUsers(), this.fetchChannels()]);\n\t\tthis.startHandoffServer();\n\t\tthis.setupEventHandlers();\n\t\tawait this.socketClient.start();\n\t\tlog.logInfo(\n\t\t\t`Connected as ${this.botUserId} in ${this.teamId}; loaded ${this.channels.size} channels and ${this.users.size} users`,\n\t\t);\n\t}\n\n\tprivate startHandoffServer(): void {\n\t\tconst handoffConfig = this.config.handoff;\n\t\tif (!handoffConfig?.enabled) return;\n\t\tif (!this.teamId || !this.engine) throw new Error(\"Gateway must be initialized before handoff server startup\");\n\t\tconst controller = new SlackHandoffController(handoffConfig, {\n\t\t\tteamId: this.teamId,\n\t\t\tpostRootMessage: async (channelId, text) => this.sink.postMessage({ channelId }, text),\n\t\t\tgetPermalink: async (channelId, messageTs) => {\n\t\t\t\tconst result = await this.webClient.chat.getPermalink({ channel: channelId, message_ts: messageTs });\n\t\t\t\tif (!result.permalink) throw new Error(\"Slack did not return a permalink\");\n\t\t\t\treturn result.permalink;\n\t\t\t},\n\t\t\tdispatch: (input) => this.engine!.dispatch(input),\n\t\t\tgetReplies: async (channelId, threadTs) => {\n\t\t\t\tif (this.slackThreadHistoryUnavailable) return this.handoffReplyCache.replies(channelId, threadTs);\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.webClient.conversations.replies({\n\t\t\t\t\t\tchannel: channelId,\n\t\t\t\t\t\tts: threadTs,\n\t\t\t\t\t\tlimit: 100,\n\t\t\t\t\t});\n\t\t\t\t\treturn (result.messages || []).map((message) => {\n\t\t\t\t\t\tconst typed = message as typeof message & {\n\t\t\t\t\t\t\tbot_id?: string;\n\t\t\t\t\t\t\tbot_profile?: { name?: string };\n\t\t\t\t\t\t\tusername?: string;\n\t\t\t\t\t\t\tthread_ts?: string;\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tts: String(typed.ts || \"\"),\n\t\t\t\t\t\t\tthreadTs: typed.thread_ts,\n\t\t\t\t\t\t\ttext: typed.text || \"\",\n\t\t\t\t\t\t\tauthor: typed.user\n\t\t\t\t\t\t\t\t? this.users.get(typed.user)?.displayName || this.users.get(typed.user)?.userName || typed.user\n\t\t\t\t\t\t\t\t: typed.bot_profile?.name || typed.username || \"Bot\",\n\t\t\t\t\t\t\tisBot: Boolean(typed.bot_id),\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst errorCode = slackErrorCode(error);\n\t\t\t\t\tif (!isSlackHistoryPermissionError(errorCode)) throw error;\n\t\t\t\t\tthis.slackThreadHistoryUnavailable = true;\n\t\t\t\t\tlog.logWarning(`Slack thread history unavailable (${errorCode}); using outbound Bee reply cache`);\n\t\t\t\t\treturn this.handoffReplyCache.replies(channelId, threadTs);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tconst server = createHandoffServer(controller);\n\t\tconst host = handoffConfig.host || \"0.0.0.0\";\n\t\tconst port = handoffConfig.port || 8080;\n\t\tserver.listen(port, host, () => log.logInfo(`bee-slack handoff listening on http://${host}:${port}`));\n\t}\n\n\tprivate botDisplayName(): string {\n\t\tif (!this.botUserId) return \"Bee\";\n\t\tconst bot = this.users.get(this.botUserId);\n\t\treturn bot?.displayName || bot?.userName || \"Bee\";\n\t}\n\n\tprivate setupEventHandlers(): void {\n\t\tthis.socketClient.on(\"app_mention\", async ({ event, ack, body }) => {\n\t\t\tawait ack();\n\t\t\tconst e = event as {\n\t\t\t\ttext: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser: string;\n\t\t\t\tts: string;\n\t\t\t\tthread_ts?: string;\n\t\t\t\tfiles?: SlackFile[];\n\t\t\t};\n\n\t\t\tif (e.channel.startsWith(\"D\")) return;\n\n\t\t\tawait this.enqueueInbound({\n\t\t\t\ttype: \"mention\",\n\t\t\t\tchannelId: e.channel,\n\t\t\t\tchannelName: this.channels.get(e.channel)?.name,\n\t\t\t\tthreadTs: e.thread_ts,\n\t\t\t\tts: e.ts,\n\t\t\t\tuserId: e.user,\n\t\t\t\tteamId: (event as typeof e & { team?: string }).team || (body as { team_id?: string }).team_id,\n\t\t\t\tuserName: this.users.get(e.user)?.userName,\n\t\t\t\tdisplayName: this.users.get(e.user)?.displayName,\n\t\t\t\ttext: e.text.replace(/<@[A-Z0-9]+>/gi, \"\").trim(),\n\t\t\t\tfiles: e.files,\n\t\t\t});\n\t\t});\n\n\t\tthis.socketClient.on(\"message\", async ({ event, ack }) => {\n\t\t\tawait ack();\n\t\t\tconst e = event as {\n\t\t\t\ttext?: string;\n\t\t\t\tchannel: string;\n\t\t\t\tuser?: string;\n\t\t\t\tts: string;\n\t\t\t\tthread_ts?: string;\n\t\t\t\tchannel_type?: string;\n\t\t\t\tsubtype?: string;\n\t\t\t\tbot_id?: string;\n\t\t\t\tfiles?: SlackFile[];\n\t\t\t};\n\n\t\t\tif (e.bot_id || !e.user || e.user === this.botUserId) return;\n\t\t\tif (e.subtype !== undefined && e.subtype !== \"file_share\") return;\n\t\t\tif (!e.text && (!e.files || e.files.length === 0)) return;\n\n\t\t\tconst isDM = e.channel_type === \"im\";\n\t\t\tconst isBotMention = !!this.botUserId && e.text?.includes(`<@${this.botUserId}>`);\n\t\t\tif (!isDM && isBotMention) return;\n\t\t\tif (!isDM) return;\n\n\t\t\tawait this.enqueueInbound({\n\t\t\t\ttype: \"dm\",\n\t\t\t\tchannelId: e.channel,\n\t\t\t\tchannelName: this.channels.get(e.channel)?.name,\n\t\t\t\tthreadTs: e.thread_ts,\n\t\t\t\tts: e.ts,\n\t\t\t\tuserId: e.user,\n\t\t\t\tuserName: this.users.get(e.user)?.userName,\n\t\t\t\tdisplayName: this.users.get(e.user)?.displayName,\n\t\t\t\ttext: (e.text || \"\").replace(/<@[A-Z0-9]+>/gi, \"\").trim(),\n\t\t\t\tfiles: e.files,\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate async enqueueInbound(message: SlackInboundMessage): Promise<void> {\n\t\tif (!this.botUserId || !this.teamId) {\n\t\t\tthrow new Error(\"Gateway has not been initialized\");\n\t\t}\n\t\tif (!this.engine) {\n\t\t\tthrow new Error(\"Gateway engine has not been initialized\");\n\t\t}\n\n\t\ttry {\n\t\t\tconst resolved = resolveRoute(this.config.routes, message, {\n\t\t\t\tbotUserId: this.botUserId,\n\t\t\t\tteamId: this.teamId,\n\t\t\t\tteamName: this.teamName || undefined,\n\t\t\t});\n\t\t\tif (!resolved) {\n\t\t\t\tlog.logWarning(`No route for channel ${message.channelId}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst outputTarget = this.buildOutputTarget(message, resolved);\n\t\t\tif (message.text.trim().toLowerCase() === \"stop\") {\n\t\t\t\tconst stopped = await this.engine.stopActiveRun(resolved.sessionId);\n\t\t\t\tawait this.sink.postMessage(outputTarget, stopped ? \"Stopping active run.\" : \"Nothing running.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst attachments = await this.downloadAttachments(resolved, message);\n\t\t\tconst input = this.buildResolvedTurn(message, resolved, attachments, outputTarget);\n\t\t\tthis.engine.dispatch(input);\n\t\t} catch (error) {\n\t\t\tconst messageText = error instanceof Error ? error.message : String(error);\n\t\t\tlog.logError(`Failed to normalize Slack inbound message ${message.ts}`, messageText);\n\t\t\tawait this.sink.postMessage(\n\t\t\t\t{\n\t\t\t\t\tchannelId: message.channelId,\n\t\t\t\t\tthreadId: message.threadTs || (message.type === \"mention\" ? message.ts : undefined),\n\t\t\t\t},\n\t\t\t\t`_Gateway error: ${messageText}_`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate buildResolvedTurn(\n\t\tmessage: SlackInboundMessage,\n\t\tresolved: ResolvedSlackRoute,\n\t\tattachments: AttachmentRef[],\n\t\toutput: TransportOutputTarget,\n\t): BeeResolvedTurn {\n\t\tif (!this.teamId || !this.botUserId) {\n\t\t\tthrow new Error(\"Missing Slack gateway identity\");\n\t\t}\n\n\t\treturn {\n\t\t\tsessionId: resolved.sessionId,\n\t\t\tthreadId: resolved.threadTs,\n\t\t\tworker: resolved.route.worker,\n\t\t\tconversation: {\n\t\t\t\ttransport: \"slack\",\n\t\t\t\tconversationId: resolved.conversationId,\n\t\t\t},\n\t\t\tactor: {\n\t\t\t\tuserId: message.userId,\n\t\t\t\tuserName: message.userName,\n\t\t\t\tdisplayName: message.displayName,\n\t\t\t},\n\t\t\tmessage: {\n\t\t\t\ttext: message.text,\n\t\t\t},\n\t\t\tattachments,\n\t\t\tstreaming: resolveStreamingPreference(resolved.route, message, {\n\t\t\t\tbotUserId: this.botUserId,\n\t\t\t\tteamId: this.teamId,\n\t\t\t\tteamName: this.teamName || undefined,\n\t\t\t}),\n\t\t\toutput,\n\t\t};\n\t}\n\n\tprivate buildOutputTarget(message: SlackInboundMessage, resolved: ResolvedSlackRoute): TransportOutputTarget {\n\t\treturn {\n\t\t\tchannelId: message.channelId,\n\t\t\tthreadId: resolved.threadTs || (message.type === \"mention\" ? message.ts : undefined),\n\t\t};\n\t}\n\n\tprivate async downloadAttachments(\n\t\tresolved: ResolvedSlackRoute,\n\t\tmessage: SlackInboundMessage,\n\t): Promise<AttachmentRef[]> {\n\t\tconst files = message.files || [];\n\t\tif (files.length === 0) return [];\n\n\t\tconst attachments: AttachmentRef[] = [];\n\t\tfor (const file of files) {\n\t\t\tconst url = file.url_private_download || file.url_private;\n\t\t\tif (!url || !file.name) continue;\n\t\t\tconst response = await fetch(url, {\n\t\t\t\theaders: { authorization: `Bearer ${this.config.botToken}` },\n\t\t\t});\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Failed to download Slack attachment ${file.name}: ${response.status}`);\n\t\t\t}\n\t\t\tconst bytes = new Uint8Array(await response.arrayBuffer());\n\t\t\tattachments.push(\n\t\t\t\tawait this.blobStore.put({\n\t\t\t\t\tnamespace: `incoming/slack/${resolved.sessionId}`,\n\t\t\t\t\tname: file.name,\n\t\t\t\t\ttitle: file.name,\n\t\t\t\t\tmimeType: file.mimetype,\n\t\t\t\t\tdata: bytes,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn attachments;\n\t}\n\n\tprivate async fetchUsers(): Promise<void> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.webClient.users.list({ limit: 200, cursor });\n\t\t\tconst members = result.members as\n\t\t\t\t| Array<{ id?: string; name?: string; real_name?: string; deleted?: boolean }>\n\t\t\t\t| undefined;\n\t\t\tif (members) {\n\t\t\t\tfor (const user of members) {\n\t\t\t\t\tif (user.id && user.name && !user.deleted) {\n\t\t\t\t\t\tthis.users.set(user.id, {\n\t\t\t\t\t\t\tid: user.id,\n\t\t\t\t\t\t\tuserName: user.name,\n\t\t\t\t\t\t\tdisplayName: user.real_name || user.name,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t} while (cursor);\n\t}\n\n\tprivate async fetchChannels(): Promise<void> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.webClient.conversations.list({\n\t\t\t\ttypes: \"public_channel,private_channel,im\",\n\t\t\t\texclude_archived: true,\n\t\t\t\tlimit: 200,\n\t\t\t\tcursor,\n\t\t\t});\n\t\t\tconst channels = result.channels as Array<{ id?: string; name?: string; user?: string }> | undefined;\n\t\t\tif (channels) {\n\t\t\t\tfor (const channel of channels) {\n\t\t\t\t\tif (!channel.id) continue;\n\t\t\t\t\tif (channel.name) {\n\t\t\t\t\t\tthis.channels.set(channel.id, { id: channel.id, name: channel.name });\n\t\t\t\t\t} else if (channel.user && this.users.has(channel.user)) {\n\t\t\t\t\t\tthis.channels.set(channel.id, {\n\t\t\t\t\t\t\tid: channel.id,\n\t\t\t\t\t\t\tname: `DM:${this.users.get(channel.user)!.userName}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor = result.response_metadata?.next_cursor;\n\t\t} while (cursor);\n\t}\n}\n\nfunction slackErrorCode(error: unknown): string | undefined {\n\tif (!error || typeof error !== \"object\" || !(\"data\" in error)) return undefined;\n\tconst data = error.data;\n\tif (!data || typeof data !== \"object\" || !(\"error\" in data)) return undefined;\n\treturn typeof data.error === \"string\" ? data.error : undefined;\n}\n\nfunction isSlackHistoryPermissionError(errorCode: string | undefined): boolean {\n\treturn errorCode === \"missing_scope\" || errorCode === \"no_permission\" || errorCode === \"not_allowed_token_type\";\n}\n\nexport async function startGatewayFromEnv(configPath?: string): Promise<void> {\n\tconst config = loadConfig(configPath);\n\tconst gateway = new SlackGateway(config);\n\tawait gateway.start();\n}\n"]}
package/dist/router.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { type TransportStreamingPreference } from "@jobmatchme/bee-gate";
1
2
  import type { ResolvedSlackRoute, SlackGatewayContext, SlackInboundMessage, SlackRouteConfig } from "./types.js";
2
3
  export declare function resolveRoute(routes: SlackRouteConfig[], message: SlackInboundMessage, context: SlackGatewayContext): ResolvedSlackRoute | null;
4
+ export declare function resolveStreamingPreference(route: SlackRouteConfig, message: SlackInboundMessage, context: SlackGatewayContext): TransportStreamingPreference | undefined;
3
5
  //# sourceMappingURL=router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA0BjH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,gBAAgB,EAAE,EAC1B,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,mBAAmB,GAC1B,kBAAkB,GAAG,IAAI,CAkB3B","sourcesContent":["import { buildConversationId, buildSessionKey } from \"@jobmatchme/bee-gate\";\nimport type { ResolvedSlackRoute, SlackGatewayContext, SlackInboundMessage, SlackRouteConfig } from \"./types.js\";\n\nfunction matchesRoute(route: SlackRouteConfig, message: SlackInboundMessage): boolean {\n\tconst match = route.match;\n\n\tif (message.type === \"dm\") {\n\t\tif (match.dm !== true) return false;\n\t} else if (match.dm === true) {\n\t\treturn false;\n\t}\n\n\tif (match.channelIds && !match.channelIds.includes(message.channelId)) {\n\t\treturn false;\n\t}\n\n\tif (match.channelNames && (!message.channelName || !match.channelNames.includes(message.channelName))) {\n\t\treturn false;\n\t}\n\n\tif (match.textPrefix && !message.text.trim().startsWith(match.textPrefix)) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport function resolveRoute(\n\troutes: SlackRouteConfig[],\n\tmessage: SlackInboundMessage,\n\tcontext: SlackGatewayContext,\n): ResolvedSlackRoute | null {\n\tconst route = routes.find((candidate) => matchesRoute(candidate, message));\n\tif (!route) return null;\n\n\tconst threadId = message.threadTs || (message.type === \"mention\" ? message.ts : undefined);\n\tconst strategy = route.session?.strategy || \"thread\";\n\tconst sessionBase = strategy === \"channel\" ? message.channelId : threadId || message.ts;\n\tconst conversationId = buildConversationId([\"slack\", context.teamId, message.channelId, threadId || message.ts]);\n\tconst prefix = route.session?.prefix || route.id;\n\treturn {\n\t\troute,\n\t\tconversationId,\n\t\tsessionId: buildSessionKey(\n\t\t\tprefix,\n\t\t\tbuildConversationId([\"slack\", context.teamId, message.channelId, sessionBase]),\n\t\t),\n\t\tthreadTs: threadId,\n\t};\n}\n"]}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,KAAK,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AAC/G,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA0BjH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,gBAAgB,EAAE,EAC1B,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,mBAAmB,GAC1B,kBAAkB,GAAG,IAAI,CAkB3B;AAED,wBAAgB,0BAA0B,CACzC,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,mBAAmB,GAC1B,4BAA4B,GAAG,SAAS,CAY1C","sourcesContent":["import { buildConversationId, buildSessionKey, type TransportStreamingPreference } from \"@jobmatchme/bee-gate\";\nimport type { ResolvedSlackRoute, SlackGatewayContext, SlackInboundMessage, SlackRouteConfig } from \"./types.js\";\n\nfunction matchesRoute(route: SlackRouteConfig, message: SlackInboundMessage): boolean {\n\tconst match = route.match;\n\n\tif (message.type === \"dm\") {\n\t\tif (match.dm !== true) return false;\n\t} else if (match.dm === true) {\n\t\treturn false;\n\t}\n\n\tif (match.channelIds && !match.channelIds.includes(message.channelId)) {\n\t\treturn false;\n\t}\n\n\tif (match.channelNames && (!message.channelName || !match.channelNames.includes(message.channelName))) {\n\t\treturn false;\n\t}\n\n\tif (match.textPrefix && !message.text.trim().startsWith(match.textPrefix)) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport function resolveRoute(\n\troutes: SlackRouteConfig[],\n\tmessage: SlackInboundMessage,\n\tcontext: SlackGatewayContext,\n): ResolvedSlackRoute | null {\n\tconst route = routes.find((candidate) => matchesRoute(candidate, message));\n\tif (!route) return null;\n\n\tconst threadId = message.threadTs || (message.type === \"mention\" ? message.ts : undefined);\n\tconst strategy = route.session?.strategy || \"thread\";\n\tconst sessionBase = strategy === \"channel\" ? message.channelId : threadId || message.ts;\n\tconst conversationId = buildConversationId([\"slack\", context.teamId, message.channelId, threadId || message.ts]);\n\tconst prefix = route.session?.prefix || route.id;\n\treturn {\n\t\troute,\n\t\tconversationId,\n\t\tsessionId: buildSessionKey(\n\t\t\tprefix,\n\t\t\tbuildConversationId([\"slack\", context.teamId, message.channelId, sessionBase]),\n\t\t),\n\t\tthreadTs: threadId,\n\t};\n}\n\nexport function resolveStreamingPreference(\n\troute: SlackRouteConfig,\n\tmessage: SlackInboundMessage,\n\tcontext: SlackGatewayContext,\n): TransportStreamingPreference | undefined {\n\tif (message.type !== \"mention\" || route.streaming?.enabled !== true) return undefined;\n\n\treturn {\n\t\tenabled: true,\n\t\trouteId: route.id,\n\t\tpresentation: route.streaming.taskDisplayMode || \"timeline\",\n\t\tcontext: {\n\t\t\trecipientUserId: message.userId,\n\t\t\trecipientTeamId: message.teamId || context.teamId,\n\t\t},\n\t};\n}\n"]}
package/dist/router.js CHANGED
@@ -35,4 +35,17 @@ export function resolveRoute(routes, message, context) {
35
35
  threadTs: threadId,
36
36
  };
37
37
  }
38
+ export function resolveStreamingPreference(route, message, context) {
39
+ if (message.type !== "mention" || route.streaming?.enabled !== true)
40
+ return undefined;
41
+ return {
42
+ enabled: true,
43
+ routeId: route.id,
44
+ presentation: route.streaming.taskDisplayMode || "timeline",
45
+ context: {
46
+ recipientUserId: message.userId,
47
+ recipientTeamId: message.teamId || context.teamId,
48
+ },
49
+ };
50
+ }
38
51
  //# sourceMappingURL=router.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5E,SAAS,YAAY,CAAC,KAAuB,EAAE,OAA4B,EAAW;IACrF,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAE1B,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;IACrC,CAAC;SAAM,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACd,CAAC;IAED,IAAI,KAAK,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACvE,OAAO,KAAK,CAAC;IACd,CAAC;IAED,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;QACvG,OAAO,KAAK,CAAC;IACd,CAAC;IAED,IAAI,KAAK,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3E,OAAO,KAAK,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,UAAU,YAAY,CAC3B,MAA0B,EAC1B,OAA4B,EAC5B,OAA4B,EACA;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3E,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC3F,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,EAAE,QAAQ,IAAI,QAAQ,CAAC;IACrD,MAAM,WAAW,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC;IACxF,MAAM,cAAc,GAAG,mBAAmB,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACjH,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC;IACjD,OAAO;QACN,KAAK;QACL,cAAc;QACd,SAAS,EAAE,eAAe,CACzB,MAAM,EACN,mBAAmB,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAC9E;QACD,QAAQ,EAAE,QAAQ;KAClB,CAAC;AAAA,CACF","sourcesContent":["import { buildConversationId, buildSessionKey } from \"@jobmatchme/bee-gate\";\nimport type { ResolvedSlackRoute, SlackGatewayContext, SlackInboundMessage, SlackRouteConfig } from \"./types.js\";\n\nfunction matchesRoute(route: SlackRouteConfig, message: SlackInboundMessage): boolean {\n\tconst match = route.match;\n\n\tif (message.type === \"dm\") {\n\t\tif (match.dm !== true) return false;\n\t} else if (match.dm === true) {\n\t\treturn false;\n\t}\n\n\tif (match.channelIds && !match.channelIds.includes(message.channelId)) {\n\t\treturn false;\n\t}\n\n\tif (match.channelNames && (!message.channelName || !match.channelNames.includes(message.channelName))) {\n\t\treturn false;\n\t}\n\n\tif (match.textPrefix && !message.text.trim().startsWith(match.textPrefix)) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport function resolveRoute(\n\troutes: SlackRouteConfig[],\n\tmessage: SlackInboundMessage,\n\tcontext: SlackGatewayContext,\n): ResolvedSlackRoute | null {\n\tconst route = routes.find((candidate) => matchesRoute(candidate, message));\n\tif (!route) return null;\n\n\tconst threadId = message.threadTs || (message.type === \"mention\" ? message.ts : undefined);\n\tconst strategy = route.session?.strategy || \"thread\";\n\tconst sessionBase = strategy === \"channel\" ? message.channelId : threadId || message.ts;\n\tconst conversationId = buildConversationId([\"slack\", context.teamId, message.channelId, threadId || message.ts]);\n\tconst prefix = route.session?.prefix || route.id;\n\treturn {\n\t\troute,\n\t\tconversationId,\n\t\tsessionId: buildSessionKey(\n\t\t\tprefix,\n\t\t\tbuildConversationId([\"slack\", context.teamId, message.channelId, sessionBase]),\n\t\t),\n\t\tthreadTs: threadId,\n\t};\n}\n"]}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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","sourcesContent":["import { buildConversationId, buildSessionKey, type TransportStreamingPreference } from \"@jobmatchme/bee-gate\";\nimport type { ResolvedSlackRoute, SlackGatewayContext, SlackInboundMessage, SlackRouteConfig } from \"./types.js\";\n\nfunction matchesRoute(route: SlackRouteConfig, message: SlackInboundMessage): boolean {\n\tconst match = route.match;\n\n\tif (message.type === \"dm\") {\n\t\tif (match.dm !== true) return false;\n\t} else if (match.dm === true) {\n\t\treturn false;\n\t}\n\n\tif (match.channelIds && !match.channelIds.includes(message.channelId)) {\n\t\treturn false;\n\t}\n\n\tif (match.channelNames && (!message.channelName || !match.channelNames.includes(message.channelName))) {\n\t\treturn false;\n\t}\n\n\tif (match.textPrefix && !message.text.trim().startsWith(match.textPrefix)) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport function resolveRoute(\n\troutes: SlackRouteConfig[],\n\tmessage: SlackInboundMessage,\n\tcontext: SlackGatewayContext,\n): ResolvedSlackRoute | null {\n\tconst route = routes.find((candidate) => matchesRoute(candidate, message));\n\tif (!route) return null;\n\n\tconst threadId = message.threadTs || (message.type === \"mention\" ? message.ts : undefined);\n\tconst strategy = route.session?.strategy || \"thread\";\n\tconst sessionBase = strategy === \"channel\" ? message.channelId : threadId || message.ts;\n\tconst conversationId = buildConversationId([\"slack\", context.teamId, message.channelId, threadId || message.ts]);\n\tconst prefix = route.session?.prefix || route.id;\n\treturn {\n\t\troute,\n\t\tconversationId,\n\t\tsessionId: buildSessionKey(\n\t\t\tprefix,\n\t\t\tbuildConversationId([\"slack\", context.teamId, message.channelId, sessionBase]),\n\t\t),\n\t\tthreadTs: threadId,\n\t};\n}\n\nexport function resolveStreamingPreference(\n\troute: SlackRouteConfig,\n\tmessage: SlackInboundMessage,\n\tcontext: SlackGatewayContext,\n): TransportStreamingPreference | undefined {\n\tif (message.type !== \"mention\" || route.streaming?.enabled !== true) return undefined;\n\n\treturn {\n\t\tenabled: true,\n\t\trouteId: route.id,\n\t\tpresentation: route.streaming.taskDisplayMode || \"timeline\",\n\t\tcontext: {\n\t\t\trecipientUserId: message.userId,\n\t\t\trecipientTeamId: message.teamId || context.teamId,\n\t\t},\n\t};\n}\n"]}
@@ -1,5 +1,11 @@
1
- import type { ArtifactRef, BlobStore, TransportOutputTarget, TransportSink } from "@jobmatchme/bee-gate";
1
+ import type { ActionUpdate, ArtifactRef, BlobStore, TransportOutputTarget, TransportSink, TransportStreamResult, TransportStreamStart } from "@jobmatchme/bee-gate";
2
2
  import type { WebClient } from "@slack/web-api";
3
+ export declare const SLACK_STREAM_HEADER = "Ich bearbeite jetzt deine Anfrage...";
4
+ export declare const SLACK_TASK_TEXT_LIMIT = 256;
5
+ export declare const SLACK_MARKDOWN_LIMIT = 12000;
6
+ export declare const SLACK_MARKDOWN_TRUNCATION_SUFFIX = "\n\n_[Antwort auf 12.000 Zeichen gek\u00FCrzt]_";
7
+ export declare function truncateSlackTaskText(text: string): string;
8
+ export declare function truncateSlackMarkdown(text: string): string;
3
9
  export interface SlackSinkObserver {
4
10
  posted(target: TransportOutputTarget, ref: string, text: string): void;
5
11
  updated(target: TransportOutputTarget, ref: string, text: string): void;
@@ -11,6 +17,10 @@ export declare class SlackSink implements TransportSink<string> {
11
17
  constructor(webClient: WebClient, blobStore: BlobStore, observer?: SlackSinkObserver | undefined);
12
18
  postMessage(target: TransportOutputTarget, text: string): Promise<string>;
13
19
  updateMessage(target: TransportOutputTarget, ref: string, text: string): Promise<void>;
20
+ prepareStreamText(text: string): string;
21
+ startStream(target: TransportOutputTarget, start: TransportStreamStart): Promise<string>;
22
+ updateStream(target: TransportOutputTarget, ref: string, action: ActionUpdate): Promise<void>;
23
+ stopStream(target: TransportOutputTarget, ref: string, result: TransportStreamResult): Promise<void>;
14
24
  publishArtifact(target: TransportOutputTarget, artifact: ArtifactRef): Promise<void>;
15
25
  }
16
26
  //# sourceMappingURL=slack-sink.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slack-sink.d.ts","sourceRoot":"","sources":["../src/slack-sink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACzG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,MAAM,WAAW,iBAAiB;IACjC,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACvE,OAAO,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACxE;AAED,qBAAa,SAAU,YAAW,aAAa,CAAC,MAAM,CAAC;IAErD,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,QAAQ,CAAC;IAHlB,YACS,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,QAAQ,CAAC,+BAAmB,EACjC;IAEE,WAAW,CAAC,MAAM,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAa9E;IAEK,aAAa,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3F;IAEK,eAAe,CAAC,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBzF;CACD","sourcesContent":["import type { ArtifactRef, BlobStore, TransportOutputTarget, TransportSink } from \"@jobmatchme/bee-gate\";\nimport type { WebClient } from \"@slack/web-api\";\nimport { createReadStream } from \"fs\";\n\nexport interface SlackSinkObserver {\n\tposted(target: TransportOutputTarget, ref: string, text: string): void;\n\tupdated(target: TransportOutputTarget, ref: string, text: string): void;\n}\n\nexport class SlackSink implements TransportSink<string> {\n\tconstructor(\n\t\tprivate webClient: WebClient,\n\t\tprivate blobStore: BlobStore,\n\t\tprivate observer?: SlackSinkObserver,\n\t) {}\n\n\tasync postMessage(target: TransportOutputTarget, text: string): Promise<string> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\tchannel: target.channelId,\n\t\t\ttext,\n\t\t\tthread_ts: target.threadId,\n\t\t});\n\t\tconst ref = result.ts as string;\n\t\tthis.observer?.posted(target, ref, text);\n\t\treturn ref;\n\t}\n\n\tasync updateMessage(target: TransportOutputTarget, ref: string, text: string): Promise<void> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tawait this.webClient.chat.update({\n\t\t\tchannel: target.channelId,\n\t\t\tts: ref,\n\t\t\ttext,\n\t\t});\n\t\tthis.observer?.updated(target, ref, text);\n\t}\n\n\tasync publishArtifact(target: TransportOutputTarget, artifact: ArtifactRef): Promise<void> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tconst materialized = await this.blobStore.materialize(artifact);\n\t\ttry {\n\t\t\tawait (this.webClient.files as any).uploadV2({\n\t\t\t\tchannel_id: target.channelId,\n\t\t\t\tthread_ts: target.threadId,\n\t\t\t\tfile: createReadStream(materialized.path),\n\t\t\t\tfilename: materialized.filename,\n\t\t\t\ttitle: artifact.title || artifact.name || materialized.filename,\n\t\t\t});\n\t\t} finally {\n\t\t\tawait materialized.cleanup?.();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"slack-sink.d.ts","sourceRoot":"","sources":["../src/slack-sink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,YAAY,EACZ,WAAW,EACX,SAAS,EACT,qBAAqB,EACrB,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,eAAO,MAAM,mBAAmB,yCAAyC,CAAC;AAC1E,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAC3C,eAAO,MAAM,gCAAgC,oDAA+C,CAAC;AAS7F,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;AAUD,MAAM,WAAW,iBAAiB;IACjC,MAAM,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACvE,OAAO,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACxE;AAED,qBAAa,SAAU,YAAW,aAAa,CAAC,MAAM,CAAC;IAErD,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,QAAQ,CAAC;IAHlB,YACS,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,QAAQ,CAAC,+BAAmB,EACjC;IAEE,WAAW,CAAC,MAAM,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAa9E;IAEK,aAAa,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3F;IAED,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtC;IAEK,WAAW,CAAC,MAAM,EAAE,qBAAqB,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwB7F;IAEK,YAAY,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBlG;IAEK,UAAU,CAAC,MAAM,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAczG;IAEK,eAAe,CAAC,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBzF;CACD","sourcesContent":["import type {\n\tActionUpdate,\n\tArtifactRef,\n\tBlobStore,\n\tTransportOutputTarget,\n\tTransportSink,\n\tTransportStreamResult,\n\tTransportStreamStart,\n} from \"@jobmatchme/bee-gate\";\nimport type { WebClient } from \"@slack/web-api\";\nimport { createReadStream } from \"fs\";\n\nexport const SLACK_STREAM_HEADER = \"Ich bearbeite jetzt deine Anfrage...\";\nexport const SLACK_TASK_TEXT_LIMIT = 256;\nexport const SLACK_MARKDOWN_LIMIT = 12_000;\nexport const SLACK_MARKDOWN_TRUNCATION_SUFFIX = \"\\n\\n_[Antwort auf 12.000 Zeichen gekürzt]_\";\n\nfunction truncateCodePoints(text: string, limit: number, suffix: string): string {\n\tconst codePoints = Array.from(text);\n\tif (codePoints.length <= limit) return text;\n\tconst suffixCodePoints = Array.from(suffix);\n\treturn `${codePoints.slice(0, Math.max(0, limit - suffixCodePoints.length)).join(\"\")}${suffix}`;\n}\n\nexport function truncateSlackTaskText(text: string): string {\n\treturn truncateCodePoints(text, SLACK_TASK_TEXT_LIMIT, \"…\");\n}\n\nexport function truncateSlackMarkdown(text: string): string {\n\treturn truncateCodePoints(text, SLACK_MARKDOWN_LIMIT, SLACK_MARKDOWN_TRUNCATION_SUFFIX);\n}\n\nfunction requiredContextString(start: TransportStreamStart, key: string): string {\n\tconst value = start.context?.[key];\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`Missing Slack stream context ${key}`);\n\t}\n\treturn value;\n}\n\nexport interface SlackSinkObserver {\n\tposted(target: TransportOutputTarget, ref: string, text: string): void;\n\tupdated(target: TransportOutputTarget, ref: string, text: string): void;\n}\n\nexport class SlackSink implements TransportSink<string> {\n\tconstructor(\n\t\tprivate webClient: WebClient,\n\t\tprivate blobStore: BlobStore,\n\t\tprivate observer?: SlackSinkObserver,\n\t) {}\n\n\tasync postMessage(target: TransportOutputTarget, text: string): Promise<string> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\tchannel: target.channelId,\n\t\t\ttext,\n\t\t\tthread_ts: target.threadId,\n\t\t});\n\t\tconst ref = result.ts as string;\n\t\tthis.observer?.posted(target, ref, text);\n\t\treturn ref;\n\t}\n\n\tasync updateMessage(target: TransportOutputTarget, ref: string, text: string): Promise<void> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tawait this.webClient.chat.update({\n\t\t\tchannel: target.channelId,\n\t\t\tts: ref,\n\t\t\ttext,\n\t\t});\n\t\tthis.observer?.updated(target, ref, text);\n\t}\n\n\tprepareStreamText(text: string): string {\n\t\treturn truncateSlackMarkdown(text);\n\t}\n\n\tasync startStream(target: TransportOutputTarget, start: TransportStreamStart): Promise<string> {\n\t\tif (!target.channelId) throw new Error(\"Missing Slack channel id\");\n\t\tif (!target.threadId) throw new Error(\"Missing Slack stream thread timestamp\");\n\n\t\tconst headerChunk = {\n\t\t\ttype: \"blocks\",\n\t\t\tblocks: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"header\",\n\t\t\t\t\ttext: { type: \"plain_text\", text: SLACK_STREAM_HEADER },\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t\tconst result = await this.webClient.chat.startStream({\n\t\t\tchannel: target.channelId,\n\t\t\tthread_ts: target.threadId,\n\t\t\trecipient_user_id: requiredContextString(start, \"recipientUserId\"),\n\t\t\trecipient_team_id: requiredContextString(start, \"recipientTeamId\"),\n\t\t\ttask_display_mode: start.presentation || \"timeline\",\n\t\t\t// Slack supports block chunks although the current SDK's chunk union omits them.\n\t\t\tchunks: [headerChunk] as never,\n\t\t});\n\t\tif (!result.ts) throw new Error(\"Slack chat.startStream did not return a message timestamp\");\n\t\treturn result.ts;\n\t}\n\n\tasync updateStream(target: TransportOutputTarget, ref: string, action: ActionUpdate): Promise<void> {\n\t\tif (!target.channelId) throw new Error(\"Missing Slack channel id\");\n\n\t\tawait this.webClient.chat.appendStream({\n\t\t\tchannel: target.channelId,\n\t\t\tts: ref,\n\t\t\tchunks: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"task_update\",\n\t\t\t\t\tid: action.id,\n\t\t\t\t\ttitle: truncateSlackTaskText(action.title),\n\t\t\t\t\t...(action.details === undefined ? {} : { details: truncateSlackTaskText(action.details) }),\n\t\t\t\t\tstatus: action.status,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t}\n\n\tasync stopStream(target: TransportOutputTarget, ref: string, result: TransportStreamResult): Promise<void> {\n\t\tif (!target.channelId) throw new Error(\"Missing Slack channel id\");\n\t\tconst payload = {\n\t\t\tchannel: target.channelId,\n\t\t\tts: ref,\n\t\t\tmarkdown_text: truncateSlackMarkdown(result.text),\n\t\t};\n\n\t\ttry {\n\t\t\tawait this.webClient.chat.stopStream(payload);\n\t\t} catch {\n\t\t\t// One explicit retry keeps final-answer delivery resilient while remaining bounded.\n\t\t\tawait this.webClient.chat.stopStream(payload);\n\t\t}\n\t}\n\n\tasync publishArtifact(target: TransportOutputTarget, artifact: ArtifactRef): Promise<void> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tconst materialized = await this.blobStore.materialize(artifact);\n\t\ttry {\n\t\t\tawait (this.webClient.files as any).uploadV2({\n\t\t\t\tchannel_id: target.channelId,\n\t\t\t\tthread_ts: target.threadId,\n\t\t\t\tfile: createReadStream(materialized.path),\n\t\t\t\tfilename: materialized.filename,\n\t\t\t\ttitle: artifact.title || artifact.name || materialized.filename,\n\t\t\t});\n\t\t} finally {\n\t\t\tawait materialized.cleanup?.();\n\t\t}\n\t}\n}\n"]}
@@ -1,4 +1,28 @@
1
1
  import { createReadStream } from "fs";
2
+ export const SLACK_STREAM_HEADER = "Ich bearbeite jetzt deine Anfrage...";
3
+ export const SLACK_TASK_TEXT_LIMIT = 256;
4
+ export const SLACK_MARKDOWN_LIMIT = 12_000;
5
+ export const SLACK_MARKDOWN_TRUNCATION_SUFFIX = "\n\n_[Antwort auf 12.000 Zeichen gekürzt]_";
6
+ function truncateCodePoints(text, limit, suffix) {
7
+ const codePoints = Array.from(text);
8
+ if (codePoints.length <= limit)
9
+ return text;
10
+ const suffixCodePoints = Array.from(suffix);
11
+ return `${codePoints.slice(0, Math.max(0, limit - suffixCodePoints.length)).join("")}${suffix}`;
12
+ }
13
+ export function truncateSlackTaskText(text) {
14
+ return truncateCodePoints(text, SLACK_TASK_TEXT_LIMIT, "…");
15
+ }
16
+ export function truncateSlackMarkdown(text) {
17
+ return truncateCodePoints(text, SLACK_MARKDOWN_LIMIT, SLACK_MARKDOWN_TRUNCATION_SUFFIX);
18
+ }
19
+ function requiredContextString(start, key) {
20
+ const value = start.context?.[key];
21
+ if (typeof value !== "string" || value.length === 0) {
22
+ throw new Error(`Missing Slack stream context ${key}`);
23
+ }
24
+ return value;
25
+ }
2
26
  export class SlackSink {
3
27
  webClient;
4
28
  blobStore;
@@ -32,6 +56,69 @@ export class SlackSink {
32
56
  });
33
57
  this.observer?.updated(target, ref, text);
34
58
  }
59
+ prepareStreamText(text) {
60
+ return truncateSlackMarkdown(text);
61
+ }
62
+ async startStream(target, start) {
63
+ if (!target.channelId)
64
+ throw new Error("Missing Slack channel id");
65
+ if (!target.threadId)
66
+ throw new Error("Missing Slack stream thread timestamp");
67
+ const headerChunk = {
68
+ type: "blocks",
69
+ blocks: [
70
+ {
71
+ type: "header",
72
+ text: { type: "plain_text", text: SLACK_STREAM_HEADER },
73
+ },
74
+ ],
75
+ };
76
+ const result = await this.webClient.chat.startStream({
77
+ channel: target.channelId,
78
+ thread_ts: target.threadId,
79
+ recipient_user_id: requiredContextString(start, "recipientUserId"),
80
+ recipient_team_id: requiredContextString(start, "recipientTeamId"),
81
+ task_display_mode: start.presentation || "timeline",
82
+ // Slack supports block chunks although the current SDK's chunk union omits them.
83
+ chunks: [headerChunk],
84
+ });
85
+ if (!result.ts)
86
+ throw new Error("Slack chat.startStream did not return a message timestamp");
87
+ return result.ts;
88
+ }
89
+ async updateStream(target, ref, action) {
90
+ if (!target.channelId)
91
+ throw new Error("Missing Slack channel id");
92
+ await this.webClient.chat.appendStream({
93
+ channel: target.channelId,
94
+ ts: ref,
95
+ chunks: [
96
+ {
97
+ type: "task_update",
98
+ id: action.id,
99
+ title: truncateSlackTaskText(action.title),
100
+ ...(action.details === undefined ? {} : { details: truncateSlackTaskText(action.details) }),
101
+ status: action.status,
102
+ },
103
+ ],
104
+ });
105
+ }
106
+ async stopStream(target, ref, result) {
107
+ if (!target.channelId)
108
+ throw new Error("Missing Slack channel id");
109
+ const payload = {
110
+ channel: target.channelId,
111
+ ts: ref,
112
+ markdown_text: truncateSlackMarkdown(result.text),
113
+ };
114
+ try {
115
+ await this.webClient.chat.stopStream(payload);
116
+ }
117
+ catch {
118
+ // One explicit retry keeps final-answer delivery resilient while remaining bounded.
119
+ await this.webClient.chat.stopStream(payload);
120
+ }
121
+ }
35
122
  async publishArtifact(target, artifact) {
36
123
  if (!target.channelId) {
37
124
  throw new Error("Missing Slack channel id");
@@ -1 +1 @@
1
- {"version":3,"file":"slack-sink.js","sourceRoot":"","sources":["../src/slack-sink.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,IAAI,CAAC;AAOtC,MAAM,OAAO,SAAS;IAEZ,SAAS;IACT,SAAS;IACT,QAAQ;IAHjB,YACS,SAAoB,EACpB,SAAoB,EACpB,QAA4B,EACnC;yBAHO,SAAS;yBACT,SAAS;wBACT,QAAQ;IACd,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,MAA6B,EAAE,IAAY,EAAmB;QAC/E,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC;YACpD,OAAO,EAAE,MAAM,CAAC,SAAS;YACzB,IAAI;YACJ,SAAS,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,EAAY,CAAC;QAChC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QACzC,OAAO,GAAG,CAAC;IAAA,CACX;IAED,KAAK,CAAC,aAAa,CAAC,MAA6B,EAAE,GAAW,EAAE,IAAY,EAAiB;QAC5F,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;YAChC,OAAO,EAAE,MAAM,CAAC,SAAS;YACzB,EAAE,EAAE,GAAG;YACP,IAAI;SACJ,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAAA,CAC1C;IAED,KAAK,CAAC,eAAe,CAAC,MAA6B,EAAE,QAAqB,EAAiB;QAC1F,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAChE,IAAI,CAAC;YACJ,MAAO,IAAI,CAAC,SAAS,CAAC,KAAa,CAAC,QAAQ,CAAC;gBAC5C,UAAU,EAAE,MAAM,CAAC,SAAS;gBAC5B,SAAS,EAAE,MAAM,CAAC,QAAQ;gBAC1B,IAAI,EAAE,gBAAgB,CAAC,YAAY,CAAC,IAAI,CAAC;gBACzC,QAAQ,EAAE,YAAY,CAAC,QAAQ;gBAC/B,KAAK,EAAE,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,IAAI,YAAY,CAAC,QAAQ;aAC/D,CAAC,CAAC;QACJ,CAAC;gBAAS,CAAC;YACV,MAAM,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;QAChC,CAAC;IAAA,CACD;CACD","sourcesContent":["import type { ArtifactRef, BlobStore, TransportOutputTarget, TransportSink } from \"@jobmatchme/bee-gate\";\nimport type { WebClient } from \"@slack/web-api\";\nimport { createReadStream } from \"fs\";\n\nexport interface SlackSinkObserver {\n\tposted(target: TransportOutputTarget, ref: string, text: string): void;\n\tupdated(target: TransportOutputTarget, ref: string, text: string): void;\n}\n\nexport class SlackSink implements TransportSink<string> {\n\tconstructor(\n\t\tprivate webClient: WebClient,\n\t\tprivate blobStore: BlobStore,\n\t\tprivate observer?: SlackSinkObserver,\n\t) {}\n\n\tasync postMessage(target: TransportOutputTarget, text: string): Promise<string> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\tchannel: target.channelId,\n\t\t\ttext,\n\t\t\tthread_ts: target.threadId,\n\t\t});\n\t\tconst ref = result.ts as string;\n\t\tthis.observer?.posted(target, ref, text);\n\t\treturn ref;\n\t}\n\n\tasync updateMessage(target: TransportOutputTarget, ref: string, text: string): Promise<void> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tawait this.webClient.chat.update({\n\t\t\tchannel: target.channelId,\n\t\t\tts: ref,\n\t\t\ttext,\n\t\t});\n\t\tthis.observer?.updated(target, ref, text);\n\t}\n\n\tasync publishArtifact(target: TransportOutputTarget, artifact: ArtifactRef): Promise<void> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tconst materialized = await this.blobStore.materialize(artifact);\n\t\ttry {\n\t\t\tawait (this.webClient.files as any).uploadV2({\n\t\t\t\tchannel_id: target.channelId,\n\t\t\t\tthread_ts: target.threadId,\n\t\t\t\tfile: createReadStream(materialized.path),\n\t\t\t\tfilename: materialized.filename,\n\t\t\t\ttitle: artifact.title || artifact.name || materialized.filename,\n\t\t\t});\n\t\t} finally {\n\t\t\tawait materialized.cleanup?.();\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"slack-sink.js","sourceRoot":"","sources":["../src/slack-sink.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tActionUpdate,\n\tArtifactRef,\n\tBlobStore,\n\tTransportOutputTarget,\n\tTransportSink,\n\tTransportStreamResult,\n\tTransportStreamStart,\n} from \"@jobmatchme/bee-gate\";\nimport type { WebClient } from \"@slack/web-api\";\nimport { createReadStream } from \"fs\";\n\nexport const SLACK_STREAM_HEADER = \"Ich bearbeite jetzt deine Anfrage...\";\nexport const SLACK_TASK_TEXT_LIMIT = 256;\nexport const SLACK_MARKDOWN_LIMIT = 12_000;\nexport const SLACK_MARKDOWN_TRUNCATION_SUFFIX = \"\\n\\n_[Antwort auf 12.000 Zeichen gekürzt]_\";\n\nfunction truncateCodePoints(text: string, limit: number, suffix: string): string {\n\tconst codePoints = Array.from(text);\n\tif (codePoints.length <= limit) return text;\n\tconst suffixCodePoints = Array.from(suffix);\n\treturn `${codePoints.slice(0, Math.max(0, limit - suffixCodePoints.length)).join(\"\")}${suffix}`;\n}\n\nexport function truncateSlackTaskText(text: string): string {\n\treturn truncateCodePoints(text, SLACK_TASK_TEXT_LIMIT, \"…\");\n}\n\nexport function truncateSlackMarkdown(text: string): string {\n\treturn truncateCodePoints(text, SLACK_MARKDOWN_LIMIT, SLACK_MARKDOWN_TRUNCATION_SUFFIX);\n}\n\nfunction requiredContextString(start: TransportStreamStart, key: string): string {\n\tconst value = start.context?.[key];\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`Missing Slack stream context ${key}`);\n\t}\n\treturn value;\n}\n\nexport interface SlackSinkObserver {\n\tposted(target: TransportOutputTarget, ref: string, text: string): void;\n\tupdated(target: TransportOutputTarget, ref: string, text: string): void;\n}\n\nexport class SlackSink implements TransportSink<string> {\n\tconstructor(\n\t\tprivate webClient: WebClient,\n\t\tprivate blobStore: BlobStore,\n\t\tprivate observer?: SlackSinkObserver,\n\t) {}\n\n\tasync postMessage(target: TransportOutputTarget, text: string): Promise<string> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tconst result = await this.webClient.chat.postMessage({\n\t\t\tchannel: target.channelId,\n\t\t\ttext,\n\t\t\tthread_ts: target.threadId,\n\t\t});\n\t\tconst ref = result.ts as string;\n\t\tthis.observer?.posted(target, ref, text);\n\t\treturn ref;\n\t}\n\n\tasync updateMessage(target: TransportOutputTarget, ref: string, text: string): Promise<void> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tawait this.webClient.chat.update({\n\t\t\tchannel: target.channelId,\n\t\t\tts: ref,\n\t\t\ttext,\n\t\t});\n\t\tthis.observer?.updated(target, ref, text);\n\t}\n\n\tprepareStreamText(text: string): string {\n\t\treturn truncateSlackMarkdown(text);\n\t}\n\n\tasync startStream(target: TransportOutputTarget, start: TransportStreamStart): Promise<string> {\n\t\tif (!target.channelId) throw new Error(\"Missing Slack channel id\");\n\t\tif (!target.threadId) throw new Error(\"Missing Slack stream thread timestamp\");\n\n\t\tconst headerChunk = {\n\t\t\ttype: \"blocks\",\n\t\t\tblocks: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"header\",\n\t\t\t\t\ttext: { type: \"plain_text\", text: SLACK_STREAM_HEADER },\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t\tconst result = await this.webClient.chat.startStream({\n\t\t\tchannel: target.channelId,\n\t\t\tthread_ts: target.threadId,\n\t\t\trecipient_user_id: requiredContextString(start, \"recipientUserId\"),\n\t\t\trecipient_team_id: requiredContextString(start, \"recipientTeamId\"),\n\t\t\ttask_display_mode: start.presentation || \"timeline\",\n\t\t\t// Slack supports block chunks although the current SDK's chunk union omits them.\n\t\t\tchunks: [headerChunk] as never,\n\t\t});\n\t\tif (!result.ts) throw new Error(\"Slack chat.startStream did not return a message timestamp\");\n\t\treturn result.ts;\n\t}\n\n\tasync updateStream(target: TransportOutputTarget, ref: string, action: ActionUpdate): Promise<void> {\n\t\tif (!target.channelId) throw new Error(\"Missing Slack channel id\");\n\n\t\tawait this.webClient.chat.appendStream({\n\t\t\tchannel: target.channelId,\n\t\t\tts: ref,\n\t\t\tchunks: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"task_update\",\n\t\t\t\t\tid: action.id,\n\t\t\t\t\ttitle: truncateSlackTaskText(action.title),\n\t\t\t\t\t...(action.details === undefined ? {} : { details: truncateSlackTaskText(action.details) }),\n\t\t\t\t\tstatus: action.status,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t}\n\n\tasync stopStream(target: TransportOutputTarget, ref: string, result: TransportStreamResult): Promise<void> {\n\t\tif (!target.channelId) throw new Error(\"Missing Slack channel id\");\n\t\tconst payload = {\n\t\t\tchannel: target.channelId,\n\t\t\tts: ref,\n\t\t\tmarkdown_text: truncateSlackMarkdown(result.text),\n\t\t};\n\n\t\ttry {\n\t\t\tawait this.webClient.chat.stopStream(payload);\n\t\t} catch {\n\t\t\t// One explicit retry keeps final-answer delivery resilient while remaining bounded.\n\t\t\tawait this.webClient.chat.stopStream(payload);\n\t\t}\n\t}\n\n\tasync publishArtifact(target: TransportOutputTarget, artifact: ArtifactRef): Promise<void> {\n\t\tif (!target.channelId) {\n\t\t\tthrow new Error(\"Missing Slack channel id\");\n\t\t}\n\n\t\tconst materialized = await this.blobStore.materialize(artifact);\n\t\ttry {\n\t\t\tawait (this.webClient.files as any).uploadV2({\n\t\t\t\tchannel_id: target.channelId,\n\t\t\t\tthread_ts: target.threadId,\n\t\t\t\tfile: createReadStream(materialized.path),\n\t\t\t\tfilename: materialized.filename,\n\t\t\t\ttitle: artifact.title || artifact.name || materialized.filename,\n\t\t\t});\n\t\t} finally {\n\t\t\tawait materialized.cleanup?.();\n\t\t}\n\t}\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -9,11 +9,16 @@ export interface SlackSessionConfig {
9
9
  strategy?: "channel" | "thread";
10
10
  prefix?: string;
11
11
  }
12
+ export interface SlackRouteStreamingConfig {
13
+ enabled: boolean;
14
+ taskDisplayMode?: "timeline" | "plan" | "dense";
15
+ }
12
16
  export interface SlackRouteConfig {
13
17
  id: string;
14
18
  match: SlackRouteMatch;
15
19
  worker: BeeWorkerTargetConfig;
16
20
  session?: SlackSessionConfig;
21
+ streaming?: SlackRouteStreamingConfig;
17
22
  }
18
23
  export interface SlackGatewayConfig {
19
24
  appToken: string;
@@ -88,6 +93,8 @@ export interface SlackInboundMessage {
88
93
  threadTs?: string;
89
94
  ts: string;
90
95
  userId: string;
96
+ /** Recipient workspace from the inbound event, relevant for Slack Connect channels. */
97
+ teamId?: string;
91
98
  userName?: string;
92
99
  displayName?: string;
93
100
  text: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE;QACL,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,MAAM,EAAE,uBAAuB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,iBAAiB,CAAC;IACzB,OAAO,EAAE,mBAAmB,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,gBAAgB,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB","sourcesContent":["import type { BeeWorkerTargetConfig } from \"@jobmatchme/bee-gate\";\n\nexport interface SlackRouteMatch {\n\tchannelIds?: string[];\n\tchannelNames?: string[];\n\tdm?: boolean;\n\ttextPrefix?: string;\n}\n\nexport interface SlackSessionConfig {\n\tstrategy?: \"channel\" | \"thread\";\n\tprefix?: string;\n}\n\nexport interface SlackRouteConfig {\n\tid: string;\n\tmatch: SlackRouteMatch;\n\tworker: BeeWorkerTargetConfig;\n\tsession?: SlackSessionConfig;\n}\n\nexport interface SlackGatewayConfig {\n\tappToken: string;\n\tbotToken: string;\n\tnats: {\n\t\tservers: string | string[];\n\t\tname?: string;\n\t};\n\troutes: SlackRouteConfig[];\n\thandoff?: SlackHandoffConfig;\n}\n\nexport interface SlackHandoffConfig {\n\tenabled?: boolean;\n\thost?: string;\n\tport?: number;\n\tallowedDashboardHosts?: string[];\n\troutes: SlackHandoffRouteConfig[];\n}\n\nexport interface SlackHandoffRouteConfig {\n\tid: string;\n\tlabel?: string;\n\tchannelId: string;\n\tworker: BeeWorkerTargetConfig;\n\tsession?: SlackSessionConfig;\n}\n\nexport interface SlackHandoffActor {\n\tuserId: string;\n\tuserName?: string;\n\tdisplayName?: string;\n}\n\nexport interface SlackHandoffContext {\n\tdashboardTitle?: string;\n\tdashboardUid?: string;\n\tpanelTitle?: string;\n\tpanelId?: number;\n\turl: string;\n\ttimeRange?: string;\n\tvariables?: Record<string, string>;\n}\n\nexport interface SlackHandoffRequest {\n\trouteId: string;\n\ttext: string;\n\tactor: SlackHandoffActor;\n\tcontext: SlackHandoffContext;\n}\n\nexport interface SlackHandoffReply {\n\tts: string;\n\tthreadTs?: string;\n\ttext: string;\n\tauthor?: string;\n\tisBot: boolean;\n}\n\nexport interface SlackHandoffRecord {\n\trouteId: string;\n\tchannelId: string;\n\tthreadTs: string;\n\tpermalink: string;\n\tcreatedAt: string;\n}\n\nexport interface SlackFile {\n\tid?: string;\n\tmimetype?: string;\n\tname?: string;\n\tsize?: number;\n\turl_private_download?: string;\n\turl_private?: string;\n}\n\nexport interface SlackInboundMessage {\n\ttype: \"mention\" | \"dm\";\n\tchannelId: string;\n\tchannelName?: string;\n\tthreadTs?: string;\n\tts: string;\n\tuserId: string;\n\tuserName?: string;\n\tdisplayName?: string;\n\ttext: string;\n\tfiles?: SlackFile[];\n}\n\nexport interface ResolvedSlackRoute {\n\troute: SlackRouteConfig;\n\tsessionId: string;\n\tthreadTs?: string;\n\tconversationId: string;\n}\n\nexport interface SlackGatewayContext {\n\tteamId: string;\n\tteamName?: string;\n\tbotUserId: string;\n}\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BeeWorkerTargetConfig } from \"@jobmatchme/bee-gate\";\n\nexport interface SlackRouteMatch {\n\tchannelIds?: string[];\n\tchannelNames?: string[];\n\tdm?: boolean;\n\ttextPrefix?: string;\n}\n\nexport interface SlackSessionConfig {\n\tstrategy?: \"channel\" | \"thread\";\n\tprefix?: string;\n}\nexport interface SlackRouteStreamingConfig {\n\tenabled: boolean;\n\ttaskDisplayMode?: \"timeline\" | \"plan\" | \"dense\";\n}\n\nexport interface SlackRouteConfig {\n\tid: string;\n\tmatch: SlackRouteMatch;\n\tworker: BeeWorkerTargetConfig;\n\tsession?: SlackSessionConfig;\n\tstreaming?: SlackRouteStreamingConfig;\n}\n\nexport interface SlackGatewayConfig {\n\tappToken: string;\n\tbotToken: string;\n\tnats: {\n\t\tservers: string | string[];\n\t\tname?: string;\n\t};\n\troutes: SlackRouteConfig[];\n\thandoff?: SlackHandoffConfig;\n}\n\nexport interface SlackHandoffConfig {\n\tenabled?: boolean;\n\thost?: string;\n\tport?: number;\n\tallowedDashboardHosts?: string[];\n\troutes: SlackHandoffRouteConfig[];\n}\n\nexport interface SlackHandoffRouteConfig {\n\tid: string;\n\tlabel?: string;\n\tchannelId: string;\n\tworker: BeeWorkerTargetConfig;\n\tsession?: SlackSessionConfig;\n}\n\nexport interface SlackHandoffActor {\n\tuserId: string;\n\tuserName?: string;\n\tdisplayName?: string;\n}\n\nexport interface SlackHandoffContext {\n\tdashboardTitle?: string;\n\tdashboardUid?: string;\n\tpanelTitle?: string;\n\tpanelId?: number;\n\turl: string;\n\ttimeRange?: string;\n\tvariables?: Record<string, string>;\n}\n\nexport interface SlackHandoffRequest {\n\trouteId: string;\n\ttext: string;\n\tactor: SlackHandoffActor;\n\tcontext: SlackHandoffContext;\n}\n\nexport interface SlackHandoffReply {\n\tts: string;\n\tthreadTs?: string;\n\ttext: string;\n\tauthor?: string;\n\tisBot: boolean;\n}\n\nexport interface SlackHandoffRecord {\n\trouteId: string;\n\tchannelId: string;\n\tthreadTs: string;\n\tpermalink: string;\n\tcreatedAt: string;\n}\n\nexport interface SlackFile {\n\tid?: string;\n\tmimetype?: string;\n\tname?: string;\n\tsize?: number;\n\turl_private_download?: string;\n\turl_private?: string;\n}\n\nexport interface SlackInboundMessage {\n\ttype: \"mention\" | \"dm\";\n\tchannelId: string;\n\tchannelName?: string;\n\tthreadTs?: string;\n\tts: string;\n\tuserId: string;\n\t/** Recipient workspace from the inbound event, relevant for Slack Connect channels. */\n\tteamId?: string;\n\tuserName?: string;\n\tdisplayName?: string;\n\ttext: string;\n\tfiles?: SlackFile[];\n}\n\nexport interface ResolvedSlackRoute {\n\troute: SlackRouteConfig;\n\tsessionId: string;\n\tthreadTs?: string;\n\tconversationId: string;\n}\n\nexport interface SlackGatewayContext {\n\tteamId: string;\n\tteamName?: string;\n\tbotUserId: string;\n}\n"]}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { BeeWorkerTargetConfig } from \"@jobmatchme/bee-gate\";\n\nexport interface SlackRouteMatch {\n\tchannelIds?: string[];\n\tchannelNames?: string[];\n\tdm?: boolean;\n\ttextPrefix?: string;\n}\n\nexport interface SlackSessionConfig {\n\tstrategy?: \"channel\" | \"thread\";\n\tprefix?: string;\n}\n\nexport interface SlackRouteConfig {\n\tid: string;\n\tmatch: SlackRouteMatch;\n\tworker: BeeWorkerTargetConfig;\n\tsession?: SlackSessionConfig;\n}\n\nexport interface SlackGatewayConfig {\n\tappToken: string;\n\tbotToken: string;\n\tnats: {\n\t\tservers: string | string[];\n\t\tname?: string;\n\t};\n\troutes: SlackRouteConfig[];\n\thandoff?: SlackHandoffConfig;\n}\n\nexport interface SlackHandoffConfig {\n\tenabled?: boolean;\n\thost?: string;\n\tport?: number;\n\tallowedDashboardHosts?: string[];\n\troutes: SlackHandoffRouteConfig[];\n}\n\nexport interface SlackHandoffRouteConfig {\n\tid: string;\n\tlabel?: string;\n\tchannelId: string;\n\tworker: BeeWorkerTargetConfig;\n\tsession?: SlackSessionConfig;\n}\n\nexport interface SlackHandoffActor {\n\tuserId: string;\n\tuserName?: string;\n\tdisplayName?: string;\n}\n\nexport interface SlackHandoffContext {\n\tdashboardTitle?: string;\n\tdashboardUid?: string;\n\tpanelTitle?: string;\n\tpanelId?: number;\n\turl: string;\n\ttimeRange?: string;\n\tvariables?: Record<string, string>;\n}\n\nexport interface SlackHandoffRequest {\n\trouteId: string;\n\ttext: string;\n\tactor: SlackHandoffActor;\n\tcontext: SlackHandoffContext;\n}\n\nexport interface SlackHandoffReply {\n\tts: string;\n\tthreadTs?: string;\n\ttext: string;\n\tauthor?: string;\n\tisBot: boolean;\n}\n\nexport interface SlackHandoffRecord {\n\trouteId: string;\n\tchannelId: string;\n\tthreadTs: string;\n\tpermalink: string;\n\tcreatedAt: string;\n}\n\nexport interface SlackFile {\n\tid?: string;\n\tmimetype?: string;\n\tname?: string;\n\tsize?: number;\n\turl_private_download?: string;\n\turl_private?: string;\n}\n\nexport interface SlackInboundMessage {\n\ttype: \"mention\" | \"dm\";\n\tchannelId: string;\n\tchannelName?: string;\n\tthreadTs?: string;\n\tts: string;\n\tuserId: string;\n\tuserName?: string;\n\tdisplayName?: string;\n\ttext: string;\n\tfiles?: SlackFile[];\n}\n\nexport interface ResolvedSlackRoute {\n\troute: SlackRouteConfig;\n\tsessionId: string;\n\tthreadTs?: string;\n\tconversationId: string;\n}\n\nexport interface SlackGatewayContext {\n\tteamId: string;\n\tteamName?: string;\n\tbotUserId: string;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { BeeWorkerTargetConfig } from \"@jobmatchme/bee-gate\";\n\nexport interface SlackRouteMatch {\n\tchannelIds?: string[];\n\tchannelNames?: string[];\n\tdm?: boolean;\n\ttextPrefix?: string;\n}\n\nexport interface SlackSessionConfig {\n\tstrategy?: \"channel\" | \"thread\";\n\tprefix?: string;\n}\nexport interface SlackRouteStreamingConfig {\n\tenabled: boolean;\n\ttaskDisplayMode?: \"timeline\" | \"plan\" | \"dense\";\n}\n\nexport interface SlackRouteConfig {\n\tid: string;\n\tmatch: SlackRouteMatch;\n\tworker: BeeWorkerTargetConfig;\n\tsession?: SlackSessionConfig;\n\tstreaming?: SlackRouteStreamingConfig;\n}\n\nexport interface SlackGatewayConfig {\n\tappToken: string;\n\tbotToken: string;\n\tnats: {\n\t\tservers: string | string[];\n\t\tname?: string;\n\t};\n\troutes: SlackRouteConfig[];\n\thandoff?: SlackHandoffConfig;\n}\n\nexport interface SlackHandoffConfig {\n\tenabled?: boolean;\n\thost?: string;\n\tport?: number;\n\tallowedDashboardHosts?: string[];\n\troutes: SlackHandoffRouteConfig[];\n}\n\nexport interface SlackHandoffRouteConfig {\n\tid: string;\n\tlabel?: string;\n\tchannelId: string;\n\tworker: BeeWorkerTargetConfig;\n\tsession?: SlackSessionConfig;\n}\n\nexport interface SlackHandoffActor {\n\tuserId: string;\n\tuserName?: string;\n\tdisplayName?: string;\n}\n\nexport interface SlackHandoffContext {\n\tdashboardTitle?: string;\n\tdashboardUid?: string;\n\tpanelTitle?: string;\n\tpanelId?: number;\n\turl: string;\n\ttimeRange?: string;\n\tvariables?: Record<string, string>;\n}\n\nexport interface SlackHandoffRequest {\n\trouteId: string;\n\ttext: string;\n\tactor: SlackHandoffActor;\n\tcontext: SlackHandoffContext;\n}\n\nexport interface SlackHandoffReply {\n\tts: string;\n\tthreadTs?: string;\n\ttext: string;\n\tauthor?: string;\n\tisBot: boolean;\n}\n\nexport interface SlackHandoffRecord {\n\trouteId: string;\n\tchannelId: string;\n\tthreadTs: string;\n\tpermalink: string;\n\tcreatedAt: string;\n}\n\nexport interface SlackFile {\n\tid?: string;\n\tmimetype?: string;\n\tname?: string;\n\tsize?: number;\n\turl_private_download?: string;\n\turl_private?: string;\n}\n\nexport interface SlackInboundMessage {\n\ttype: \"mention\" | \"dm\";\n\tchannelId: string;\n\tchannelName?: string;\n\tthreadTs?: string;\n\tts: string;\n\tuserId: string;\n\t/** Recipient workspace from the inbound event, relevant for Slack Connect channels. */\n\tteamId?: string;\n\tuserName?: string;\n\tdisplayName?: string;\n\ttext: string;\n\tfiles?: SlackFile[];\n}\n\nexport interface ResolvedSlackRoute {\n\troute: SlackRouteConfig;\n\tsessionId: string;\n\tthreadTs?: string;\n\tconversationId: string;\n}\n\nexport interface SlackGatewayContext {\n\tteamId: string;\n\tteamName?: string;\n\tbotUserId: string;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobmatchme/bee-slack",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Slack adapter for Bee Gate and the Bee Dance protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -48,7 +48,7 @@
48
48
  "prepublishOnly": "npm run clean && npm run check"
49
49
  },
50
50
  "dependencies": {
51
- "@jobmatchme/bee-gate": "0.1.8",
51
+ "@jobmatchme/bee-gate": "0.1.9",
52
52
  "@slack/socket-mode": "^2.0.0",
53
53
  "@slack/web-api": "^7.11.0",
54
54
  "chalk": "^5.6.2"