@jobmatchme/bee-slack 0.1.8 → 0.1.10
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 +18 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/config.js.map +1 -1
- package/dist/gateway.d.ts +1 -0
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +39 -0
- package/dist/gateway.js.map +1 -1
- package/dist/handoff.d.ts +28 -0
- package/dist/handoff.d.ts.map +1 -0
- package/dist/handoff.js +193 -0
- package/dist/handoff.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/scheduled.d.ts +1 -0
- package/dist/scheduled.d.ts.map +1 -1
- package/dist/scheduled.js +29 -10
- package/dist/scheduled.js.map +1 -1
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,11 +23,29 @@ The package is intentionally thin. It owns Slack-specific concerns:
|
|
|
23
23
|
- route matching
|
|
24
24
|
- posting and updating Slack messages
|
|
25
25
|
- uploading artifacts to Slack
|
|
26
|
+
- public handoffs from trusted internal frontends into allowlisted Slack threads
|
|
26
27
|
|
|
27
28
|
It does not own protocol orchestration itself. That responsibility stays in
|
|
28
29
|
`@jobmatchme/bee-gate`, which keeps the Slack adapter replaceable and easier to
|
|
29
30
|
compare against other frontends.
|
|
30
31
|
|
|
32
|
+
## Grafana and other authenticated handoffs
|
|
33
|
+
|
|
34
|
+
The optional handoff HTTP server lets a trusted internal frontend create a
|
|
35
|
+
public Slack thread, dispatch the same question to an allowlisted Bee worker,
|
|
36
|
+
return the Slack permalink immediately, and read the thread replies. The
|
|
37
|
+
handoff route fixes both channel and worker server-side. Keep the Service
|
|
38
|
+
cluster-internal and restrict ingress to the trusted frontend namespace. POST
|
|
39
|
+
requests require Grafana's authenticated `X-Grafana-User` data-proxy header;
|
|
40
|
+
the server replaces any browser-supplied actor identity with that value.
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
GET /health
|
|
44
|
+
GET /api/handoffs/routes
|
|
45
|
+
POST /api/handoffs
|
|
46
|
+
GET /api/handoffs/:routeId/:threadTs/replies
|
|
47
|
+
```
|
|
48
|
+
|
|
31
49
|
## Local development
|
|
32
50
|
|
|
33
51
|
For local manual testing, copy `local.config.example.json` to
|
package/dist/config.d.ts.map
CHANGED
|
@@ -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,
|
|
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"]}
|
package/dist/config.js
CHANGED
|
@@ -7,6 +7,10 @@ export function loadConfig(configPath) {
|
|
|
7
7
|
}
|
|
8
8
|
const fullPath = resolve(path);
|
|
9
9
|
const config = JSON.parse(readFileSync(fullPath, "utf-8"));
|
|
10
|
+
const handoffConfig = process.env.BEE_SLACK_HANDOFF_CONFIG;
|
|
11
|
+
if (handoffConfig) {
|
|
12
|
+
config.handoff = JSON.parse(handoffConfig);
|
|
13
|
+
}
|
|
10
14
|
if (!config.appToken)
|
|
11
15
|
throw new Error(`Missing appToken in ${fullPath}`);
|
|
12
16
|
if (!config.botToken)
|
|
@@ -17,6 +21,16 @@ export function loadConfig(configPath) {
|
|
|
17
21
|
if (!Array.isArray(config.routes) || config.routes.length === 0) {
|
|
18
22
|
throw new Error(`Missing routes in ${fullPath}`);
|
|
19
23
|
}
|
|
24
|
+
if (config.handoff?.enabled) {
|
|
25
|
+
if (!Array.isArray(config.handoff.routes) || config.handoff.routes.length === 0) {
|
|
26
|
+
throw new Error(`handoff.routes must be a non-empty array in ${fullPath}`);
|
|
27
|
+
}
|
|
28
|
+
for (const route of config.handoff.routes) {
|
|
29
|
+
if (!route.id || !route.channelId || !route.worker?.subject) {
|
|
30
|
+
throw new Error(`Every handoff route needs id, channelId and worker.subject in ${fullPath}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
20
34
|
return config;
|
|
21
35
|
}
|
|
22
36
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -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,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,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\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\treturn config;\n}\n"]}
|
|
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"]}
|
package/dist/gateway.d.ts
CHANGED
package/dist/gateway.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAiC,kBAAkB,EAAuB,MAAM,YAAY,CAAC;AAazG,qBAAa,YAAY;IAaZ,OAAO,CAAC,MAAM;IAZ1B,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;IAEpD,YAAoB,MAAM,EAAE,kBAAkB,EAU7C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAqB3B;IAED,OAAO,CAAC,kBAAkB;YAkEZ,cAAc;IA0C5B,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,iBAAiB;YAOX,mBAAmB;YA+BnB,UAAU;YAsBV,aAAa;CA0B3B;AAED,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 * 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\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}\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.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 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\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":"AAkBA,OAAO,KAAK,EAAiC,kBAAkB,EAAuB,MAAM,YAAY,CAAC;AAazG,qBAAa,YAAY;IAaZ,OAAO,CAAC,MAAM;IAZ1B,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;IAEpD,YAAoB,MAAM,EAAE,kBAAkB,EAU7C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB3B;IAED,OAAO,CAAC,kBAAkB;IAwC1B,OAAO,CAAC,kBAAkB;YAkEZ,cAAc;IA0C5B,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,iBAAiB;YAOX,mBAAmB;YA+BnB,UAAU;YAsBV,aAAa;CA0B3B;AAED,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 * 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\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}\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\tconst result = await this.webClient.conversations.replies({ channel: channelId, ts: threadTs, limit: 100 });\n\t\t\t\treturn (result.messages || []).map((message) => {\n\t\t\t\t\tconst typed = message as typeof message & {\n\t\t\t\t\t\tbot_id?: string;\n\t\t\t\t\t\tbot_profile?: { name?: string };\n\t\t\t\t\t\tusername?: string;\n\t\t\t\t\t\tthread_ts?: string;\n\t\t\t\t\t};\n\t\t\t\t\treturn {\n\t\t\t\t\t\tts: String(typed.ts || \"\"),\n\t\t\t\t\t\tthreadTs: typed.thread_ts,\n\t\t\t\t\t\ttext: typed.text || \"\",\n\t\t\t\t\t\tauthor: typed.user\n\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: typed.bot_profile?.name || typed.username || \"Bot\",\n\t\t\t\t\t\tisBot: Boolean(typed.bot_id),\n\t\t\t\t\t};\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 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\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
|
@@ -3,6 +3,7 @@ import { SocketModeClient } from "@slack/socket-mode";
|
|
|
3
3
|
import { WebClient } from "@slack/web-api";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { loadConfig } from "./config.js";
|
|
6
|
+
import { createHandoffServer, SlackHandoffController } from "./handoff.js";
|
|
6
7
|
import * as log from "./log.js";
|
|
7
8
|
import { resolveRoute } from "./router.js";
|
|
8
9
|
import { SlackSink } from "./slack-sink.js";
|
|
@@ -45,10 +46,48 @@ export class SlackGateway {
|
|
|
45
46
|
this.teamId = auth.team_id;
|
|
46
47
|
this.teamName = auth.team || null;
|
|
47
48
|
await Promise.all([this.fetchUsers(), this.fetchChannels()]);
|
|
49
|
+
this.startHandoffServer();
|
|
48
50
|
this.setupEventHandlers();
|
|
49
51
|
await this.socketClient.start();
|
|
50
52
|
log.logInfo(`Connected as ${this.botUserId} in ${this.teamId}; loaded ${this.channels.size} channels and ${this.users.size} users`);
|
|
51
53
|
}
|
|
54
|
+
startHandoffServer() {
|
|
55
|
+
const handoffConfig = this.config.handoff;
|
|
56
|
+
if (!handoffConfig?.enabled)
|
|
57
|
+
return;
|
|
58
|
+
if (!this.teamId || !this.engine)
|
|
59
|
+
throw new Error("Gateway must be initialized before handoff server startup");
|
|
60
|
+
const controller = new SlackHandoffController(handoffConfig, {
|
|
61
|
+
teamId: this.teamId,
|
|
62
|
+
postRootMessage: async (channelId, text) => this.sink.postMessage({ channelId }, text),
|
|
63
|
+
getPermalink: async (channelId, messageTs) => {
|
|
64
|
+
const result = await this.webClient.chat.getPermalink({ channel: channelId, message_ts: messageTs });
|
|
65
|
+
if (!result.permalink)
|
|
66
|
+
throw new Error("Slack did not return a permalink");
|
|
67
|
+
return result.permalink;
|
|
68
|
+
},
|
|
69
|
+
dispatch: (input) => this.engine.dispatch(input),
|
|
70
|
+
getReplies: async (channelId, threadTs) => {
|
|
71
|
+
const result = await this.webClient.conversations.replies({ channel: channelId, ts: threadTs, limit: 100 });
|
|
72
|
+
return (result.messages || []).map((message) => {
|
|
73
|
+
const typed = message;
|
|
74
|
+
return {
|
|
75
|
+
ts: String(typed.ts || ""),
|
|
76
|
+
threadTs: typed.thread_ts,
|
|
77
|
+
text: typed.text || "",
|
|
78
|
+
author: typed.user
|
|
79
|
+
? this.users.get(typed.user)?.displayName || this.users.get(typed.user)?.userName || typed.user
|
|
80
|
+
: typed.bot_profile?.name || typed.username || "Bot",
|
|
81
|
+
isBot: Boolean(typed.bot_id),
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
const server = createHandoffServer(controller);
|
|
87
|
+
const host = handoffConfig.host || "0.0.0.0";
|
|
88
|
+
const port = handoffConfig.port || 8080;
|
|
89
|
+
server.listen(port, host, () => log.logInfo(`bee-slack handoff listening on http://${host}:${port}`));
|
|
90
|
+
}
|
|
52
91
|
setupEventHandlers() {
|
|
53
92
|
this.socketClient.on("app_mention", async ({ event, ack }) => {
|
|
54
93
|
await ack();
|
package/dist/gateway.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,gBAAgB,EAIhB,mBAAmB,EACnB,kBAAkB,GAElB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAc5C,MAAM,OAAO,YAAY;IAaJ,MAAM;IAZlB,YAAY,CAAmB;IAC/B,SAAS,CAAY;IACrB,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;IACrC,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC3C,SAAS,GAAkB,IAAI,CAAC;IAChC,MAAM,GAAkB,IAAI,CAAC;IAC7B,QAAQ,GAAkB,IAAI,CAAC;IAC/B,SAAS,CAAY;IACrB,IAAI,CAAY;IAChB,MAAM,GAAoC,IAAI,CAAC;IAC/C,YAAY,GAA2B,IAAI,CAAC;IAEpD,YAAoB,MAA0B,EAAE;sBAA5B,MAAM;QACzB,IAAI,CAAC,YAAY,GAAG,IAAI,gBAAgB,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxE,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,SAAS,GAAG,IAAI,kBAAkB,CACtC,OAAO,CAAC,GAAG,CAAC,yBAAyB;YACpC,OAAO,CAAC,GAAG,CAAC,mBAAmB;YAC/B,OAAO,CAAC,GAAG,CAAC,qBAAqB;YACjC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,iBAAiB,CAAC,CACvC,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAAA,CAC1D;IAED,KAAK,CAAC,KAAK,GAAkB;QAC5B,IAAI,CAAC,YAAY,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChE,IAAI,CAAC,MAAM,GAAG,IAAI,gBAAgB,CAAC;YAClC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,MAAM,EAAE;gBACP,IAAI,EAAE,GAAG,CAAC,OAAO;gBACjB,IAAI,EAAE,GAAG,CAAC,UAAU;gBACpB,KAAK,EAAE,GAAG,CAAC,QAAQ;aACnB;SACD,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAiB,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAiB,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAI,IAAI,CAAC,IAA2B,IAAI,IAAI,CAAC;QAC1D,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAChC,GAAG,CAAC,OAAO,CACV,gBAAgB,IAAI,CAAC,SAAS,OAAO,IAAI,CAAC,MAAM,YAAY,IAAI,CAAC,QAAQ,CAAC,IAAI,iBAAiB,IAAI,CAAC,KAAK,CAAC,IAAI,QAAQ,CACtH,CAAC;IAAA,CACF;IAEO,kBAAkB,GAAS;QAClC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;YAC7D,MAAM,GAAG,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,KAOT,CAAC;YAEF,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,OAAO;YAEtC,MAAM,IAAI,CAAC,cAAc,CAAC;gBACzB,IAAI,EAAE,SAAS;gBACf,SAAS,EAAE,CAAC,CAAC,OAAO;gBACpB,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI;gBAC/C,QAAQ,EAAE,CAAC,CAAC,SAAS;gBACrB,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,MAAM,EAAE,CAAC,CAAC,IAAI;gBACd,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ;gBAC1C,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW;gBAChD,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE;gBACjD,KAAK,EAAE,CAAC,CAAC,KAAK;aACd,CAAC,CAAC;QAAA,CACH,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;YACzD,MAAM,GAAG,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,KAUT,CAAC;YAEF,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS;gBAAE,OAAO;YAC7D,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,KAAK,YAAY;gBAAE,OAAO;YAClE,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;gBAAE,OAAO;YAE1D,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC;YACrC,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;YAClF,IAAI,CAAC,IAAI,IAAI,YAAY;gBAAE,OAAO;YAClC,IAAI,CAAC,IAAI;gBAAE,OAAO;YAElB,MAAM,IAAI,CAAC,cAAc,CAAC;gBACzB,IAAI,EAAE,IAAI;gBACV,SAAS,EAAE,CAAC,CAAC,OAAO;gBACpB,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI;gBAC/C,QAAQ,EAAE,CAAC,CAAC,SAAS;gBACrB,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,MAAM,EAAE,CAAC,CAAC,IAAI;gBACd,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ;gBAC1C,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW;gBAChD,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE;gBACzD,KAAK,EAAE,CAAC,CAAC,KAAK;aACd,CAAC,CAAC;QAAA,CACH,CAAC,CAAC;IAAA,CACH;IAEO,KAAK,CAAC,cAAc,CAAC,OAA4B,EAAiB;QACzE,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE;gBAC1D,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,SAAS;aACpC,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACf,GAAG,CAAC,UAAU,CAAC,wBAAwB,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;gBAC5D,OAAO;YACR,CAAC;YAED,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC/D,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;gBAClD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;gBACpE,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;gBACjG,OAAO;YACR,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACtE,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;YACnF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,WAAW,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC3E,GAAG,CAAC,QAAQ,CAAC,6CAA6C,OAAO,CAAC,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC;YACrF,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAC1B;gBACC,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;aACnF,EACD,mBAAmB,WAAW,GAAG,CACjC,CAAC;QACH,CAAC;IAAA,CACD;IAEO,iBAAiB,CACxB,OAA4B,EAC5B,QAA4B,EAC5B,WAA4B,EAC5B,MAA6B,EACX;QAClB,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACnD,CAAC;QAED,OAAO;YACN,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;YAC7B,YAAY,EAAE;gBACb,SAAS,EAAE,OAAO;gBAClB,cAAc,EAAE,QAAQ,CAAC,cAAc;aACvC;YACD,KAAK,EAAE;gBACN,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,WAAW,EAAE,OAAO,CAAC,WAAW;aAChC;YACD,OAAO,EAAE;gBACR,IAAI,EAAE,OAAO,CAAC,IAAI;aAClB;YACD,WAAW;YACX,MAAM;SACN,CAAC;IAAA,CACF;IAEO,iBAAiB,CAAC,OAA4B,EAAE,QAA4B,EAAyB;QAC5G,OAAO;YACN,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,QAAQ,EAAE,QAAQ,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;SACpF,CAAC;IAAA,CACF;IAEO,KAAK,CAAC,mBAAmB,CAChC,QAA4B,EAC5B,OAA4B,EACD;QAC3B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAElC,MAAM,WAAW,GAAoB,EAAE,CAAC;QACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,oBAAoB,IAAI,IAAI,CAAC,WAAW,CAAC;YAC1D,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI;gBAAE,SAAS;YACjC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBACjC,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE;aAC5D,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACzF,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;YAC3D,WAAW,CAAC,IAAI,CACf,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;gBACxB,SAAS,EAAE,kBAAkB,QAAQ,CAAC,SAAS,EAAE;gBACjD,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,IAAI;gBAChB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,KAAK;aACX,CAAC,CACF,CAAC;QACH,CAAC;QACD,OAAO,WAAW,CAAC;IAAA,CACnB;IAEO,KAAK,CAAC,UAAU,GAAkB;QACzC,IAAI,MAA0B,CAAC;QAC/B,GAAG,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;YACvE,MAAM,OAAO,GAAG,MAAM,CAAC,OAEX,CAAC;YACb,IAAI,OAAO,EAAE,CAAC;gBACb,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;oBAC5B,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;wBAC3C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE;4BACvB,EAAE,EAAE,IAAI,CAAC,EAAE;4BACX,QAAQ,EAAE,IAAI,CAAC,IAAI;4BACnB,WAAW,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI;yBACxC,CAAC,CAAC;oBACJ,CAAC;gBACF,CAAC;YACF,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,iBAAiB,EAAE,WAAW,CAAC;QAChD,CAAC,QAAQ,MAAM,EAAE;IAAA,CACjB;IAEO,KAAK,CAAC,aAAa,GAAkB;QAC5C,IAAI,MAA0B,CAAC;QAC/B,GAAG,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC;gBACtD,KAAK,EAAE,mCAAmC;gBAC1C,gBAAgB,EAAE,IAAI;gBACtB,KAAK,EAAE,GAAG;gBACV,MAAM;aACN,CAAC,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,CAAC,QAA4E,CAAC;YACrG,IAAI,QAAQ,EAAE,CAAC;gBACd,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;oBAChC,IAAI,CAAC,OAAO,CAAC,EAAE;wBAAE,SAAS;oBAC1B,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;wBAClB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;oBACvE,CAAC;yBAAM,IAAI,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;wBACzD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE;4BAC7B,EAAE,EAAE,OAAO,CAAC,EAAE;4BACd,IAAI,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAE,CAAC,QAAQ,EAAE;yBACpD,CAAC,CAAC;oBACJ,CAAC;gBACF,CAAC;YACF,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,iBAAiB,EAAE,WAAW,CAAC;QAChD,CAAC,QAAQ,MAAM,EAAE;IAAA,CACjB;CACD;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,UAAmB,EAAiB;IAC7E,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;AAAA,CACtB","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 * 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\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}\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.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 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\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 * 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\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}\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\tconst result = await this.webClient.conversations.replies({ channel: channelId, ts: threadTs, limit: 100 });\n\t\t\t\treturn (result.messages || []).map((message) => {\n\t\t\t\t\tconst typed = message as typeof message & {\n\t\t\t\t\t\tbot_id?: string;\n\t\t\t\t\t\tbot_profile?: { name?: string };\n\t\t\t\t\t\tusername?: string;\n\t\t\t\t\t\tthread_ts?: string;\n\t\t\t\t\t};\n\t\t\t\t\treturn {\n\t\t\t\t\t\tts: String(typed.ts || \"\"),\n\t\t\t\t\t\tthreadTs: typed.thread_ts,\n\t\t\t\t\t\ttext: typed.text || \"\",\n\t\t\t\t\t\tauthor: typed.user\n\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: typed.bot_profile?.name || typed.username || \"Bot\",\n\t\t\t\t\t\tisBot: Boolean(typed.bot_id),\n\t\t\t\t\t};\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 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\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"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type BeeResolvedTurn } from "@jobmatchme/bee-gate";
|
|
2
|
+
import { type Server } from "http";
|
|
3
|
+
import type { SlackHandoffConfig, SlackHandoffRecord, SlackHandoffReply, SlackHandoffRequest } from "./types.js";
|
|
4
|
+
export interface SlackHandoffDependencies {
|
|
5
|
+
teamId: string;
|
|
6
|
+
postRootMessage(channelId: string, text: string): Promise<string>;
|
|
7
|
+
getPermalink(channelId: string, messageTs: string): Promise<string>;
|
|
8
|
+
dispatch(input: BeeResolvedTurn): void;
|
|
9
|
+
getReplies(channelId: string, threadTs: string): Promise<SlackHandoffReply[]>;
|
|
10
|
+
}
|
|
11
|
+
export declare class SlackHandoffController {
|
|
12
|
+
private config;
|
|
13
|
+
private dependencies;
|
|
14
|
+
private routes;
|
|
15
|
+
constructor(config: SlackHandoffConfig, dependencies: SlackHandoffDependencies);
|
|
16
|
+
publicRoutes(): Array<{
|
|
17
|
+
id: string;
|
|
18
|
+
label: string;
|
|
19
|
+
}>;
|
|
20
|
+
create(request: SlackHandoffRequest): Promise<SlackHandoffRecord>;
|
|
21
|
+
replies(routeId: string, threadTs: string): Promise<SlackHandoffReply[]>;
|
|
22
|
+
private requireRoute;
|
|
23
|
+
}
|
|
24
|
+
export declare function createHandoffServer(controller: SlackHandoffController): Server;
|
|
25
|
+
export declare function withTrustedGrafanaActor(request: SlackHandoffRequest, trustedUserHeader: string | string[] | undefined): SlackHandoffRequest;
|
|
26
|
+
export declare function renderSlackHandoffMessage(request: SlackHandoffRequest): string;
|
|
27
|
+
export declare function renderAgentRequest(request: SlackHandoffRequest): string;
|
|
28
|
+
//# sourceMappingURL=handoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff.d.ts","sourceRoot":"","sources":["../src/handoff.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,eAAe,EAIpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,MAAM,CAAC;AAC5F,OAAO,KAAK,EACX,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EAEnB,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IACvC,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;CAC9E;AAED,qBAAa,sBAAsB;IAIjC,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IAJrB,OAAO,CAAC,MAAM,CAA8C;IAE5D,YACS,MAAM,EAAE,kBAAkB,EAC1B,YAAY,EAAE,wBAAwB,EAG9C;IAEM,YAAY,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAE1D;IAEY,MAAM,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+B7E;IAEY,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAIpF;IAED,OAAO,CAAC,YAAY;CAKpB;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,sBAAsB,GAAG,MAAM,CAU9E;AAiCD,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,mBAAmB,EAC5B,iBAAiB,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAC9C,mBAAmB,CAQrB;AAmCD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAqB9E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAgBvE","sourcesContent":["import {\n\ttype BeeResolvedTurn,\n\tbuildConversationId,\n\tbuildSessionKey,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"http\";\nimport type {\n\tSlackHandoffConfig,\n\tSlackHandoffRecord,\n\tSlackHandoffReply,\n\tSlackHandoffRequest,\n\tSlackHandoffRouteConfig,\n} from \"./types.js\";\n\nconst MAX_BODY_BYTES = 32 * 1024;\nconst MAX_QUESTION_LENGTH = 4000;\nconst THREAD_TS_PATTERN = /^\\d{10,}\\.\\d{6}$/;\n\nexport interface SlackHandoffDependencies {\n\tteamId: string;\n\tpostRootMessage(channelId: string, text: string): Promise<string>;\n\tgetPermalink(channelId: string, messageTs: string): Promise<string>;\n\tdispatch(input: BeeResolvedTurn): void;\n\tgetReplies(channelId: string, threadTs: string): Promise<SlackHandoffReply[]>;\n}\n\nexport class SlackHandoffController {\n\tprivate routes = new Map<string, SlackHandoffRouteConfig>();\n\n\tconstructor(\n\t\tprivate config: SlackHandoffConfig,\n\t\tprivate dependencies: SlackHandoffDependencies,\n\t) {\n\t\tfor (const route of config.routes) this.routes.set(route.id, route);\n\t}\n\n\tpublic publicRoutes(): Array<{ id: string; label: string }> {\n\t\treturn this.config.routes.map((route) => ({ id: route.id, label: route.label || route.id }));\n\t}\n\n\tpublic async create(request: SlackHandoffRequest): Promise<SlackHandoffRecord> {\n\t\tconst route = this.requireRoute(request.routeId);\n\t\tvalidateRequest(request, this.config.allowedDashboardHosts);\n\n\t\tconst threadTs = await this.dependencies.postRootMessage(route.channelId, renderSlackHandoffMessage(request));\n\t\tconst permalink = await this.dependencies.getPermalink(route.channelId, threadTs);\n\t\tconst conversationId = buildConversationId([\"slack\", this.dependencies.teamId, route.channelId, threadTs]);\n\t\tconst sessionBase = route.session?.strategy === \"channel\" ? route.channelId : threadTs;\n\t\tconst sessionId = buildSessionKey(\n\t\t\troute.session?.prefix || route.id,\n\t\t\tbuildConversationId([\"slack\", this.dependencies.teamId, route.channelId, sessionBase]),\n\t\t);\n\t\tconst output: TransportOutputTarget = { channelId: route.channelId, threadId: threadTs };\n\t\tthis.dependencies.dispatch({\n\t\t\tsessionId,\n\t\t\tthreadId: threadTs,\n\t\t\tworker: route.worker,\n\t\t\tconversation: { transport: \"slack\", conversationId },\n\t\t\tactor: request.actor,\n\t\t\tmessage: { text: renderAgentRequest(request) },\n\t\t\tattachments: [],\n\t\t\toutput,\n\t\t});\n\n\t\treturn {\n\t\t\trouteId: route.id,\n\t\t\tchannelId: route.channelId,\n\t\t\tthreadTs,\n\t\t\tpermalink,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t};\n\t}\n\n\tpublic async replies(routeId: string, threadTs: string): Promise<SlackHandoffReply[]> {\n\t\tconst route = this.requireRoute(routeId);\n\t\tif (!THREAD_TS_PATTERN.test(threadTs)) throw new HandoffHttpError(400, \"Invalid thread timestamp\");\n\t\treturn this.dependencies.getReplies(route.channelId, threadTs);\n\t}\n\n\tprivate requireRoute(routeId: string): SlackHandoffRouteConfig {\n\t\tconst route = this.routes.get(routeId);\n\t\tif (!route) throw new HandoffHttpError(404, \"Unknown handoff route\");\n\t\treturn route;\n\t}\n}\n\nexport function createHandoffServer(controller: SlackHandoffController): Server {\n\treturn createServer(async (request, response) => {\n\t\ttry {\n\t\t\tawait handleRequest(controller, request, response);\n\t\t} catch (error) {\n\t\t\tconst status = error instanceof HandoffHttpError ? error.status : 500;\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tsendJson(response, status, { error: status === 500 ? \"Internal handoff error\" : message });\n\t\t}\n\t});\n}\n\nasync function handleRequest(\n\tcontroller: SlackHandoffController,\n\trequest: IncomingMessage,\n\tresponse: ServerResponse,\n): Promise<void> {\n\tconst method = request.method || \"GET\";\n\tconst url = new URL(request.url || \"/\", \"http://bee-slack.internal\");\n\tif (method === \"GET\" && url.pathname === \"/health\") {\n\t\tsendJson(response, 200, { ok: true, service: \"bee-slack-handoff\" });\n\t\treturn;\n\t}\n\tif (method === \"GET\" && url.pathname === \"/api/handoffs/routes\") {\n\t\tsendJson(response, 200, { routes: controller.publicRoutes() });\n\t\treturn;\n\t}\n\tif (method === \"POST\" && url.pathname === \"/api/handoffs\") {\n\t\tconst body = withTrustedGrafanaActor(\n\t\t\tawait readJsonBody<SlackHandoffRequest>(request),\n\t\t\trequest.headers[\"x-grafana-user\"],\n\t\t);\n\t\tsendJson(response, 201, { handoff: await controller.create(body) });\n\t\treturn;\n\t}\n\tconst match = url.pathname.match(/^\\/api\\/handoffs\\/([^/]+)\\/([^/]+)\\/replies$/);\n\tif (method === \"GET\" && match) {\n\t\tsendJson(response, 200, { replies: await controller.replies(decodeURIComponent(match[1]), match[2]) });\n\t\treturn;\n\t}\n\tthrow new HandoffHttpError(404, \"Not found\");\n}\n\nexport function withTrustedGrafanaActor(\n\trequest: SlackHandoffRequest,\n\ttrustedUserHeader: string | string[] | undefined,\n): SlackHandoffRequest {\n\tif (typeof trustedUserHeader !== \"string\" || !trustedUserHeader.trim()) {\n\t\tthrow new HandoffHttpError(401, \"Authenticated Grafana user header is required\");\n\t}\n\treturn {\n\t\t...request,\n\t\tactor: { userId: trustedUserHeader, userName: trustedUserHeader },\n\t};\n}\n\nasync function readJsonBody<T>(request: IncomingMessage): Promise<T> {\n\tlet body = \"\";\n\tfor await (const chunk of request) {\n\t\tbody += chunk;\n\t\tif (Buffer.byteLength(body) > MAX_BODY_BYTES) throw new HandoffHttpError(413, \"Request body too large\");\n\t}\n\ttry {\n\t\treturn JSON.parse(body) as T;\n\t} catch {\n\t\tthrow new HandoffHttpError(400, \"Invalid JSON body\");\n\t}\n}\n\nfunction validateRequest(request: SlackHandoffRequest, allowedHosts?: string[]): void {\n\tif (!request || typeof request !== \"object\") throw new HandoffHttpError(400, \"Request body is required\");\n\tif (!request.text?.trim()) throw new HandoffHttpError(400, \"Question is required\");\n\tif (request.text.length > MAX_QUESTION_LENGTH) throw new HandoffHttpError(400, \"Question is too long\");\n\tif (!request.actor?.userId?.trim()) throw new HandoffHttpError(400, \"Actor userId is required\");\n\tif (!request.context?.url?.trim()) throw new HandoffHttpError(400, \"Grafana context URL is required\");\n\tlet contextUrl: URL;\n\ttry {\n\t\tcontextUrl = new URL(request.context.url);\n\t} catch {\n\t\tthrow new HandoffHttpError(400, \"Grafana context URL is invalid\");\n\t}\n\tif (contextUrl.protocol !== \"https:\" && contextUrl.hostname !== \"localhost\") {\n\t\tthrow new HandoffHttpError(400, \"Grafana context URL must use HTTPS\");\n\t}\n\tif (allowedHosts?.length && !allowedHosts.includes(contextUrl.hostname)) {\n\t\tthrow new HandoffHttpError(400, \"Grafana context host is not allowed\");\n\t}\n}\n\nexport function renderSlackHandoffMessage(request: SlackHandoffRequest): string {\n\tconst actor = request.actor.displayName || request.actor.userName || request.actor.userId;\n\tconst context = request.context;\n\tconst title = context.panelTitle || context.dashboardTitle || \"Grafana\";\n\tconst details = [\n\t\tcontext.dashboardTitle ? `*Dashboard:* ${escapeSlack(context.dashboardTitle)}` : undefined,\n\t\tcontext.panelTitle ? `*Panel:* ${escapeSlack(context.panelTitle)}` : undefined,\n\t\tcontext.timeRange ? `*Zeitraum:* ${escapeSlack(context.timeRange)}` : undefined,\n\t\t...Object.entries(context.variables || {}).map(\n\t\t\t([name, value]) => `*${escapeSlack(name)}:* ${escapeSlack(value)}`,\n\t\t),\n\t].filter((value): value is string => Boolean(value));\n\treturn [\n\t\t`:honeybee: *Frage aus Grafana · ${escapeSlack(title)}*`,\n\t\t`*Von:* ${escapeSlack(actor)}`,\n\t\t...details,\n\t\t\"\",\n\t\tescapeSlack(request.text.trim()),\n\t\t\"\",\n\t\t`<${escapeSlack(context.url)}|Dashboard-Kontext öffnen>`,\n\t].join(\"\\n\");\n}\n\nexport function renderAgentRequest(request: SlackHandoffRequest): string {\n\tconst variables = Object.entries(request.context.variables || {})\n\t\t.map(([name, value]) => `- ${name}: ${value}`)\n\t\t.join(\"\\n\");\n\treturn [\n\t\trequest.text.trim(),\n\t\t\"\",\n\t\t\"Grafana-Kontext:\",\n\t\trequest.context.dashboardTitle ? `- Dashboard: ${request.context.dashboardTitle}` : undefined,\n\t\trequest.context.panelTitle ? `- Panel: ${request.context.panelTitle}` : undefined,\n\t\trequest.context.timeRange ? `- Zeitraum: ${request.context.timeRange}` : undefined,\n\t\tvariables || undefined,\n\t\t`- URL: ${request.context.url}`,\n\t]\n\t\t.filter((value): value is string => Boolean(value))\n\t\t.join(\"\\n\");\n}\n\nfunction escapeSlack(value: string): string {\n\treturn value.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nfunction sendJson(response: ServerResponse, status: number, body: unknown): void {\n\tresponse.writeHead(status, { \"content-type\": \"application/json; charset=utf-8\", \"cache-control\": \"no-store\" });\n\tresponse.end(JSON.stringify(body));\n}\n\nclass HandoffHttpError extends Error {\n\tconstructor(\n\t\tpublic status: number,\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t}\n}\n"]}
|
package/dist/handoff.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { buildConversationId, buildSessionKey, } from "@jobmatchme/bee-gate";
|
|
2
|
+
import { createServer } from "http";
|
|
3
|
+
const MAX_BODY_BYTES = 32 * 1024;
|
|
4
|
+
const MAX_QUESTION_LENGTH = 4000;
|
|
5
|
+
const THREAD_TS_PATTERN = /^\d{10,}\.\d{6}$/;
|
|
6
|
+
export class SlackHandoffController {
|
|
7
|
+
config;
|
|
8
|
+
dependencies;
|
|
9
|
+
routes = new Map();
|
|
10
|
+
constructor(config, dependencies) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.dependencies = dependencies;
|
|
13
|
+
for (const route of config.routes)
|
|
14
|
+
this.routes.set(route.id, route);
|
|
15
|
+
}
|
|
16
|
+
publicRoutes() {
|
|
17
|
+
return this.config.routes.map((route) => ({ id: route.id, label: route.label || route.id }));
|
|
18
|
+
}
|
|
19
|
+
async create(request) {
|
|
20
|
+
const route = this.requireRoute(request.routeId);
|
|
21
|
+
validateRequest(request, this.config.allowedDashboardHosts);
|
|
22
|
+
const threadTs = await this.dependencies.postRootMessage(route.channelId, renderSlackHandoffMessage(request));
|
|
23
|
+
const permalink = await this.dependencies.getPermalink(route.channelId, threadTs);
|
|
24
|
+
const conversationId = buildConversationId(["slack", this.dependencies.teamId, route.channelId, threadTs]);
|
|
25
|
+
const sessionBase = route.session?.strategy === "channel" ? route.channelId : threadTs;
|
|
26
|
+
const sessionId = buildSessionKey(route.session?.prefix || route.id, buildConversationId(["slack", this.dependencies.teamId, route.channelId, sessionBase]));
|
|
27
|
+
const output = { channelId: route.channelId, threadId: threadTs };
|
|
28
|
+
this.dependencies.dispatch({
|
|
29
|
+
sessionId,
|
|
30
|
+
threadId: threadTs,
|
|
31
|
+
worker: route.worker,
|
|
32
|
+
conversation: { transport: "slack", conversationId },
|
|
33
|
+
actor: request.actor,
|
|
34
|
+
message: { text: renderAgentRequest(request) },
|
|
35
|
+
attachments: [],
|
|
36
|
+
output,
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
routeId: route.id,
|
|
40
|
+
channelId: route.channelId,
|
|
41
|
+
threadTs,
|
|
42
|
+
permalink,
|
|
43
|
+
createdAt: new Date().toISOString(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async replies(routeId, threadTs) {
|
|
47
|
+
const route = this.requireRoute(routeId);
|
|
48
|
+
if (!THREAD_TS_PATTERN.test(threadTs))
|
|
49
|
+
throw new HandoffHttpError(400, "Invalid thread timestamp");
|
|
50
|
+
return this.dependencies.getReplies(route.channelId, threadTs);
|
|
51
|
+
}
|
|
52
|
+
requireRoute(routeId) {
|
|
53
|
+
const route = this.routes.get(routeId);
|
|
54
|
+
if (!route)
|
|
55
|
+
throw new HandoffHttpError(404, "Unknown handoff route");
|
|
56
|
+
return route;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function createHandoffServer(controller) {
|
|
60
|
+
return createServer(async (request, response) => {
|
|
61
|
+
try {
|
|
62
|
+
await handleRequest(controller, request, response);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const status = error instanceof HandoffHttpError ? error.status : 500;
|
|
66
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
67
|
+
sendJson(response, status, { error: status === 500 ? "Internal handoff error" : message });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async function handleRequest(controller, request, response) {
|
|
72
|
+
const method = request.method || "GET";
|
|
73
|
+
const url = new URL(request.url || "/", "http://bee-slack.internal");
|
|
74
|
+
if (method === "GET" && url.pathname === "/health") {
|
|
75
|
+
sendJson(response, 200, { ok: true, service: "bee-slack-handoff" });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (method === "GET" && url.pathname === "/api/handoffs/routes") {
|
|
79
|
+
sendJson(response, 200, { routes: controller.publicRoutes() });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (method === "POST" && url.pathname === "/api/handoffs") {
|
|
83
|
+
const body = withTrustedGrafanaActor(await readJsonBody(request), request.headers["x-grafana-user"]);
|
|
84
|
+
sendJson(response, 201, { handoff: await controller.create(body) });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const match = url.pathname.match(/^\/api\/handoffs\/([^/]+)\/([^/]+)\/replies$/);
|
|
88
|
+
if (method === "GET" && match) {
|
|
89
|
+
sendJson(response, 200, { replies: await controller.replies(decodeURIComponent(match[1]), match[2]) });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
throw new HandoffHttpError(404, "Not found");
|
|
93
|
+
}
|
|
94
|
+
export function withTrustedGrafanaActor(request, trustedUserHeader) {
|
|
95
|
+
if (typeof trustedUserHeader !== "string" || !trustedUserHeader.trim()) {
|
|
96
|
+
throw new HandoffHttpError(401, "Authenticated Grafana user header is required");
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
...request,
|
|
100
|
+
actor: { userId: trustedUserHeader, userName: trustedUserHeader },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function readJsonBody(request) {
|
|
104
|
+
let body = "";
|
|
105
|
+
for await (const chunk of request) {
|
|
106
|
+
body += chunk;
|
|
107
|
+
if (Buffer.byteLength(body) > MAX_BODY_BYTES)
|
|
108
|
+
throw new HandoffHttpError(413, "Request body too large");
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(body);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
throw new HandoffHttpError(400, "Invalid JSON body");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function validateRequest(request, allowedHosts) {
|
|
118
|
+
if (!request || typeof request !== "object")
|
|
119
|
+
throw new HandoffHttpError(400, "Request body is required");
|
|
120
|
+
if (!request.text?.trim())
|
|
121
|
+
throw new HandoffHttpError(400, "Question is required");
|
|
122
|
+
if (request.text.length > MAX_QUESTION_LENGTH)
|
|
123
|
+
throw new HandoffHttpError(400, "Question is too long");
|
|
124
|
+
if (!request.actor?.userId?.trim())
|
|
125
|
+
throw new HandoffHttpError(400, "Actor userId is required");
|
|
126
|
+
if (!request.context?.url?.trim())
|
|
127
|
+
throw new HandoffHttpError(400, "Grafana context URL is required");
|
|
128
|
+
let contextUrl;
|
|
129
|
+
try {
|
|
130
|
+
contextUrl = new URL(request.context.url);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
throw new HandoffHttpError(400, "Grafana context URL is invalid");
|
|
134
|
+
}
|
|
135
|
+
if (contextUrl.protocol !== "https:" && contextUrl.hostname !== "localhost") {
|
|
136
|
+
throw new HandoffHttpError(400, "Grafana context URL must use HTTPS");
|
|
137
|
+
}
|
|
138
|
+
if (allowedHosts?.length && !allowedHosts.includes(contextUrl.hostname)) {
|
|
139
|
+
throw new HandoffHttpError(400, "Grafana context host is not allowed");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export function renderSlackHandoffMessage(request) {
|
|
143
|
+
const actor = request.actor.displayName || request.actor.userName || request.actor.userId;
|
|
144
|
+
const context = request.context;
|
|
145
|
+
const title = context.panelTitle || context.dashboardTitle || "Grafana";
|
|
146
|
+
const details = [
|
|
147
|
+
context.dashboardTitle ? `*Dashboard:* ${escapeSlack(context.dashboardTitle)}` : undefined,
|
|
148
|
+
context.panelTitle ? `*Panel:* ${escapeSlack(context.panelTitle)}` : undefined,
|
|
149
|
+
context.timeRange ? `*Zeitraum:* ${escapeSlack(context.timeRange)}` : undefined,
|
|
150
|
+
...Object.entries(context.variables || {}).map(([name, value]) => `*${escapeSlack(name)}:* ${escapeSlack(value)}`),
|
|
151
|
+
].filter((value) => Boolean(value));
|
|
152
|
+
return [
|
|
153
|
+
`:honeybee: *Frage aus Grafana · ${escapeSlack(title)}*`,
|
|
154
|
+
`*Von:* ${escapeSlack(actor)}`,
|
|
155
|
+
...details,
|
|
156
|
+
"",
|
|
157
|
+
escapeSlack(request.text.trim()),
|
|
158
|
+
"",
|
|
159
|
+
`<${escapeSlack(context.url)}|Dashboard-Kontext öffnen>`,
|
|
160
|
+
].join("\n");
|
|
161
|
+
}
|
|
162
|
+
export function renderAgentRequest(request) {
|
|
163
|
+
const variables = Object.entries(request.context.variables || {})
|
|
164
|
+
.map(([name, value]) => `- ${name}: ${value}`)
|
|
165
|
+
.join("\n");
|
|
166
|
+
return [
|
|
167
|
+
request.text.trim(),
|
|
168
|
+
"",
|
|
169
|
+
"Grafana-Kontext:",
|
|
170
|
+
request.context.dashboardTitle ? `- Dashboard: ${request.context.dashboardTitle}` : undefined,
|
|
171
|
+
request.context.panelTitle ? `- Panel: ${request.context.panelTitle}` : undefined,
|
|
172
|
+
request.context.timeRange ? `- Zeitraum: ${request.context.timeRange}` : undefined,
|
|
173
|
+
variables || undefined,
|
|
174
|
+
`- URL: ${request.context.url}`,
|
|
175
|
+
]
|
|
176
|
+
.filter((value) => Boolean(value))
|
|
177
|
+
.join("\n");
|
|
178
|
+
}
|
|
179
|
+
function escapeSlack(value) {
|
|
180
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
181
|
+
}
|
|
182
|
+
function sendJson(response, status, body) {
|
|
183
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
184
|
+
response.end(JSON.stringify(body));
|
|
185
|
+
}
|
|
186
|
+
class HandoffHttpError extends Error {
|
|
187
|
+
status;
|
|
188
|
+
constructor(status, message) {
|
|
189
|
+
super(message);
|
|
190
|
+
this.status = status;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff.js","sourceRoot":"","sources":["../src/handoff.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype BeeResolvedTurn,\n\tbuildConversationId,\n\tbuildSessionKey,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"http\";\nimport type {\n\tSlackHandoffConfig,\n\tSlackHandoffRecord,\n\tSlackHandoffReply,\n\tSlackHandoffRequest,\n\tSlackHandoffRouteConfig,\n} from \"./types.js\";\n\nconst MAX_BODY_BYTES = 32 * 1024;\nconst MAX_QUESTION_LENGTH = 4000;\nconst THREAD_TS_PATTERN = /^\\d{10,}\\.\\d{6}$/;\n\nexport interface SlackHandoffDependencies {\n\tteamId: string;\n\tpostRootMessage(channelId: string, text: string): Promise<string>;\n\tgetPermalink(channelId: string, messageTs: string): Promise<string>;\n\tdispatch(input: BeeResolvedTurn): void;\n\tgetReplies(channelId: string, threadTs: string): Promise<SlackHandoffReply[]>;\n}\n\nexport class SlackHandoffController {\n\tprivate routes = new Map<string, SlackHandoffRouteConfig>();\n\n\tconstructor(\n\t\tprivate config: SlackHandoffConfig,\n\t\tprivate dependencies: SlackHandoffDependencies,\n\t) {\n\t\tfor (const route of config.routes) this.routes.set(route.id, route);\n\t}\n\n\tpublic publicRoutes(): Array<{ id: string; label: string }> {\n\t\treturn this.config.routes.map((route) => ({ id: route.id, label: route.label || route.id }));\n\t}\n\n\tpublic async create(request: SlackHandoffRequest): Promise<SlackHandoffRecord> {\n\t\tconst route = this.requireRoute(request.routeId);\n\t\tvalidateRequest(request, this.config.allowedDashboardHosts);\n\n\t\tconst threadTs = await this.dependencies.postRootMessage(route.channelId, renderSlackHandoffMessage(request));\n\t\tconst permalink = await this.dependencies.getPermalink(route.channelId, threadTs);\n\t\tconst conversationId = buildConversationId([\"slack\", this.dependencies.teamId, route.channelId, threadTs]);\n\t\tconst sessionBase = route.session?.strategy === \"channel\" ? route.channelId : threadTs;\n\t\tconst sessionId = buildSessionKey(\n\t\t\troute.session?.prefix || route.id,\n\t\t\tbuildConversationId([\"slack\", this.dependencies.teamId, route.channelId, sessionBase]),\n\t\t);\n\t\tconst output: TransportOutputTarget = { channelId: route.channelId, threadId: threadTs };\n\t\tthis.dependencies.dispatch({\n\t\t\tsessionId,\n\t\t\tthreadId: threadTs,\n\t\t\tworker: route.worker,\n\t\t\tconversation: { transport: \"slack\", conversationId },\n\t\t\tactor: request.actor,\n\t\t\tmessage: { text: renderAgentRequest(request) },\n\t\t\tattachments: [],\n\t\t\toutput,\n\t\t});\n\n\t\treturn {\n\t\t\trouteId: route.id,\n\t\t\tchannelId: route.channelId,\n\t\t\tthreadTs,\n\t\t\tpermalink,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t};\n\t}\n\n\tpublic async replies(routeId: string, threadTs: string): Promise<SlackHandoffReply[]> {\n\t\tconst route = this.requireRoute(routeId);\n\t\tif (!THREAD_TS_PATTERN.test(threadTs)) throw new HandoffHttpError(400, \"Invalid thread timestamp\");\n\t\treturn this.dependencies.getReplies(route.channelId, threadTs);\n\t}\n\n\tprivate requireRoute(routeId: string): SlackHandoffRouteConfig {\n\t\tconst route = this.routes.get(routeId);\n\t\tif (!route) throw new HandoffHttpError(404, \"Unknown handoff route\");\n\t\treturn route;\n\t}\n}\n\nexport function createHandoffServer(controller: SlackHandoffController): Server {\n\treturn createServer(async (request, response) => {\n\t\ttry {\n\t\t\tawait handleRequest(controller, request, response);\n\t\t} catch (error) {\n\t\t\tconst status = error instanceof HandoffHttpError ? error.status : 500;\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tsendJson(response, status, { error: status === 500 ? \"Internal handoff error\" : message });\n\t\t}\n\t});\n}\n\nasync function handleRequest(\n\tcontroller: SlackHandoffController,\n\trequest: IncomingMessage,\n\tresponse: ServerResponse,\n): Promise<void> {\n\tconst method = request.method || \"GET\";\n\tconst url = new URL(request.url || \"/\", \"http://bee-slack.internal\");\n\tif (method === \"GET\" && url.pathname === \"/health\") {\n\t\tsendJson(response, 200, { ok: true, service: \"bee-slack-handoff\" });\n\t\treturn;\n\t}\n\tif (method === \"GET\" && url.pathname === \"/api/handoffs/routes\") {\n\t\tsendJson(response, 200, { routes: controller.publicRoutes() });\n\t\treturn;\n\t}\n\tif (method === \"POST\" && url.pathname === \"/api/handoffs\") {\n\t\tconst body = withTrustedGrafanaActor(\n\t\t\tawait readJsonBody<SlackHandoffRequest>(request),\n\t\t\trequest.headers[\"x-grafana-user\"],\n\t\t);\n\t\tsendJson(response, 201, { handoff: await controller.create(body) });\n\t\treturn;\n\t}\n\tconst match = url.pathname.match(/^\\/api\\/handoffs\\/([^/]+)\\/([^/]+)\\/replies$/);\n\tif (method === \"GET\" && match) {\n\t\tsendJson(response, 200, { replies: await controller.replies(decodeURIComponent(match[1]), match[2]) });\n\t\treturn;\n\t}\n\tthrow new HandoffHttpError(404, \"Not found\");\n}\n\nexport function withTrustedGrafanaActor(\n\trequest: SlackHandoffRequest,\n\ttrustedUserHeader: string | string[] | undefined,\n): SlackHandoffRequest {\n\tif (typeof trustedUserHeader !== \"string\" || !trustedUserHeader.trim()) {\n\t\tthrow new HandoffHttpError(401, \"Authenticated Grafana user header is required\");\n\t}\n\treturn {\n\t\t...request,\n\t\tactor: { userId: trustedUserHeader, userName: trustedUserHeader },\n\t};\n}\n\nasync function readJsonBody<T>(request: IncomingMessage): Promise<T> {\n\tlet body = \"\";\n\tfor await (const chunk of request) {\n\t\tbody += chunk;\n\t\tif (Buffer.byteLength(body) > MAX_BODY_BYTES) throw new HandoffHttpError(413, \"Request body too large\");\n\t}\n\ttry {\n\t\treturn JSON.parse(body) as T;\n\t} catch {\n\t\tthrow new HandoffHttpError(400, \"Invalid JSON body\");\n\t}\n}\n\nfunction validateRequest(request: SlackHandoffRequest, allowedHosts?: string[]): void {\n\tif (!request || typeof request !== \"object\") throw new HandoffHttpError(400, \"Request body is required\");\n\tif (!request.text?.trim()) throw new HandoffHttpError(400, \"Question is required\");\n\tif (request.text.length > MAX_QUESTION_LENGTH) throw new HandoffHttpError(400, \"Question is too long\");\n\tif (!request.actor?.userId?.trim()) throw new HandoffHttpError(400, \"Actor userId is required\");\n\tif (!request.context?.url?.trim()) throw new HandoffHttpError(400, \"Grafana context URL is required\");\n\tlet contextUrl: URL;\n\ttry {\n\t\tcontextUrl = new URL(request.context.url);\n\t} catch {\n\t\tthrow new HandoffHttpError(400, \"Grafana context URL is invalid\");\n\t}\n\tif (contextUrl.protocol !== \"https:\" && contextUrl.hostname !== \"localhost\") {\n\t\tthrow new HandoffHttpError(400, \"Grafana context URL must use HTTPS\");\n\t}\n\tif (allowedHosts?.length && !allowedHosts.includes(contextUrl.hostname)) {\n\t\tthrow new HandoffHttpError(400, \"Grafana context host is not allowed\");\n\t}\n}\n\nexport function renderSlackHandoffMessage(request: SlackHandoffRequest): string {\n\tconst actor = request.actor.displayName || request.actor.userName || request.actor.userId;\n\tconst context = request.context;\n\tconst title = context.panelTitle || context.dashboardTitle || \"Grafana\";\n\tconst details = [\n\t\tcontext.dashboardTitle ? `*Dashboard:* ${escapeSlack(context.dashboardTitle)}` : undefined,\n\t\tcontext.panelTitle ? `*Panel:* ${escapeSlack(context.panelTitle)}` : undefined,\n\t\tcontext.timeRange ? `*Zeitraum:* ${escapeSlack(context.timeRange)}` : undefined,\n\t\t...Object.entries(context.variables || {}).map(\n\t\t\t([name, value]) => `*${escapeSlack(name)}:* ${escapeSlack(value)}`,\n\t\t),\n\t].filter((value): value is string => Boolean(value));\n\treturn [\n\t\t`:honeybee: *Frage aus Grafana · ${escapeSlack(title)}*`,\n\t\t`*Von:* ${escapeSlack(actor)}`,\n\t\t...details,\n\t\t\"\",\n\t\tescapeSlack(request.text.trim()),\n\t\t\"\",\n\t\t`<${escapeSlack(context.url)}|Dashboard-Kontext öffnen>`,\n\t].join(\"\\n\");\n}\n\nexport function renderAgentRequest(request: SlackHandoffRequest): string {\n\tconst variables = Object.entries(request.context.variables || {})\n\t\t.map(([name, value]) => `- ${name}: ${value}`)\n\t\t.join(\"\\n\");\n\treturn [\n\t\trequest.text.trim(),\n\t\t\"\",\n\t\t\"Grafana-Kontext:\",\n\t\trequest.context.dashboardTitle ? `- Dashboard: ${request.context.dashboardTitle}` : undefined,\n\t\trequest.context.panelTitle ? `- Panel: ${request.context.panelTitle}` : undefined,\n\t\trequest.context.timeRange ? `- Zeitraum: ${request.context.timeRange}` : undefined,\n\t\tvariables || undefined,\n\t\t`- URL: ${request.context.url}`,\n\t]\n\t\t.filter((value): value is string => Boolean(value))\n\t\t.join(\"\\n\");\n}\n\nfunction escapeSlack(value: string): string {\n\treturn value.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nfunction sendJson(response: ServerResponse, status: number, body: unknown): void {\n\tresponse.writeHead(status, { \"content-type\": \"application/json; charset=utf-8\", \"cache-control\": \"no-store\" });\n\tresponse.end(JSON.stringify(body));\n}\n\nclass HandoffHttpError extends Error {\n\tconstructor(\n\t\tpublic status: number,\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t}\n}\n"]}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./config.js\";\nexport * from \"./gateway.js\";\nexport * from \"./router.js\";\nexport * from \"./scheduled.js\";\nexport * from \"./slack-sink.js\";\nexport * from \"./types.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./config.js\";\nexport * from \"./gateway.js\";\nexport * from \"./handoff.js\";\nexport * from \"./router.js\";\nexport * from \"./scheduled.js\";\nexport * from \"./slack-sink.js\";\nexport * from \"./types.js\";\n"]}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./config.js\";\nexport * from \"./gateway.js\";\nexport * from \"./router.js\";\nexport * from \"./scheduled.js\";\nexport * from \"./slack-sink.js\";\nexport * from \"./types.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./config.js\";\nexport * from \"./gateway.js\";\nexport * from \"./handoff.js\";\nexport * from \"./router.js\";\nexport * from \"./scheduled.js\";\nexport * from \"./slack-sink.js\";\nexport * from \"./types.js\";\n"]}
|
package/dist/scheduled.d.ts
CHANGED
|
@@ -20,4 +20,5 @@ export interface SlackScheduledRunConfig {
|
|
|
20
20
|
export declare function loadScheduledRunConfig(jobPath?: string): SlackScheduledRunConfig;
|
|
21
21
|
export declare function runScheduledSlackTurnFromFiles(configPath?: string, jobPath?: string): Promise<void>;
|
|
22
22
|
export declare function runScheduledSlackTurn(gatewayConfig: SlackGatewayConfig, scheduledRun: SlackScheduledRunConfig): Promise<void>;
|
|
23
|
+
export declare function renderScheduledFinalMessage(requestText: string | undefined, responseText: string): string;
|
|
23
24
|
//# sourceMappingURL=scheduled.d.ts.map
|
package/dist/scheduled.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled.d.ts","sourceRoot":"","sources":["../src/scheduled.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QACP,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,KAAK,CAAC,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AA8BD,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,uBAAuB,CAYhF;AAED,wBAAsB,8BAA8B,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzG;AAED,wBAAsB,qBAAqB,CAC1C,aAAa,EAAE,kBAAkB,EACjC,YAAY,EAAE,uBAAuB,GACnC,OAAO,CAAC,IAAI,CAAC,CAqEf","sourcesContent":["import {\n\ttype ArtifactRef,\n\ttype BeeResolvedTurn,\n\ttype BeeRunEvent,\n\ttype BeeWorkerClient,\n\tbuildConversationId,\n\tbuildSessionKey,\n\tcreateNatsBeeClient,\n\tLocalFileBlobStore,\n\tnewTurnId,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { WebClient } from \"@slack/web-api\";\nimport { readFileSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { loadConfig } from \"./config.js\";\nimport * as log from \"./log.js\";\nimport { SlackSink } from \"./slack-sink.js\";\nimport type { SlackGatewayConfig } from \"./types.js\";\n\nexport interface SlackScheduledRunConfig {\n\tid: string;\n\trouteId: string;\n\ttext: string;\n\ttarget: {\n\t\tslackUserId?: string;\n\t\tchannelId?: string;\n\t\tthreadTs?: string;\n\t};\n\tactor?: {\n\t\tuserId: string;\n\t\tuserName?: string;\n\t\tdisplayName?: string;\n\t};\n\tsessionId?: string;\n\tsessionPrefix?: string;\n\tconversationId?: string;\n}\n\ninterface RenderState {\n\tstatusRef?: string;\n\tlatestText: string;\n\titemTexts: Map<string, string>;\n}\n\ntype ItemPartLike = { kind: string; [key: string]: unknown };\n\ntype ItemAppendedPayload = {\n\teventType: \"item.appended\";\n\titem: { id: string; kind: string; role: string; parts: ItemPartLike[] };\n};\n\ntype ItemUpdatedPayload = {\n\teventType: \"item.updated\";\n\titemId: string;\n\tappendParts?: ItemPartLike[];\n};\n\ntype RunFailedPayload = {\n\teventType: \"run.failed\";\n\terror: string;\n};\n\ntype ApprovalRequestedPayload = {\n\tsummary: string;\n};\n\nexport function loadScheduledRunConfig(jobPath?: string): SlackScheduledRunConfig {\n\tconst path = jobPath || process.env.BEE_SLACK_SCHEDULED_RUN_CONFIG;\n\tif (!path) {\n\t\tthrow new Error(\n\t\t\t\"Missing scheduled run config path; pass it as second argument or set BEE_SLACK_SCHEDULED_RUN_CONFIG\",\n\t\t);\n\t}\n\n\tconst fullPath = resolve(path);\n\tconst config = JSON.parse(readFileSync(fullPath, \"utf-8\")) as SlackScheduledRunConfig;\n\tvalidateScheduledRunConfig(config, fullPath);\n\treturn config;\n}\n\nexport async function runScheduledSlackTurnFromFiles(configPath?: string, jobPath?: string): Promise<void> {\n\tconst gatewayConfig = loadConfig(configPath);\n\tconst scheduledRun = loadScheduledRunConfig(jobPath);\n\tawait runScheduledSlackTurn(gatewayConfig, scheduledRun);\n}\n\nexport async function runScheduledSlackTurn(\n\tgatewayConfig: SlackGatewayConfig,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<void> {\n\tvalidateScheduledRunConfig(scheduledRun, \"scheduled run config\");\n\tconst route = gatewayConfig.routes.find((candidate) => candidate.id === scheduledRun.routeId);\n\tif (!route) {\n\t\tthrow new Error(`Scheduled run ${scheduledRun.id} references unknown routeId ${scheduledRun.routeId}`);\n\t}\n\n\tconst webClient = new WebClient(gatewayConfig.botToken);\n\tconst blobStore = new LocalFileBlobStore(\n\t\tprocess.env.BEE_SLACK_BLOB_STORE_ROOT ||\n\t\t\tprocess.env.BEE_BLOB_STORE_ROOT ||\n\t\t\tprocess.env.HUDAI_BLOB_STORE_ROOT ||\n\t\t\tjoin(process.cwd(), \".bee-blob-store\"),\n\t);\n\tconst sink = new SlackSink(webClient, blobStore);\n\tconst workerClient = await createNatsBeeClient(gatewayConfig.nats);\n\n\ttry {\n\t\tconst auth = await webClient.auth.test();\n\t\tconst teamId = String(auth.team_id || \"unknown-team\");\n\t\tconst output = await resolveScheduledOutputTarget(webClient, scheduledRun);\n\t\tconst actor = await resolveScheduledActor(webClient, scheduledRun);\n\t\tif (!output.threadId) {\n\t\t\toutput.threadId = await sink.postMessage(\n\t\t\t\t{ channelId: output.channelId },\n\t\t\t\t`:alarm_clock: Scheduled Bee run \\`${scheduledRun.id}\\` started. Reply in this thread to continue the session.`,\n\t\t\t);\n\t\t}\n\t\tconst slackConversationId = buildConversationId([\n\t\t\t\"slack\",\n\t\t\tteamId,\n\t\t\toutput.channelId || \"unknown-channel\",\n\t\t\toutput.threadId,\n\t\t]);\n\t\tconst conversationId = scheduledRun.conversationId || slackConversationId;\n\t\tconst sessionBase =\n\t\t\troute.session?.strategy === \"channel\" ? output.channelId || \"unknown-channel\" : output.threadId;\n\t\tconst routeSessionId = buildSessionKey(\n\t\t\tscheduledRun.sessionPrefix || route.session?.prefix || route.id,\n\t\t\tbuildConversationId([\"slack\", teamId, output.channelId || \"unknown-channel\", sessionBase || output.threadId]),\n\t\t);\n\t\tconst sessionId = scheduledRun.sessionId || routeSessionId;\n\n\t\tconst input: BeeResolvedTurn = {\n\t\t\tsessionId,\n\t\t\tthreadId: output.threadId,\n\t\t\tworker: route.worker,\n\t\t\tconversation: {\n\t\t\t\ttransport: \"slack\",\n\t\t\t\tconversationId,\n\t\t\t},\n\t\t\tactor,\n\t\t\tmessage: {\n\t\t\t\ttext: scheduledRun.text,\n\t\t\t},\n\t\t\tattachments: [],\n\t\t\toutput,\n\t\t};\n\n\t\tlog.logInfo(`Starting scheduled Slack run ${scheduledRun.id} on route ${route.id}`);\n\t\tawait streamScheduledTurn(workerClient, sink, input);\n\t\tlog.logInfo(`Completed scheduled Slack run ${scheduledRun.id}`);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog.logError(`Scheduled Slack run ${scheduledRun.id} failed`, message);\n\t\tthrow error;\n\t} finally {\n\t\tawait workerClient.close?.();\n\t}\n}\n\nasync function resolveScheduledOutputTarget(\n\twebClient: WebClient,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<TransportOutputTarget> {\n\tif (scheduledRun.target.channelId) {\n\t\treturn {\n\t\t\tchannelId: scheduledRun.target.channelId,\n\t\t\tthreadId: scheduledRun.target.threadTs,\n\t\t};\n\t}\n\n\tif (!scheduledRun.target.slackUserId) {\n\t\tthrow new Error(`Scheduled run ${scheduledRun.id} needs target.slackUserId or target.channelId`);\n\t}\n\n\tconst result = await webClient.conversations.open({ users: scheduledRun.target.slackUserId });\n\tconst channel = result.channel as { id?: string } | undefined;\n\tif (!channel?.id) {\n\t\tthrow new Error(`Slack conversations.open did not return a channel for user ${scheduledRun.target.slackUserId}`);\n\t}\n\treturn {\n\t\tchannelId: channel.id,\n\t\tthreadId: scheduledRun.target.threadTs,\n\t};\n}\n\nasync function resolveScheduledActor(\n\twebClient: WebClient,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<BeeResolvedTurn[\"actor\"]> {\n\tif (scheduledRun.actor) return scheduledRun.actor;\n\tif (!scheduledRun.target.slackUserId) {\n\t\treturn {\n\t\t\tuserId: `scheduler:${scheduledRun.id}`,\n\t\t\tdisplayName: \"Bee Scheduler\",\n\t\t};\n\t}\n\n\ttry {\n\t\tconst info = await webClient.users.info({ user: scheduledRun.target.slackUserId });\n\t\tconst user = info.user as\n\t\t\t| { id?: string; name?: string; real_name?: string; profile?: { display_name?: string } }\n\t\t\t| undefined;\n\t\treturn {\n\t\t\tuserId: scheduledRun.target.slackUserId,\n\t\t\tuserName: user?.name,\n\t\t\tdisplayName: user?.profile?.display_name || user?.real_name || user?.name,\n\t\t};\n\t} catch (error) {\n\t\tlog.logWarning(\n\t\t\t`Could not resolve Slack user ${scheduledRun.target.slackUserId}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t\treturn {\n\t\t\tuserId: scheduledRun.target.slackUserId,\n\t\t\tdisplayName: scheduledRun.target.slackUserId,\n\t\t};\n\t}\n}\n\nasync function streamScheduledTurn(\n\tworkerClient: BeeWorkerClient,\n\tsink: SlackSink,\n\tinput: BeeResolvedTurn,\n): Promise<void> {\n\tconst state: RenderState = {\n\t\tlatestText: \"_Working..._\",\n\t\titemTexts: new Map<string, string>(),\n\t};\n\tconst request = {\n\t\tsessionId: input.sessionId,\n\t\tthreadId: input.threadId,\n\t\tturnId: newTurnId(),\n\t\tconversation: input.conversation,\n\t\tactor: input.actor,\n\t\tmessage: input.message,\n\t\tattachments: input.attachments,\n\t};\n\n\ttry {\n\t\tawait workerClient.streamTurn(input.worker, request, async (event) => {\n\t\t\tawait handleScheduledEvent(sink, input.output, event, state);\n\t\t});\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tawait sink.postMessage(input.output, `_Scheduled gateway error: ${message}_`);\n\t\tthrow error;\n\t}\n}\n\nasync function handleScheduledEvent(\n\tsink: SlackSink,\n\toutput: TransportOutputTarget,\n\tevent: BeeRunEvent,\n\tstate: RenderState,\n): Promise<void> {\n\tif (event.name === \"run.started\") {\n\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\treturn;\n\t}\n\n\tif (event.name === \"run.completed\") {\n\t\tif (!state.statusRef) {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"run.failed\") {\n\t\tconst errorText = `_Error: ${asRunFailedPayload(event).error}_`;\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, errorText);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, errorText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"approval.requested\") {\n\t\tawait sink.postMessage(output, `Approval requested: ${asApprovalRequestedPayload(event).summary}`);\n\t\treturn;\n\t}\n\n\tif (event.name === \"item.appended\") {\n\t\tconst payload = asItemAppendedPayload(event);\n\t\tconst text = renderParts(payload.item.parts);\n\t\tstate.itemTexts.set(payload.item.id, text);\n\t\tif (payload.item.kind === \"artifact\") {\n\t\t\tconst artifact = firstArtifactRef(payload.item.parts);\n\t\t\tif (artifact && artifactHasPayload(artifact)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait sink.publishArtifact(output, artifact);\n\t\t\t\t\treturn;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tawait sink.postMessage(output, `${text}\\n_Artifact upload failed: ${message}_`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait sink.postMessage(output, text);\n\t\t\treturn;\n\t\t}\n\t\tstate.latestText = text;\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, text);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, text);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"item.updated\") {\n\t\tconst payload = asItemUpdatedPayload(event);\n\t\tconst current = state.itemTexts.get(payload.itemId) || \"\";\n\t\tconst appended = renderParts(payload.appendParts || []);\n\t\tconst next = current ? `${current}${appended}` : appended;\n\t\tstate.itemTexts.set(payload.itemId, next);\n\t\tstate.latestText = next;\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, next);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, next);\n\t\t}\n\t}\n}\n\nfunction firstArtifactRef(parts: ItemPartLike[]): ArtifactRef | undefined {\n\tconst part = parts.find((entry) => entry.kind === \"artifactRef\");\n\tif (!part) return undefined;\n\treturn {\n\t\tartifactId: String(part.artifactId || \"artifact\"),\n\t\tblobKey: typeof part.blobKey === \"string\" ? part.blobKey : undefined,\n\t\tname: typeof part.name === \"string\" ? part.name : undefined,\n\t\ttitle: typeof part.title === \"string\" ? part.title : undefined,\n\t\tmimeType: typeof part.mimeType === \"string\" ? part.mimeType : undefined,\n\t\turi: typeof part.uri === \"string\" ? part.uri : undefined,\n\t\tsizeBytes: typeof part.sizeBytes === \"number\" ? part.sizeBytes : undefined,\n\t};\n}\n\nfunction artifactHasPayload(artifact: ArtifactRef): boolean {\n\treturn !!(artifact.uri || artifact.blobKey);\n}\n\nfunction renderParts(parts: ItemPartLike[]): string {\n\treturn parts\n\t\t.map((part) => {\n\t\t\tif (part.kind === \"text\") return String(part.text || \"\");\n\t\t\tif (part.kind === \"status\") return String(part.status || \"\");\n\t\t\tif (part.kind === \"artifactRef\") {\n\t\t\t\treturn `Artifact: ${String(part.title || part.name || part.artifactId || \"artifact\")}`;\n\t\t\t}\n\t\t\tif (part.kind === \"approval\" || part.kind === \"choice\") {\n\t\t\t\treturn `${String(part.title || \"\")}\\n${String(part.summary || \"\")}`.trim();\n\t\t\t}\n\t\t\tif (part.kind === \"form\") return String(part.title || \"\");\n\t\t\tif (part.kind === \"log\") return String(part.text || \"\");\n\t\t\tif (part.kind === \"patch\" || part.kind === \"diff\") {\n\t\t\t\tconst files = Array.isArray(part.files) ? (part.files as Array<{ path?: unknown }>) : [];\n\t\t\t\treturn files.map((entry) => `File: ${String(entry.path || \"\")}`).join(\"\\n\");\n\t\t\t}\n\t\t\treturn JSON.stringify(part);\n\t\t})\n\t\t.filter(Boolean)\n\t\t.join(\"\\n\");\n}\n\nfunction validateScheduledRunConfig(config: SlackScheduledRunConfig, source: string): void {\n\tif (!config.id) throw new Error(`Missing id in ${source}`);\n\tif (!config.routeId) throw new Error(`Missing routeId in ${source}`);\n\tif (!config.text) throw new Error(`Missing text in ${source}`);\n\tif (!config.target) throw new Error(`Missing target in ${source}`);\n\tif (!config.target.slackUserId && !config.target.channelId) {\n\t\tthrow new Error(`Missing target.slackUserId or target.channelId in ${source}`);\n\t}\n}\n\nfunction asRunFailedPayload(event: BeeRunEvent): RunFailedPayload {\n\treturn event.payload as RunFailedPayload;\n}\n\nfunction asApprovalRequestedPayload(event: BeeRunEvent): ApprovalRequestedPayload {\n\treturn event.payload as ApprovalRequestedPayload;\n}\n\nfunction asItemAppendedPayload(event: BeeRunEvent): ItemAppendedPayload {\n\treturn event.payload as ItemAppendedPayload;\n}\n\nfunction asItemUpdatedPayload(event: BeeRunEvent): ItemUpdatedPayload {\n\treturn event.payload as ItemUpdatedPayload;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scheduled.d.ts","sourceRoot":"","sources":["../src/scheduled.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QACP,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,KAAK,CAAC,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AA+BD,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,uBAAuB,CAYhF;AAED,wBAAsB,8BAA8B,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzG;AAED,wBAAsB,qBAAqB,CAC1C,aAAa,EAAE,kBAAkB,EACjC,YAAY,EAAE,uBAAuB,GACnC,OAAO,CAAC,IAAI,CAAC,CAoEf;AA8LD,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAUzG","sourcesContent":["import {\n\ttype ArtifactRef,\n\ttype BeeResolvedTurn,\n\ttype BeeRunEvent,\n\ttype BeeWorkerClient,\n\tbuildConversationId,\n\tbuildSessionKey,\n\tcreateNatsBeeClient,\n\tLocalFileBlobStore,\n\tnewTurnId,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { WebClient } from \"@slack/web-api\";\nimport { readFileSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { loadConfig } from \"./config.js\";\nimport * as log from \"./log.js\";\nimport { SlackSink } from \"./slack-sink.js\";\nimport type { SlackGatewayConfig } from \"./types.js\";\n\nexport interface SlackScheduledRunConfig {\n\tid: string;\n\trouteId: string;\n\ttext: string;\n\ttarget: {\n\t\tslackUserId?: string;\n\t\tchannelId?: string;\n\t\tthreadTs?: string;\n\t};\n\tactor?: {\n\t\tuserId: string;\n\t\tuserName?: string;\n\t\tdisplayName?: string;\n\t};\n\tsessionId?: string;\n\tsessionPrefix?: string;\n\tconversationId?: string;\n}\n\ninterface RenderState {\n\tstatusRef?: string;\n\tlatestText: string;\n\trequestText?: string;\n\titemTexts: Map<string, string>;\n}\n\ntype ItemPartLike = { kind: string; [key: string]: unknown };\n\ntype ItemAppendedPayload = {\n\teventType: \"item.appended\";\n\titem: { id: string; kind: string; role: string; parts: ItemPartLike[] };\n};\n\ntype ItemUpdatedPayload = {\n\teventType: \"item.updated\";\n\titemId: string;\n\tappendParts?: ItemPartLike[];\n};\n\ntype RunFailedPayload = {\n\teventType: \"run.failed\";\n\terror: string;\n};\n\ntype ApprovalRequestedPayload = {\n\tsummary: string;\n};\n\nexport function loadScheduledRunConfig(jobPath?: string): SlackScheduledRunConfig {\n\tconst path = jobPath || process.env.BEE_SLACK_SCHEDULED_RUN_CONFIG;\n\tif (!path) {\n\t\tthrow new Error(\n\t\t\t\"Missing scheduled run config path; pass it as second argument or set BEE_SLACK_SCHEDULED_RUN_CONFIG\",\n\t\t);\n\t}\n\n\tconst fullPath = resolve(path);\n\tconst config = JSON.parse(readFileSync(fullPath, \"utf-8\")) as SlackScheduledRunConfig;\n\tvalidateScheduledRunConfig(config, fullPath);\n\treturn config;\n}\n\nexport async function runScheduledSlackTurnFromFiles(configPath?: string, jobPath?: string): Promise<void> {\n\tconst gatewayConfig = loadConfig(configPath);\n\tconst scheduledRun = loadScheduledRunConfig(jobPath);\n\tawait runScheduledSlackTurn(gatewayConfig, scheduledRun);\n}\n\nexport async function runScheduledSlackTurn(\n\tgatewayConfig: SlackGatewayConfig,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<void> {\n\tvalidateScheduledRunConfig(scheduledRun, \"scheduled run config\");\n\tconst route = gatewayConfig.routes.find((candidate) => candidate.id === scheduledRun.routeId);\n\tif (!route) {\n\t\tthrow new Error(`Scheduled run ${scheduledRun.id} references unknown routeId ${scheduledRun.routeId}`);\n\t}\n\n\tconst webClient = new WebClient(gatewayConfig.botToken);\n\tconst blobStore = new LocalFileBlobStore(\n\t\tprocess.env.BEE_SLACK_BLOB_STORE_ROOT ||\n\t\t\tprocess.env.BEE_BLOB_STORE_ROOT ||\n\t\t\tprocess.env.HUDAI_BLOB_STORE_ROOT ||\n\t\t\tjoin(process.cwd(), \".bee-blob-store\"),\n\t);\n\tconst sink = new SlackSink(webClient, blobStore);\n\tconst workerClient = await createNatsBeeClient(gatewayConfig.nats);\n\n\ttry {\n\t\tconst auth = await webClient.auth.test();\n\t\tconst teamId = String(auth.team_id || \"unknown-team\");\n\t\tconst output = await resolveScheduledOutputTarget(webClient, scheduledRun);\n\t\tconst actor = await resolveScheduledActor(webClient, scheduledRun);\n\t\tlet statusRef: string | undefined;\n\t\tif (!output.threadId) {\n\t\t\tstatusRef = await sink.postMessage({ channelId: output.channelId }, \"_Working..._\");\n\t\t\toutput.threadId = statusRef;\n\t\t}\n\t\tconst slackConversationId = buildConversationId([\n\t\t\t\"slack\",\n\t\t\tteamId,\n\t\t\toutput.channelId || \"unknown-channel\",\n\t\t\toutput.threadId,\n\t\t]);\n\t\tconst conversationId = scheduledRun.conversationId || slackConversationId;\n\t\tconst sessionBase =\n\t\t\troute.session?.strategy === \"channel\" ? output.channelId || \"unknown-channel\" : output.threadId;\n\t\tconst routeSessionId = buildSessionKey(\n\t\t\tscheduledRun.sessionPrefix || route.session?.prefix || route.id,\n\t\t\tbuildConversationId([\"slack\", teamId, output.channelId || \"unknown-channel\", sessionBase || output.threadId]),\n\t\t);\n\t\tconst sessionId = scheduledRun.sessionId || routeSessionId;\n\n\t\tconst input: BeeResolvedTurn = {\n\t\t\tsessionId,\n\t\t\tthreadId: output.threadId,\n\t\t\tworker: route.worker,\n\t\t\tconversation: {\n\t\t\t\ttransport: \"slack\",\n\t\t\t\tconversationId,\n\t\t\t},\n\t\t\tactor,\n\t\t\tmessage: {\n\t\t\t\ttext: scheduledRun.text,\n\t\t\t},\n\t\t\tattachments: [],\n\t\t\toutput,\n\t\t};\n\n\t\tlog.logInfo(`Starting scheduled Slack run ${scheduledRun.id} on route ${route.id}`);\n\t\tawait streamScheduledTurn(workerClient, sink, input, statusRef);\n\t\tlog.logInfo(`Completed scheduled Slack run ${scheduledRun.id}`);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog.logError(`Scheduled Slack run ${scheduledRun.id} failed`, message);\n\t\tthrow error;\n\t} finally {\n\t\tawait workerClient.close?.();\n\t}\n}\n\nasync function resolveScheduledOutputTarget(\n\twebClient: WebClient,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<TransportOutputTarget> {\n\tif (scheduledRun.target.channelId) {\n\t\treturn {\n\t\t\tchannelId: scheduledRun.target.channelId,\n\t\t\tthreadId: scheduledRun.target.threadTs,\n\t\t};\n\t}\n\n\tif (!scheduledRun.target.slackUserId) {\n\t\tthrow new Error(`Scheduled run ${scheduledRun.id} needs target.slackUserId or target.channelId`);\n\t}\n\n\tconst result = await webClient.conversations.open({ users: scheduledRun.target.slackUserId });\n\tconst channel = result.channel as { id?: string } | undefined;\n\tif (!channel?.id) {\n\t\tthrow new Error(`Slack conversations.open did not return a channel for user ${scheduledRun.target.slackUserId}`);\n\t}\n\treturn {\n\t\tchannelId: channel.id,\n\t\tthreadId: scheduledRun.target.threadTs,\n\t};\n}\n\nasync function resolveScheduledActor(\n\twebClient: WebClient,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<BeeResolvedTurn[\"actor\"]> {\n\tif (scheduledRun.actor) return scheduledRun.actor;\n\tif (!scheduledRun.target.slackUserId) {\n\t\treturn {\n\t\t\tuserId: `scheduler:${scheduledRun.id}`,\n\t\t\tdisplayName: \"Bee Scheduler\",\n\t\t};\n\t}\n\n\ttry {\n\t\tconst info = await webClient.users.info({ user: scheduledRun.target.slackUserId });\n\t\tconst user = info.user as\n\t\t\t| { id?: string; name?: string; real_name?: string; profile?: { display_name?: string } }\n\t\t\t| undefined;\n\t\treturn {\n\t\t\tuserId: scheduledRun.target.slackUserId,\n\t\t\tuserName: user?.name,\n\t\t\tdisplayName: user?.profile?.display_name || user?.real_name || user?.name,\n\t\t};\n\t} catch (error) {\n\t\tlog.logWarning(\n\t\t\t`Could not resolve Slack user ${scheduledRun.target.slackUserId}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t\treturn {\n\t\t\tuserId: scheduledRun.target.slackUserId,\n\t\t\tdisplayName: scheduledRun.target.slackUserId,\n\t\t};\n\t}\n}\n\nasync function streamScheduledTurn(\n\tworkerClient: BeeWorkerClient,\n\tsink: SlackSink,\n\tinput: BeeResolvedTurn,\n\tstatusRef?: string,\n): Promise<void> {\n\tconst state: RenderState = {\n\t\tstatusRef,\n\t\tlatestText: \"_Working..._\",\n\t\trequestText: input.message.text,\n\t\titemTexts: new Map<string, string>(),\n\t};\n\tconst request = {\n\t\tsessionId: input.sessionId,\n\t\tthreadId: input.threadId,\n\t\tturnId: newTurnId(),\n\t\tconversation: input.conversation,\n\t\tactor: input.actor,\n\t\tmessage: input.message,\n\t\tattachments: input.attachments,\n\t};\n\n\ttry {\n\t\tawait workerClient.streamTurn(input.worker, request, async (event) => {\n\t\t\tawait handleScheduledEvent(sink, input.output, event, state);\n\t\t});\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tawait sink.postMessage(input.output, `_Scheduled gateway error: ${message}_`);\n\t\tthrow error;\n\t}\n}\n\nasync function handleScheduledEvent(\n\tsink: SlackSink,\n\toutput: TransportOutputTarget,\n\tevent: BeeRunEvent,\n\tstate: RenderState,\n): Promise<void> {\n\tif (event.name === \"run.started\") {\n\t\tif (!state.statusRef) {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"run.completed\") {\n\t\tif (!state.statusRef) {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"run.failed\") {\n\t\tconst errorText = `_Error: ${asRunFailedPayload(event).error}_`;\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, errorText);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, errorText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"approval.requested\") {\n\t\tawait sink.postMessage(output, `Approval requested: ${asApprovalRequestedPayload(event).summary}`);\n\t\treturn;\n\t}\n\n\tif (event.name === \"item.appended\") {\n\t\tconst payload = asItemAppendedPayload(event);\n\t\tconst text = renderParts(payload.item.parts);\n\t\tstate.itemTexts.set(payload.item.id, text);\n\t\tif (payload.item.kind === \"artifact\") {\n\t\t\tconst artifact = firstArtifactRef(payload.item.parts);\n\t\t\tif (artifact && artifactHasPayload(artifact)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait sink.publishArtifact(output, artifact);\n\t\t\t\t\treturn;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tawait sink.postMessage(output, `${text}\\n_Artifact upload failed: ${message}_`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait sink.postMessage(output, text);\n\t\t\treturn;\n\t\t}\n\t\tstate.latestText = renderScheduledFinalMessage(state.requestText, text);\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, state.latestText);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"item.updated\") {\n\t\tconst payload = asItemUpdatedPayload(event);\n\t\tconst current = state.itemTexts.get(payload.itemId) || \"\";\n\t\tconst appended = renderParts(payload.appendParts || []);\n\t\tconst next = current ? `${current}${appended}` : appended;\n\t\tstate.itemTexts.set(payload.itemId, next);\n\t\tstate.latestText = renderScheduledFinalMessage(state.requestText, next);\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, state.latestText);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t}\n}\n\nfunction firstArtifactRef(parts: ItemPartLike[]): ArtifactRef | undefined {\n\tconst part = parts.find((entry) => entry.kind === \"artifactRef\");\n\tif (!part) return undefined;\n\treturn {\n\t\tartifactId: String(part.artifactId || \"artifact\"),\n\t\tblobKey: typeof part.blobKey === \"string\" ? part.blobKey : undefined,\n\t\tname: typeof part.name === \"string\" ? part.name : undefined,\n\t\ttitle: typeof part.title === \"string\" ? part.title : undefined,\n\t\tmimeType: typeof part.mimeType === \"string\" ? part.mimeType : undefined,\n\t\turi: typeof part.uri === \"string\" ? part.uri : undefined,\n\t\tsizeBytes: typeof part.sizeBytes === \"number\" ? part.sizeBytes : undefined,\n\t};\n}\n\nfunction artifactHasPayload(artifact: ArtifactRef): boolean {\n\treturn !!(artifact.uri || artifact.blobKey);\n}\n\nexport function renderScheduledFinalMessage(requestText: string | undefined, responseText: string): string {\n\tconst request = requestText?.trim();\n\tif (!request) return responseText;\n\n\tconst quotedRequest = request\n\t\t.split(/\\r?\\n/)\n\t\t.map((line) => `> ${escapeSlackText(line)}`)\n\t\t.join(\"\\n\");\n\n\treturn [\"*Request:*\", quotedRequest, \"*Antwort:*\", responseText].filter(Boolean).join(\"\\n\\n\");\n}\n\nfunction escapeSlackText(text: string): string {\n\treturn text.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nfunction renderParts(parts: ItemPartLike[]): string {\n\treturn parts\n\t\t.map((part) => {\n\t\t\tif (part.kind === \"text\") return String(part.text || \"\");\n\t\t\tif (part.kind === \"status\") return String(part.status || \"\");\n\t\t\tif (part.kind === \"artifactRef\") {\n\t\t\t\treturn `Artifact: ${String(part.title || part.name || part.artifactId || \"artifact\")}`;\n\t\t\t}\n\t\t\tif (part.kind === \"approval\" || part.kind === \"choice\") {\n\t\t\t\treturn `${String(part.title || \"\")}\\n${String(part.summary || \"\")}`.trim();\n\t\t\t}\n\t\t\tif (part.kind === \"form\") return String(part.title || \"\");\n\t\t\tif (part.kind === \"log\") return String(part.text || \"\");\n\t\t\tif (part.kind === \"patch\" || part.kind === \"diff\") {\n\t\t\t\tconst files = Array.isArray(part.files) ? (part.files as Array<{ path?: unknown }>) : [];\n\t\t\t\treturn files.map((entry) => `File: ${String(entry.path || \"\")}`).join(\"\\n\");\n\t\t\t}\n\t\t\treturn JSON.stringify(part);\n\t\t})\n\t\t.filter(Boolean)\n\t\t.join(\"\\n\");\n}\n\nfunction validateScheduledRunConfig(config: SlackScheduledRunConfig, source: string): void {\n\tif (!config.id) throw new Error(`Missing id in ${source}`);\n\tif (!config.routeId) throw new Error(`Missing routeId in ${source}`);\n\tif (!config.text) throw new Error(`Missing text in ${source}`);\n\tif (!config.target) throw new Error(`Missing target in ${source}`);\n\tif (!config.target.slackUserId && !config.target.channelId) {\n\t\tthrow new Error(`Missing target.slackUserId or target.channelId in ${source}`);\n\t}\n}\n\nfunction asRunFailedPayload(event: BeeRunEvent): RunFailedPayload {\n\treturn event.payload as RunFailedPayload;\n}\n\nfunction asApprovalRequestedPayload(event: BeeRunEvent): ApprovalRequestedPayload {\n\treturn event.payload as ApprovalRequestedPayload;\n}\n\nfunction asItemAppendedPayload(event: BeeRunEvent): ItemAppendedPayload {\n\treturn event.payload as ItemAppendedPayload;\n}\n\nfunction asItemUpdatedPayload(event: BeeRunEvent): ItemUpdatedPayload {\n\treturn event.payload as ItemUpdatedPayload;\n}\n"]}
|
package/dist/scheduled.js
CHANGED
|
@@ -38,8 +38,10 @@ export async function runScheduledSlackTurn(gatewayConfig, scheduledRun) {
|
|
|
38
38
|
const teamId = String(auth.team_id || "unknown-team");
|
|
39
39
|
const output = await resolveScheduledOutputTarget(webClient, scheduledRun);
|
|
40
40
|
const actor = await resolveScheduledActor(webClient, scheduledRun);
|
|
41
|
+
let statusRef;
|
|
41
42
|
if (!output.threadId) {
|
|
42
|
-
|
|
43
|
+
statusRef = await sink.postMessage({ channelId: output.channelId }, "_Working..._");
|
|
44
|
+
output.threadId = statusRef;
|
|
43
45
|
}
|
|
44
46
|
const slackConversationId = buildConversationId([
|
|
45
47
|
"slack",
|
|
@@ -67,7 +69,7 @@ export async function runScheduledSlackTurn(gatewayConfig, scheduledRun) {
|
|
|
67
69
|
output,
|
|
68
70
|
};
|
|
69
71
|
log.logInfo(`Starting scheduled Slack run ${scheduledRun.id} on route ${route.id}`);
|
|
70
|
-
await streamScheduledTurn(workerClient, sink, input);
|
|
72
|
+
await streamScheduledTurn(workerClient, sink, input, statusRef);
|
|
71
73
|
log.logInfo(`Completed scheduled Slack run ${scheduledRun.id}`);
|
|
72
74
|
}
|
|
73
75
|
catch (error) {
|
|
@@ -125,9 +127,11 @@ async function resolveScheduledActor(webClient, scheduledRun) {
|
|
|
125
127
|
};
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
|
-
async function streamScheduledTurn(workerClient, sink, input) {
|
|
130
|
+
async function streamScheduledTurn(workerClient, sink, input, statusRef) {
|
|
129
131
|
const state = {
|
|
132
|
+
statusRef,
|
|
130
133
|
latestText: "_Working..._",
|
|
134
|
+
requestText: input.message.text,
|
|
131
135
|
itemTexts: new Map(),
|
|
132
136
|
};
|
|
133
137
|
const request = {
|
|
@@ -152,7 +156,9 @@ async function streamScheduledTurn(workerClient, sink, input) {
|
|
|
152
156
|
}
|
|
153
157
|
async function handleScheduledEvent(sink, output, event, state) {
|
|
154
158
|
if (event.name === "run.started") {
|
|
155
|
-
|
|
159
|
+
if (!state.statusRef) {
|
|
160
|
+
state.statusRef = await sink.postMessage(output, state.latestText);
|
|
161
|
+
}
|
|
156
162
|
return;
|
|
157
163
|
}
|
|
158
164
|
if (event.name === "run.completed") {
|
|
@@ -195,12 +201,12 @@ async function handleScheduledEvent(sink, output, event, state) {
|
|
|
195
201
|
await sink.postMessage(output, text);
|
|
196
202
|
return;
|
|
197
203
|
}
|
|
198
|
-
state.latestText = text;
|
|
204
|
+
state.latestText = renderScheduledFinalMessage(state.requestText, text);
|
|
199
205
|
if (state.statusRef) {
|
|
200
|
-
await sink.updateMessage(output, state.statusRef,
|
|
206
|
+
await sink.updateMessage(output, state.statusRef, state.latestText);
|
|
201
207
|
}
|
|
202
208
|
else {
|
|
203
|
-
state.statusRef = await sink.postMessage(output,
|
|
209
|
+
state.statusRef = await sink.postMessage(output, state.latestText);
|
|
204
210
|
}
|
|
205
211
|
return;
|
|
206
212
|
}
|
|
@@ -210,12 +216,12 @@ async function handleScheduledEvent(sink, output, event, state) {
|
|
|
210
216
|
const appended = renderParts(payload.appendParts || []);
|
|
211
217
|
const next = current ? `${current}${appended}` : appended;
|
|
212
218
|
state.itemTexts.set(payload.itemId, next);
|
|
213
|
-
state.latestText = next;
|
|
219
|
+
state.latestText = renderScheduledFinalMessage(state.requestText, next);
|
|
214
220
|
if (state.statusRef) {
|
|
215
|
-
await sink.updateMessage(output, state.statusRef,
|
|
221
|
+
await sink.updateMessage(output, state.statusRef, state.latestText);
|
|
216
222
|
}
|
|
217
223
|
else {
|
|
218
|
-
state.statusRef = await sink.postMessage(output,
|
|
224
|
+
state.statusRef = await sink.postMessage(output, state.latestText);
|
|
219
225
|
}
|
|
220
226
|
}
|
|
221
227
|
}
|
|
@@ -236,6 +242,19 @@ function firstArtifactRef(parts) {
|
|
|
236
242
|
function artifactHasPayload(artifact) {
|
|
237
243
|
return !!(artifact.uri || artifact.blobKey);
|
|
238
244
|
}
|
|
245
|
+
export function renderScheduledFinalMessage(requestText, responseText) {
|
|
246
|
+
const request = requestText?.trim();
|
|
247
|
+
if (!request)
|
|
248
|
+
return responseText;
|
|
249
|
+
const quotedRequest = request
|
|
250
|
+
.split(/\r?\n/)
|
|
251
|
+
.map((line) => `> ${escapeSlackText(line)}`)
|
|
252
|
+
.join("\n");
|
|
253
|
+
return ["*Request:*", quotedRequest, "*Antwort:*", responseText].filter(Boolean).join("\n\n");
|
|
254
|
+
}
|
|
255
|
+
function escapeSlackText(text) {
|
|
256
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
257
|
+
}
|
|
239
258
|
function renderParts(parts) {
|
|
240
259
|
return parts
|
|
241
260
|
.map((part) => {
|
package/dist/scheduled.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduled.js","sourceRoot":"","sources":["../src/scheduled.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype ArtifactRef,\n\ttype BeeResolvedTurn,\n\ttype BeeRunEvent,\n\ttype BeeWorkerClient,\n\tbuildConversationId,\n\tbuildSessionKey,\n\tcreateNatsBeeClient,\n\tLocalFileBlobStore,\n\tnewTurnId,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { WebClient } from \"@slack/web-api\";\nimport { readFileSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { loadConfig } from \"./config.js\";\nimport * as log from \"./log.js\";\nimport { SlackSink } from \"./slack-sink.js\";\nimport type { SlackGatewayConfig } from \"./types.js\";\n\nexport interface SlackScheduledRunConfig {\n\tid: string;\n\trouteId: string;\n\ttext: string;\n\ttarget: {\n\t\tslackUserId?: string;\n\t\tchannelId?: string;\n\t\tthreadTs?: string;\n\t};\n\tactor?: {\n\t\tuserId: string;\n\t\tuserName?: string;\n\t\tdisplayName?: string;\n\t};\n\tsessionId?: string;\n\tsessionPrefix?: string;\n\tconversationId?: string;\n}\n\ninterface RenderState {\n\tstatusRef?: string;\n\tlatestText: string;\n\titemTexts: Map<string, string>;\n}\n\ntype ItemPartLike = { kind: string; [key: string]: unknown };\n\ntype ItemAppendedPayload = {\n\teventType: \"item.appended\";\n\titem: { id: string; kind: string; role: string; parts: ItemPartLike[] };\n};\n\ntype ItemUpdatedPayload = {\n\teventType: \"item.updated\";\n\titemId: string;\n\tappendParts?: ItemPartLike[];\n};\n\ntype RunFailedPayload = {\n\teventType: \"run.failed\";\n\terror: string;\n};\n\ntype ApprovalRequestedPayload = {\n\tsummary: string;\n};\n\nexport function loadScheduledRunConfig(jobPath?: string): SlackScheduledRunConfig {\n\tconst path = jobPath || process.env.BEE_SLACK_SCHEDULED_RUN_CONFIG;\n\tif (!path) {\n\t\tthrow new Error(\n\t\t\t\"Missing scheduled run config path; pass it as second argument or set BEE_SLACK_SCHEDULED_RUN_CONFIG\",\n\t\t);\n\t}\n\n\tconst fullPath = resolve(path);\n\tconst config = JSON.parse(readFileSync(fullPath, \"utf-8\")) as SlackScheduledRunConfig;\n\tvalidateScheduledRunConfig(config, fullPath);\n\treturn config;\n}\n\nexport async function runScheduledSlackTurnFromFiles(configPath?: string, jobPath?: string): Promise<void> {\n\tconst gatewayConfig = loadConfig(configPath);\n\tconst scheduledRun = loadScheduledRunConfig(jobPath);\n\tawait runScheduledSlackTurn(gatewayConfig, scheduledRun);\n}\n\nexport async function runScheduledSlackTurn(\n\tgatewayConfig: SlackGatewayConfig,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<void> {\n\tvalidateScheduledRunConfig(scheduledRun, \"scheduled run config\");\n\tconst route = gatewayConfig.routes.find((candidate) => candidate.id === scheduledRun.routeId);\n\tif (!route) {\n\t\tthrow new Error(`Scheduled run ${scheduledRun.id} references unknown routeId ${scheduledRun.routeId}`);\n\t}\n\n\tconst webClient = new WebClient(gatewayConfig.botToken);\n\tconst blobStore = new LocalFileBlobStore(\n\t\tprocess.env.BEE_SLACK_BLOB_STORE_ROOT ||\n\t\t\tprocess.env.BEE_BLOB_STORE_ROOT ||\n\t\t\tprocess.env.HUDAI_BLOB_STORE_ROOT ||\n\t\t\tjoin(process.cwd(), \".bee-blob-store\"),\n\t);\n\tconst sink = new SlackSink(webClient, blobStore);\n\tconst workerClient = await createNatsBeeClient(gatewayConfig.nats);\n\n\ttry {\n\t\tconst auth = await webClient.auth.test();\n\t\tconst teamId = String(auth.team_id || \"unknown-team\");\n\t\tconst output = await resolveScheduledOutputTarget(webClient, scheduledRun);\n\t\tconst actor = await resolveScheduledActor(webClient, scheduledRun);\n\t\tif (!output.threadId) {\n\t\t\toutput.threadId = await sink.postMessage(\n\t\t\t\t{ channelId: output.channelId },\n\t\t\t\t`:alarm_clock: Scheduled Bee run \\`${scheduledRun.id}\\` started. Reply in this thread to continue the session.`,\n\t\t\t);\n\t\t}\n\t\tconst slackConversationId = buildConversationId([\n\t\t\t\"slack\",\n\t\t\tteamId,\n\t\t\toutput.channelId || \"unknown-channel\",\n\t\t\toutput.threadId,\n\t\t]);\n\t\tconst conversationId = scheduledRun.conversationId || slackConversationId;\n\t\tconst sessionBase =\n\t\t\troute.session?.strategy === \"channel\" ? output.channelId || \"unknown-channel\" : output.threadId;\n\t\tconst routeSessionId = buildSessionKey(\n\t\t\tscheduledRun.sessionPrefix || route.session?.prefix || route.id,\n\t\t\tbuildConversationId([\"slack\", teamId, output.channelId || \"unknown-channel\", sessionBase || output.threadId]),\n\t\t);\n\t\tconst sessionId = scheduledRun.sessionId || routeSessionId;\n\n\t\tconst input: BeeResolvedTurn = {\n\t\t\tsessionId,\n\t\t\tthreadId: output.threadId,\n\t\t\tworker: route.worker,\n\t\t\tconversation: {\n\t\t\t\ttransport: \"slack\",\n\t\t\t\tconversationId,\n\t\t\t},\n\t\t\tactor,\n\t\t\tmessage: {\n\t\t\t\ttext: scheduledRun.text,\n\t\t\t},\n\t\t\tattachments: [],\n\t\t\toutput,\n\t\t};\n\n\t\tlog.logInfo(`Starting scheduled Slack run ${scheduledRun.id} on route ${route.id}`);\n\t\tawait streamScheduledTurn(workerClient, sink, input);\n\t\tlog.logInfo(`Completed scheduled Slack run ${scheduledRun.id}`);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog.logError(`Scheduled Slack run ${scheduledRun.id} failed`, message);\n\t\tthrow error;\n\t} finally {\n\t\tawait workerClient.close?.();\n\t}\n}\n\nasync function resolveScheduledOutputTarget(\n\twebClient: WebClient,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<TransportOutputTarget> {\n\tif (scheduledRun.target.channelId) {\n\t\treturn {\n\t\t\tchannelId: scheduledRun.target.channelId,\n\t\t\tthreadId: scheduledRun.target.threadTs,\n\t\t};\n\t}\n\n\tif (!scheduledRun.target.slackUserId) {\n\t\tthrow new Error(`Scheduled run ${scheduledRun.id} needs target.slackUserId or target.channelId`);\n\t}\n\n\tconst result = await webClient.conversations.open({ users: scheduledRun.target.slackUserId });\n\tconst channel = result.channel as { id?: string } | undefined;\n\tif (!channel?.id) {\n\t\tthrow new Error(`Slack conversations.open did not return a channel for user ${scheduledRun.target.slackUserId}`);\n\t}\n\treturn {\n\t\tchannelId: channel.id,\n\t\tthreadId: scheduledRun.target.threadTs,\n\t};\n}\n\nasync function resolveScheduledActor(\n\twebClient: WebClient,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<BeeResolvedTurn[\"actor\"]> {\n\tif (scheduledRun.actor) return scheduledRun.actor;\n\tif (!scheduledRun.target.slackUserId) {\n\t\treturn {\n\t\t\tuserId: `scheduler:${scheduledRun.id}`,\n\t\t\tdisplayName: \"Bee Scheduler\",\n\t\t};\n\t}\n\n\ttry {\n\t\tconst info = await webClient.users.info({ user: scheduledRun.target.slackUserId });\n\t\tconst user = info.user as\n\t\t\t| { id?: string; name?: string; real_name?: string; profile?: { display_name?: string } }\n\t\t\t| undefined;\n\t\treturn {\n\t\t\tuserId: scheduledRun.target.slackUserId,\n\t\t\tuserName: user?.name,\n\t\t\tdisplayName: user?.profile?.display_name || user?.real_name || user?.name,\n\t\t};\n\t} catch (error) {\n\t\tlog.logWarning(\n\t\t\t`Could not resolve Slack user ${scheduledRun.target.slackUserId}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t\treturn {\n\t\t\tuserId: scheduledRun.target.slackUserId,\n\t\t\tdisplayName: scheduledRun.target.slackUserId,\n\t\t};\n\t}\n}\n\nasync function streamScheduledTurn(\n\tworkerClient: BeeWorkerClient,\n\tsink: SlackSink,\n\tinput: BeeResolvedTurn,\n): Promise<void> {\n\tconst state: RenderState = {\n\t\tlatestText: \"_Working..._\",\n\t\titemTexts: new Map<string, string>(),\n\t};\n\tconst request = {\n\t\tsessionId: input.sessionId,\n\t\tthreadId: input.threadId,\n\t\tturnId: newTurnId(),\n\t\tconversation: input.conversation,\n\t\tactor: input.actor,\n\t\tmessage: input.message,\n\t\tattachments: input.attachments,\n\t};\n\n\ttry {\n\t\tawait workerClient.streamTurn(input.worker, request, async (event) => {\n\t\t\tawait handleScheduledEvent(sink, input.output, event, state);\n\t\t});\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tawait sink.postMessage(input.output, `_Scheduled gateway error: ${message}_`);\n\t\tthrow error;\n\t}\n}\n\nasync function handleScheduledEvent(\n\tsink: SlackSink,\n\toutput: TransportOutputTarget,\n\tevent: BeeRunEvent,\n\tstate: RenderState,\n): Promise<void> {\n\tif (event.name === \"run.started\") {\n\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\treturn;\n\t}\n\n\tif (event.name === \"run.completed\") {\n\t\tif (!state.statusRef) {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"run.failed\") {\n\t\tconst errorText = `_Error: ${asRunFailedPayload(event).error}_`;\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, errorText);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, errorText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"approval.requested\") {\n\t\tawait sink.postMessage(output, `Approval requested: ${asApprovalRequestedPayload(event).summary}`);\n\t\treturn;\n\t}\n\n\tif (event.name === \"item.appended\") {\n\t\tconst payload = asItemAppendedPayload(event);\n\t\tconst text = renderParts(payload.item.parts);\n\t\tstate.itemTexts.set(payload.item.id, text);\n\t\tif (payload.item.kind === \"artifact\") {\n\t\t\tconst artifact = firstArtifactRef(payload.item.parts);\n\t\t\tif (artifact && artifactHasPayload(artifact)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait sink.publishArtifact(output, artifact);\n\t\t\t\t\treturn;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tawait sink.postMessage(output, `${text}\\n_Artifact upload failed: ${message}_`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait sink.postMessage(output, text);\n\t\t\treturn;\n\t\t}\n\t\tstate.latestText = text;\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, text);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, text);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"item.updated\") {\n\t\tconst payload = asItemUpdatedPayload(event);\n\t\tconst current = state.itemTexts.get(payload.itemId) || \"\";\n\t\tconst appended = renderParts(payload.appendParts || []);\n\t\tconst next = current ? `${current}${appended}` : appended;\n\t\tstate.itemTexts.set(payload.itemId, next);\n\t\tstate.latestText = next;\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, next);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, next);\n\t\t}\n\t}\n}\n\nfunction firstArtifactRef(parts: ItemPartLike[]): ArtifactRef | undefined {\n\tconst part = parts.find((entry) => entry.kind === \"artifactRef\");\n\tif (!part) return undefined;\n\treturn {\n\t\tartifactId: String(part.artifactId || \"artifact\"),\n\t\tblobKey: typeof part.blobKey === \"string\" ? part.blobKey : undefined,\n\t\tname: typeof part.name === \"string\" ? part.name : undefined,\n\t\ttitle: typeof part.title === \"string\" ? part.title : undefined,\n\t\tmimeType: typeof part.mimeType === \"string\" ? part.mimeType : undefined,\n\t\turi: typeof part.uri === \"string\" ? part.uri : undefined,\n\t\tsizeBytes: typeof part.sizeBytes === \"number\" ? part.sizeBytes : undefined,\n\t};\n}\n\nfunction artifactHasPayload(artifact: ArtifactRef): boolean {\n\treturn !!(artifact.uri || artifact.blobKey);\n}\n\nfunction renderParts(parts: ItemPartLike[]): string {\n\treturn parts\n\t\t.map((part) => {\n\t\t\tif (part.kind === \"text\") return String(part.text || \"\");\n\t\t\tif (part.kind === \"status\") return String(part.status || \"\");\n\t\t\tif (part.kind === \"artifactRef\") {\n\t\t\t\treturn `Artifact: ${String(part.title || part.name || part.artifactId || \"artifact\")}`;\n\t\t\t}\n\t\t\tif (part.kind === \"approval\" || part.kind === \"choice\") {\n\t\t\t\treturn `${String(part.title || \"\")}\\n${String(part.summary || \"\")}`.trim();\n\t\t\t}\n\t\t\tif (part.kind === \"form\") return String(part.title || \"\");\n\t\t\tif (part.kind === \"log\") return String(part.text || \"\");\n\t\t\tif (part.kind === \"patch\" || part.kind === \"diff\") {\n\t\t\t\tconst files = Array.isArray(part.files) ? (part.files as Array<{ path?: unknown }>) : [];\n\t\t\t\treturn files.map((entry) => `File: ${String(entry.path || \"\")}`).join(\"\\n\");\n\t\t\t}\n\t\t\treturn JSON.stringify(part);\n\t\t})\n\t\t.filter(Boolean)\n\t\t.join(\"\\n\");\n}\n\nfunction validateScheduledRunConfig(config: SlackScheduledRunConfig, source: string): void {\n\tif (!config.id) throw new Error(`Missing id in ${source}`);\n\tif (!config.routeId) throw new Error(`Missing routeId in ${source}`);\n\tif (!config.text) throw new Error(`Missing text in ${source}`);\n\tif (!config.target) throw new Error(`Missing target in ${source}`);\n\tif (!config.target.slackUserId && !config.target.channelId) {\n\t\tthrow new Error(`Missing target.slackUserId or target.channelId in ${source}`);\n\t}\n}\n\nfunction asRunFailedPayload(event: BeeRunEvent): RunFailedPayload {\n\treturn event.payload as RunFailedPayload;\n}\n\nfunction asApprovalRequestedPayload(event: BeeRunEvent): ApprovalRequestedPayload {\n\treturn event.payload as ApprovalRequestedPayload;\n}\n\nfunction asItemAppendedPayload(event: BeeRunEvent): ItemAppendedPayload {\n\treturn event.payload as ItemAppendedPayload;\n}\n\nfunction asItemUpdatedPayload(event: BeeRunEvent): ItemUpdatedPayload {\n\treturn event.payload as ItemUpdatedPayload;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scheduled.js","sourceRoot":"","sources":["../src/scheduled.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype ArtifactRef,\n\ttype BeeResolvedTurn,\n\ttype BeeRunEvent,\n\ttype BeeWorkerClient,\n\tbuildConversationId,\n\tbuildSessionKey,\n\tcreateNatsBeeClient,\n\tLocalFileBlobStore,\n\tnewTurnId,\n\ttype TransportOutputTarget,\n} from \"@jobmatchme/bee-gate\";\nimport { WebClient } from \"@slack/web-api\";\nimport { readFileSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { loadConfig } from \"./config.js\";\nimport * as log from \"./log.js\";\nimport { SlackSink } from \"./slack-sink.js\";\nimport type { SlackGatewayConfig } from \"./types.js\";\n\nexport interface SlackScheduledRunConfig {\n\tid: string;\n\trouteId: string;\n\ttext: string;\n\ttarget: {\n\t\tslackUserId?: string;\n\t\tchannelId?: string;\n\t\tthreadTs?: string;\n\t};\n\tactor?: {\n\t\tuserId: string;\n\t\tuserName?: string;\n\t\tdisplayName?: string;\n\t};\n\tsessionId?: string;\n\tsessionPrefix?: string;\n\tconversationId?: string;\n}\n\ninterface RenderState {\n\tstatusRef?: string;\n\tlatestText: string;\n\trequestText?: string;\n\titemTexts: Map<string, string>;\n}\n\ntype ItemPartLike = { kind: string; [key: string]: unknown };\n\ntype ItemAppendedPayload = {\n\teventType: \"item.appended\";\n\titem: { id: string; kind: string; role: string; parts: ItemPartLike[] };\n};\n\ntype ItemUpdatedPayload = {\n\teventType: \"item.updated\";\n\titemId: string;\n\tappendParts?: ItemPartLike[];\n};\n\ntype RunFailedPayload = {\n\teventType: \"run.failed\";\n\terror: string;\n};\n\ntype ApprovalRequestedPayload = {\n\tsummary: string;\n};\n\nexport function loadScheduledRunConfig(jobPath?: string): SlackScheduledRunConfig {\n\tconst path = jobPath || process.env.BEE_SLACK_SCHEDULED_RUN_CONFIG;\n\tif (!path) {\n\t\tthrow new Error(\n\t\t\t\"Missing scheduled run config path; pass it as second argument or set BEE_SLACK_SCHEDULED_RUN_CONFIG\",\n\t\t);\n\t}\n\n\tconst fullPath = resolve(path);\n\tconst config = JSON.parse(readFileSync(fullPath, \"utf-8\")) as SlackScheduledRunConfig;\n\tvalidateScheduledRunConfig(config, fullPath);\n\treturn config;\n}\n\nexport async function runScheduledSlackTurnFromFiles(configPath?: string, jobPath?: string): Promise<void> {\n\tconst gatewayConfig = loadConfig(configPath);\n\tconst scheduledRun = loadScheduledRunConfig(jobPath);\n\tawait runScheduledSlackTurn(gatewayConfig, scheduledRun);\n}\n\nexport async function runScheduledSlackTurn(\n\tgatewayConfig: SlackGatewayConfig,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<void> {\n\tvalidateScheduledRunConfig(scheduledRun, \"scheduled run config\");\n\tconst route = gatewayConfig.routes.find((candidate) => candidate.id === scheduledRun.routeId);\n\tif (!route) {\n\t\tthrow new Error(`Scheduled run ${scheduledRun.id} references unknown routeId ${scheduledRun.routeId}`);\n\t}\n\n\tconst webClient = new WebClient(gatewayConfig.botToken);\n\tconst blobStore = new LocalFileBlobStore(\n\t\tprocess.env.BEE_SLACK_BLOB_STORE_ROOT ||\n\t\t\tprocess.env.BEE_BLOB_STORE_ROOT ||\n\t\t\tprocess.env.HUDAI_BLOB_STORE_ROOT ||\n\t\t\tjoin(process.cwd(), \".bee-blob-store\"),\n\t);\n\tconst sink = new SlackSink(webClient, blobStore);\n\tconst workerClient = await createNatsBeeClient(gatewayConfig.nats);\n\n\ttry {\n\t\tconst auth = await webClient.auth.test();\n\t\tconst teamId = String(auth.team_id || \"unknown-team\");\n\t\tconst output = await resolveScheduledOutputTarget(webClient, scheduledRun);\n\t\tconst actor = await resolveScheduledActor(webClient, scheduledRun);\n\t\tlet statusRef: string | undefined;\n\t\tif (!output.threadId) {\n\t\t\tstatusRef = await sink.postMessage({ channelId: output.channelId }, \"_Working..._\");\n\t\t\toutput.threadId = statusRef;\n\t\t}\n\t\tconst slackConversationId = buildConversationId([\n\t\t\t\"slack\",\n\t\t\tteamId,\n\t\t\toutput.channelId || \"unknown-channel\",\n\t\t\toutput.threadId,\n\t\t]);\n\t\tconst conversationId = scheduledRun.conversationId || slackConversationId;\n\t\tconst sessionBase =\n\t\t\troute.session?.strategy === \"channel\" ? output.channelId || \"unknown-channel\" : output.threadId;\n\t\tconst routeSessionId = buildSessionKey(\n\t\t\tscheduledRun.sessionPrefix || route.session?.prefix || route.id,\n\t\t\tbuildConversationId([\"slack\", teamId, output.channelId || \"unknown-channel\", sessionBase || output.threadId]),\n\t\t);\n\t\tconst sessionId = scheduledRun.sessionId || routeSessionId;\n\n\t\tconst input: BeeResolvedTurn = {\n\t\t\tsessionId,\n\t\t\tthreadId: output.threadId,\n\t\t\tworker: route.worker,\n\t\t\tconversation: {\n\t\t\t\ttransport: \"slack\",\n\t\t\t\tconversationId,\n\t\t\t},\n\t\t\tactor,\n\t\t\tmessage: {\n\t\t\t\ttext: scheduledRun.text,\n\t\t\t},\n\t\t\tattachments: [],\n\t\t\toutput,\n\t\t};\n\n\t\tlog.logInfo(`Starting scheduled Slack run ${scheduledRun.id} on route ${route.id}`);\n\t\tawait streamScheduledTurn(workerClient, sink, input, statusRef);\n\t\tlog.logInfo(`Completed scheduled Slack run ${scheduledRun.id}`);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog.logError(`Scheduled Slack run ${scheduledRun.id} failed`, message);\n\t\tthrow error;\n\t} finally {\n\t\tawait workerClient.close?.();\n\t}\n}\n\nasync function resolveScheduledOutputTarget(\n\twebClient: WebClient,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<TransportOutputTarget> {\n\tif (scheduledRun.target.channelId) {\n\t\treturn {\n\t\t\tchannelId: scheduledRun.target.channelId,\n\t\t\tthreadId: scheduledRun.target.threadTs,\n\t\t};\n\t}\n\n\tif (!scheduledRun.target.slackUserId) {\n\t\tthrow new Error(`Scheduled run ${scheduledRun.id} needs target.slackUserId or target.channelId`);\n\t}\n\n\tconst result = await webClient.conversations.open({ users: scheduledRun.target.slackUserId });\n\tconst channel = result.channel as { id?: string } | undefined;\n\tif (!channel?.id) {\n\t\tthrow new Error(`Slack conversations.open did not return a channel for user ${scheduledRun.target.slackUserId}`);\n\t}\n\treturn {\n\t\tchannelId: channel.id,\n\t\tthreadId: scheduledRun.target.threadTs,\n\t};\n}\n\nasync function resolveScheduledActor(\n\twebClient: WebClient,\n\tscheduledRun: SlackScheduledRunConfig,\n): Promise<BeeResolvedTurn[\"actor\"]> {\n\tif (scheduledRun.actor) return scheduledRun.actor;\n\tif (!scheduledRun.target.slackUserId) {\n\t\treturn {\n\t\t\tuserId: `scheduler:${scheduledRun.id}`,\n\t\t\tdisplayName: \"Bee Scheduler\",\n\t\t};\n\t}\n\n\ttry {\n\t\tconst info = await webClient.users.info({ user: scheduledRun.target.slackUserId });\n\t\tconst user = info.user as\n\t\t\t| { id?: string; name?: string; real_name?: string; profile?: { display_name?: string } }\n\t\t\t| undefined;\n\t\treturn {\n\t\t\tuserId: scheduledRun.target.slackUserId,\n\t\t\tuserName: user?.name,\n\t\t\tdisplayName: user?.profile?.display_name || user?.real_name || user?.name,\n\t\t};\n\t} catch (error) {\n\t\tlog.logWarning(\n\t\t\t`Could not resolve Slack user ${scheduledRun.target.slackUserId}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t\treturn {\n\t\t\tuserId: scheduledRun.target.slackUserId,\n\t\t\tdisplayName: scheduledRun.target.slackUserId,\n\t\t};\n\t}\n}\n\nasync function streamScheduledTurn(\n\tworkerClient: BeeWorkerClient,\n\tsink: SlackSink,\n\tinput: BeeResolvedTurn,\n\tstatusRef?: string,\n): Promise<void> {\n\tconst state: RenderState = {\n\t\tstatusRef,\n\t\tlatestText: \"_Working..._\",\n\t\trequestText: input.message.text,\n\t\titemTexts: new Map<string, string>(),\n\t};\n\tconst request = {\n\t\tsessionId: input.sessionId,\n\t\tthreadId: input.threadId,\n\t\tturnId: newTurnId(),\n\t\tconversation: input.conversation,\n\t\tactor: input.actor,\n\t\tmessage: input.message,\n\t\tattachments: input.attachments,\n\t};\n\n\ttry {\n\t\tawait workerClient.streamTurn(input.worker, request, async (event) => {\n\t\t\tawait handleScheduledEvent(sink, input.output, event, state);\n\t\t});\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tawait sink.postMessage(input.output, `_Scheduled gateway error: ${message}_`);\n\t\tthrow error;\n\t}\n}\n\nasync function handleScheduledEvent(\n\tsink: SlackSink,\n\toutput: TransportOutputTarget,\n\tevent: BeeRunEvent,\n\tstate: RenderState,\n): Promise<void> {\n\tif (event.name === \"run.started\") {\n\t\tif (!state.statusRef) {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"run.completed\") {\n\t\tif (!state.statusRef) {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"run.failed\") {\n\t\tconst errorText = `_Error: ${asRunFailedPayload(event).error}_`;\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, errorText);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, errorText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"approval.requested\") {\n\t\tawait sink.postMessage(output, `Approval requested: ${asApprovalRequestedPayload(event).summary}`);\n\t\treturn;\n\t}\n\n\tif (event.name === \"item.appended\") {\n\t\tconst payload = asItemAppendedPayload(event);\n\t\tconst text = renderParts(payload.item.parts);\n\t\tstate.itemTexts.set(payload.item.id, text);\n\t\tif (payload.item.kind === \"artifact\") {\n\t\t\tconst artifact = firstArtifactRef(payload.item.parts);\n\t\t\tif (artifact && artifactHasPayload(artifact)) {\n\t\t\t\ttry {\n\t\t\t\t\tawait sink.publishArtifact(output, artifact);\n\t\t\t\t\treturn;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tawait sink.postMessage(output, `${text}\\n_Artifact upload failed: ${message}_`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait sink.postMessage(output, text);\n\t\t\treturn;\n\t\t}\n\t\tstate.latestText = renderScheduledFinalMessage(state.requestText, text);\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, state.latestText);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (event.name === \"item.updated\") {\n\t\tconst payload = asItemUpdatedPayload(event);\n\t\tconst current = state.itemTexts.get(payload.itemId) || \"\";\n\t\tconst appended = renderParts(payload.appendParts || []);\n\t\tconst next = current ? `${current}${appended}` : appended;\n\t\tstate.itemTexts.set(payload.itemId, next);\n\t\tstate.latestText = renderScheduledFinalMessage(state.requestText, next);\n\t\tif (state.statusRef) {\n\t\t\tawait sink.updateMessage(output, state.statusRef, state.latestText);\n\t\t} else {\n\t\t\tstate.statusRef = await sink.postMessage(output, state.latestText);\n\t\t}\n\t}\n}\n\nfunction firstArtifactRef(parts: ItemPartLike[]): ArtifactRef | undefined {\n\tconst part = parts.find((entry) => entry.kind === \"artifactRef\");\n\tif (!part) return undefined;\n\treturn {\n\t\tartifactId: String(part.artifactId || \"artifact\"),\n\t\tblobKey: typeof part.blobKey === \"string\" ? part.blobKey : undefined,\n\t\tname: typeof part.name === \"string\" ? part.name : undefined,\n\t\ttitle: typeof part.title === \"string\" ? part.title : undefined,\n\t\tmimeType: typeof part.mimeType === \"string\" ? part.mimeType : undefined,\n\t\turi: typeof part.uri === \"string\" ? part.uri : undefined,\n\t\tsizeBytes: typeof part.sizeBytes === \"number\" ? part.sizeBytes : undefined,\n\t};\n}\n\nfunction artifactHasPayload(artifact: ArtifactRef): boolean {\n\treturn !!(artifact.uri || artifact.blobKey);\n}\n\nexport function renderScheduledFinalMessage(requestText: string | undefined, responseText: string): string {\n\tconst request = requestText?.trim();\n\tif (!request) return responseText;\n\n\tconst quotedRequest = request\n\t\t.split(/\\r?\\n/)\n\t\t.map((line) => `> ${escapeSlackText(line)}`)\n\t\t.join(\"\\n\");\n\n\treturn [\"*Request:*\", quotedRequest, \"*Antwort:*\", responseText].filter(Boolean).join(\"\\n\\n\");\n}\n\nfunction escapeSlackText(text: string): string {\n\treturn text.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nfunction renderParts(parts: ItemPartLike[]): string {\n\treturn parts\n\t\t.map((part) => {\n\t\t\tif (part.kind === \"text\") return String(part.text || \"\");\n\t\t\tif (part.kind === \"status\") return String(part.status || \"\");\n\t\t\tif (part.kind === \"artifactRef\") {\n\t\t\t\treturn `Artifact: ${String(part.title || part.name || part.artifactId || \"artifact\")}`;\n\t\t\t}\n\t\t\tif (part.kind === \"approval\" || part.kind === \"choice\") {\n\t\t\t\treturn `${String(part.title || \"\")}\\n${String(part.summary || \"\")}`.trim();\n\t\t\t}\n\t\t\tif (part.kind === \"form\") return String(part.title || \"\");\n\t\t\tif (part.kind === \"log\") return String(part.text || \"\");\n\t\t\tif (part.kind === \"patch\" || part.kind === \"diff\") {\n\t\t\t\tconst files = Array.isArray(part.files) ? (part.files as Array<{ path?: unknown }>) : [];\n\t\t\t\treturn files.map((entry) => `File: ${String(entry.path || \"\")}`).join(\"\\n\");\n\t\t\t}\n\t\t\treturn JSON.stringify(part);\n\t\t})\n\t\t.filter(Boolean)\n\t\t.join(\"\\n\");\n}\n\nfunction validateScheduledRunConfig(config: SlackScheduledRunConfig, source: string): void {\n\tif (!config.id) throw new Error(`Missing id in ${source}`);\n\tif (!config.routeId) throw new Error(`Missing routeId in ${source}`);\n\tif (!config.text) throw new Error(`Missing text in ${source}`);\n\tif (!config.target) throw new Error(`Missing target in ${source}`);\n\tif (!config.target.slackUserId && !config.target.channelId) {\n\t\tthrow new Error(`Missing target.slackUserId or target.channelId in ${source}`);\n\t}\n}\n\nfunction asRunFailedPayload(event: BeeRunEvent): RunFailedPayload {\n\treturn event.payload as RunFailedPayload;\n}\n\nfunction asApprovalRequestedPayload(event: BeeRunEvent): ApprovalRequestedPayload {\n\treturn event.payload as ApprovalRequestedPayload;\n}\n\nfunction asItemAppendedPayload(event: BeeRunEvent): ItemAppendedPayload {\n\treturn event.payload as ItemAppendedPayload;\n}\n\nfunction asItemUpdatedPayload(event: BeeRunEvent): ItemUpdatedPayload {\n\treturn event.payload as ItemUpdatedPayload;\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,55 @@ export interface SlackGatewayConfig {
|
|
|
23
23
|
name?: string;
|
|
24
24
|
};
|
|
25
25
|
routes: SlackRouteConfig[];
|
|
26
|
+
handoff?: SlackHandoffConfig;
|
|
27
|
+
}
|
|
28
|
+
export interface SlackHandoffConfig {
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
host?: string;
|
|
31
|
+
port?: number;
|
|
32
|
+
allowedDashboardHosts?: string[];
|
|
33
|
+
routes: SlackHandoffRouteConfig[];
|
|
34
|
+
}
|
|
35
|
+
export interface SlackHandoffRouteConfig {
|
|
36
|
+
id: string;
|
|
37
|
+
label?: string;
|
|
38
|
+
channelId: string;
|
|
39
|
+
worker: BeeWorkerTargetConfig;
|
|
40
|
+
session?: SlackSessionConfig;
|
|
41
|
+
}
|
|
42
|
+
export interface SlackHandoffActor {
|
|
43
|
+
userId: string;
|
|
44
|
+
userName?: string;
|
|
45
|
+
displayName?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface SlackHandoffContext {
|
|
48
|
+
dashboardTitle?: string;
|
|
49
|
+
dashboardUid?: string;
|
|
50
|
+
panelTitle?: string;
|
|
51
|
+
panelId?: number;
|
|
52
|
+
url: string;
|
|
53
|
+
timeRange?: string;
|
|
54
|
+
variables?: Record<string, string>;
|
|
55
|
+
}
|
|
56
|
+
export interface SlackHandoffRequest {
|
|
57
|
+
routeId: string;
|
|
58
|
+
text: string;
|
|
59
|
+
actor: SlackHandoffActor;
|
|
60
|
+
context: SlackHandoffContext;
|
|
61
|
+
}
|
|
62
|
+
export interface SlackHandoffReply {
|
|
63
|
+
ts: string;
|
|
64
|
+
threadTs?: string;
|
|
65
|
+
text: string;
|
|
66
|
+
author?: string;
|
|
67
|
+
isBot: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface SlackHandoffRecord {
|
|
70
|
+
routeId: string;
|
|
71
|
+
channelId: string;
|
|
72
|
+
threadTs: string;
|
|
73
|
+
permalink: string;
|
|
74
|
+
createdAt: string;
|
|
26
75
|
}
|
|
27
76
|
export interface SlackFile {
|
|
28
77
|
id?: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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}\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"]}
|
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}\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}\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"]}
|