@ai-devkit/channel-connector 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/ConfigStore.d.ts.map +1 -1
- package/dist/ConfigStore.js +1 -0
- package/dist/ConfigStore.js.map +1 -1
- package/dist/__tests__/ChannelManager.test.js +3 -1
- package/dist/__tests__/ChannelManager.test.js.map +1 -1
- package/dist/__tests__/ConfigStore.test.js +33 -4
- package/dist/__tests__/ConfigStore.test.js.map +1 -1
- package/dist/__tests__/adapters/SlackAdapter.test.js +429 -0
- package/dist/__tests__/adapters/SlackAdapter.test.js.map +1 -0
- package/dist/__tests__/utils/SlackDeliveryQueue.test.js +127 -0
- package/dist/__tests__/utils/SlackDeliveryQueue.test.js.map +1 -0
- package/dist/__tests__/utils/slackMarkdown.test.js +44 -0
- package/dist/__tests__/utils/slackMarkdown.test.js.map +1 -0
- package/dist/__tests__/utils/telegramHtml.test.js +46 -2
- package/dist/__tests__/utils/telegramHtml.test.js.map +1 -1
- package/dist/adapters/ChannelAdapter.d.ts +8 -2
- package/dist/adapters/ChannelAdapter.d.ts.map +1 -1
- package/dist/adapters/ChannelAdapter.js +3 -6
- package/dist/adapters/ChannelAdapter.js.map +1 -1
- package/dist/adapters/SlackAdapter.d.ts +71 -0
- package/dist/adapters/SlackAdapter.d.ts.map +1 -0
- package/dist/adapters/SlackAdapter.js +205 -0
- package/dist/adapters/SlackAdapter.js.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +59 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -3
- package/dist/types.js.map +1 -1
- package/dist/utils/SlackDeliveryQueue.d.ts +33 -0
- package/dist/utils/SlackDeliveryQueue.d.ts.map +1 -0
- package/dist/utils/SlackDeliveryQueue.js +83 -0
- package/dist/utils/SlackDeliveryQueue.js.map +1 -0
- package/dist/utils/slackMarkdown.d.ts +5 -0
- package/dist/utils/slackMarkdown.d.ts.map +1 -0
- package/dist/utils/slackMarkdown.js +150 -0
- package/dist/utils/slackMarkdown.js.map +1 -0
- package/dist/utils/telegramHtml.d.ts.map +1 -1
- package/dist/utils/telegramHtml.js +5 -9
- package/dist/utils/telegramHtml.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/adapters/SlackAdapter.test.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport { SlackAdapter, validateSlackAppToken, validateSlackCredentials } from '../../adapters/SlackAdapter.js';\nimport type { SlackConfig } from '../../types.js';\n\nclass FakeSocketClient extends EventEmitter {\n start = vi.fn().mockResolvedValue(undefined);\n disconnect = vi.fn().mockResolvedValue(undefined);\n}\n\nconst config: SlackConfig = {\n appToken: 'xapp-fake',\n botToken: 'xoxb-fake',\n appId: 'A123',\n botUserId: 'U-BOT',\n workspaceId: 'T123',\n transport: 'socket-mode',\n audience: 'dm',\n};\n\nfunction envelope(overrides: Record<string, unknown> = {}) {\n return {\n type: 'events_api',\n ack: vi.fn().mockResolvedValue(undefined),\n body: {\n team_id: 'T123',\n event_id: 'Ev123',\n event: {\n type: 'message',\n channel_type: 'im',\n channel: 'D123',\n user: 'U123',\n text: 'run tests',\n ts: '100.1',\n event_ts: '100.1',\n ...overrides,\n },\n },\n };\n}\n\ndescribe('SlackAdapter', () => {\n it('validates bot identity without returning either credential', async () => {\n const authTest = vi.fn().mockResolvedValue({ ok: true, app_id: 'A123', user_id: 'U-BOT', team_id: 'T123', team: 'Sandbox' });\n await expect(validateSlackCredentials('xoxb-fake', { authTest })).resolves.toEqual({\n appId: 'A123', botUserId: 'U-BOT', workspaceId: 'T123', workspaceName: 'Sandbox',\n });\n });\n\n it('accepts Slack documented bot identity without an app ID', async () => {\n const authTest = vi.fn().mockResolvedValue({\n ok: true,\n bot_id: 'B123',\n user_id: 'U-BOT',\n team_id: 'T123',\n team: 'Sandbox',\n });\n await expect(validateSlackCredentials('xoxb-fake', { authTest })).resolves.toEqual({\n botUserId: 'U-BOT',\n workspaceId: 'T123',\n workspaceName: 'Sandbox',\n });\n });\n\n it('rejects incomplete Slack credential identity', async () => {\n const authTest = vi.fn().mockResolvedValue({ ok: true, team_id: 'T123' });\n await expect(validateSlackCredentials('xoxb-fake', { authTest })).rejects.toThrow('Slack bot token returned incomplete identity');\n });\n\n it('validates an app token through apps.connections.open', async () => {\n const open = vi.fn().mockResolvedValue({ ok: true, url: 'wss://wss-primary.slack.com/link/' });\n await expect(validateSlackAppToken('xapp-fake', { open })).resolves.toBeUndefined();\n expect(open).toHaveBeenCalledOnce();\n });\n\n it('rejects an app token that cannot open Socket Mode', async () => {\n const open = vi.fn().mockResolvedValue({ ok: false });\n await expect(validateSlackAppToken('xapp-fake', { open })).rejects.toThrow('Slack app token cannot open Socket Mode');\n });\n\n it('starts and stops the official Socket Mode client and reports health', async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n expect(await adapter.isHealthy()).toBe(false);\n await adapter.start();\n expect(socket.start).toHaveBeenCalledOnce();\n expect(await adapter.isHealthy()).toBe(true);\n await adapter.stop();\n expect(socket.disconnect).toHaveBeenCalledOnce();\n expect(await adapter.isHealthy()).toBe(false);\n });\n\n it('does not register duplicate listeners across restarts', async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n await adapter.start();\n await adapter.stop();\n await adapter.start();\n expect(socket.listenerCount('slack_event')).toBe(1);\n expect(socket.listenerCount('interactive')).toBe(1);\n socket.emit('disconnected');\n expect(await adapter.isHealthy()).toBe(false);\n socket.emit('connected');\n expect(await adapter.isHealthy()).toBe(true);\n });\n\n it('acknowledges before delivering one normalized workspace DM event', async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n const order: string[] = [];\n adapter.onMessage(vi.fn(async (message) => {\n order.push('handler');\n expect(message).toMatchObject({\n channelType: 'slack', chatId: 'D123', userId: 'U123', workspaceId: 'T123', messageId: 'Ev123', text: 'run tests',\n });\n }));\n await adapter.start();\n const item = envelope();\n item.ack.mockImplementation(async () => { order.push('ack'); });\n socket.emit('slack_event', item);\n await vi.waitFor(() => expect(order).toEqual(['ack', 'handler']));\n socket.emit('slack_event', item);\n await new Promise((resolve) => setTimeout(resolve, 0));\n expect(order).toEqual(['ack', 'handler', 'ack']);\n });\n\n it.each([\n ['wrong workspace', {}, { team_id: 'T999' }],\n ['bot message', { bot_id: 'B123' }, {}],\n ['self message', { user: 'U-BOT' }, {}],\n ['subtype', { subtype: 'message_changed' }, {}],\n ['non-DM', { channel_type: 'channel' }, {}],\n ])('ignores %s events', async (_name, eventOverrides, bodyOverrides) => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n adapter.onMessage(handler);\n await adapter.start();\n const item = envelope(eventOverrides);\n Object.assign(item.body, bodyOverrides);\n socket.emit('slack_event', item);\n await vi.waitFor(() => expect(item.ack).toHaveBeenCalled());\n expect(handler).not.toHaveBeenCalled();\n });\n\n it('delivers an ordinary workspace DM immediately without pairing', async () => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n adapter.onMessage(handler);\n await adapter.start();\n\n socket.emit('slack_event', envelope({ text: 'run tests' }));\n\n await vi.waitFor(() => expect(handler).toHaveBeenCalledWith(expect.objectContaining({\n chatId: 'D123', userId: 'U123', text: 'run tests', workspaceId: 'T123',\n })));\n });\n\n it('sends an accessible Block Kit question and finalizes its actions', async () => {\n const socket = new FakeSocketClient();\n const postMessage = vi.fn().mockResolvedValue({ ok: true, ts: '400.1' });\n const update = vi.fn().mockResolvedValue({ ok: true });\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage, update } } });\n await expect(adapter.sendQuestion('D123', {\n id: 'q1', header: 'Scope', question: 'Choose one', allowSkip: true,\n options: [{ label: 'Safe', description: 'Read only', value: '1' }],\n })).resolves.toEqual({ messageId: '400.1' });\n expect(postMessage).toHaveBeenCalledWith(expect.objectContaining({\n channel: 'D123', text: 'Scope: Choose one', blocks: expect.any(Array),\n }));\n await adapter.finalizeInteraction('D123', '400.1');\n expect(update).toHaveBeenCalledWith({ channel: 'D123', ts: '400.1', blocks: [] });\n });\n\n it('escapes Slack control syntax in question fallback text', async () => {\n const postMessage = vi.fn().mockResolvedValue({ ok: true, ts: '400.1' });\n const adapter = new SlackAdapter(config, {\n socketClient: new FakeSocketClient(),\n webClient: { chat: { postMessage } },\n });\n await adapter.sendQuestion('D123', {\n id: 'q1', header: '<!channel>', question: 'Choose <one> & continue', allowSkip: false,\n options: [{ label: 'Safe', value: '1' }],\n });\n expect(postMessage).toHaveBeenCalledWith(expect.objectContaining({\n text: '<!channel>: Choose <one> & continue',\n }));\n });\n\n it('acknowledges and delivers one valid workspace block action', async () => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n adapter.onInteraction(handler);\n await adapter.start();\n const item = {\n ack: vi.fn().mockResolvedValue(undefined),\n body: {\n type: 'block_actions', team: { id: 'T123' }, user: { id: 'U123' },\n channel: { id: 'D123' }, container: { message_ts: '400.1' },\n actions: [{ action_id: 'ai_devkit_question', action_ts: '401.1', value: 'q1:1' }],\n },\n };\n socket.emit('interactive', item);\n await vi.waitFor(() => expect(handler).toHaveBeenCalledOnce());\n expect(item.ack).toHaveBeenCalledOnce();\n expect(handler).toHaveBeenCalledWith(expect.objectContaining({\n workspaceId: 'T123', chatId: 'D123', userId: 'U123', messageId: '400.1', value: 'q1:1',\n }));\n socket.emit('interactive', item);\n await new Promise((resolve) => setTimeout(resolve, 0));\n expect(handler).toHaveBeenCalledOnce();\n expect(item.ack).toHaveBeenCalledTimes(2);\n });\n\n it('does not acknowledge interactive envelopes a second time on the generic event stream', async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n await adapter.start();\n const ack = vi.fn().mockResolvedValue(undefined);\n socket.emit('slack_event', { type: 'interactive', ack, body: {} });\n await new Promise((resolve) => setTimeout(resolve, 0));\n expect(ack).not.toHaveBeenCalled();\n });\n\n it('acknowledges malformed and wrong-workspace block actions without delivery', async () => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n adapter.onInteraction(handler);\n await adapter.start();\n const ack = vi.fn().mockResolvedValue(undefined);\n socket.emit('interactive', { ack, body: { type: 'block_actions', team: { id: 'T999' }, actions: [] } });\n await vi.waitFor(() => expect(ack).toHaveBeenCalled());\n expect(handler).not.toHaveBeenCalled();\n });\n});\n"],"names":["EventEmitter","SlackAdapter","validateSlackAppToken","validateSlackCredentials","FakeSocketClient","start","vi","fn","mockResolvedValue","undefined","disconnect","config","appToken","botToken","appId","botUserId","workspaceId","transport","audience","envelope","overrides","type","ack","body","team_id","event_id","event","channel_type","channel","user","text","ts","event_ts","describe","it","authTest","ok","app_id","user_id","team","expect","resolves","toEqual","workspaceName","bot_id","rejects","toThrow","open","url","toBeUndefined","toHaveBeenCalledOnce","socket","adapter","socketClient","webClient","chat","postMessage","isHealthy","toBe","stop","listenerCount","emit","order","onMessage","message","push","toMatchObject","channelType","chatId","userId","messageId","item","mockImplementation","waitFor","Promise","resolve","setTimeout","each","subtype","_name","eventOverrides","bodyOverrides","handler","Object","assign","toHaveBeenCalled","not","toHaveBeenCalledWith","objectContaining","update","sendQuestion","id","header","question","allowSkip","options","label","description","value","blocks","any","Array","finalizeInteraction","onInteraction","container","message_ts","actions","action_id","action_ts","toHaveBeenCalledTimes"],"mappings":"AAAA,SAASA,YAAY,QAAQ,cAAc;AAC3C,SAASC,YAAY,EAAEC,qBAAqB,EAAEC,wBAAwB,QAAQ,iCAAiC;AAG/G,IAAA,AAAMC,mBAAN,MAAMA,yBAAyBJ;IAC3BK,QAAQC,GAAGC,EAAE,GAAGC,iBAAiB,CAACC,WAAW;IAC7CC,aAAaJ,GAAGC,EAAE,GAAGC,iBAAiB,CAACC,WAAW;AACtD;AAEA,MAAME,SAAsB;IACxBC,UAAU;IACVC,UAAU;IACVC,OAAO;IACPC,WAAW;IACXC,aAAa;IACbC,WAAW;IACXC,UAAU;AACd;AAEA,SAASC,SAASC,YAAqC,CAAC,CAAC;IACrD,OAAO;QACHC,MAAM;QACNC,KAAKhB,GAAGC,EAAE,GAAGC,iBAAiB,CAACC;QAC/Bc,MAAM;YACFC,SAAS;YACTC,UAAU;YACVC,OAAO;gBACHL,MAAM;gBACNM,cAAc;gBACdC,SAAS;gBACTC,MAAM;gBACNC,MAAM;gBACNC,IAAI;gBACJC,UAAU;gBACV,GAAGZ,SAAS;YAChB;QACJ;IACJ;AACJ;AAEAa,SAAS,gBAAgB;IACrBC,GAAG,8DAA8D;QAC7D,MAAMC,WAAW7B,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;YAAE4B,IAAI;YAAMC,QAAQ;YAAQC,SAAS;YAASd,SAAS;YAAQe,MAAM;QAAU;QAC1H,MAAMC,OAAOrC,yBAAyB,aAAa;YAAEgC;QAAS,IAAIM,QAAQ,CAACC,OAAO,CAAC;YAC/E5B,OAAO;YAAQC,WAAW;YAASC,aAAa;YAAQ2B,eAAe;QAC3E;IACJ;IAEAT,GAAG,2DAA2D;QAC1D,MAAMC,WAAW7B,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;YACvC4B,IAAI;YACJQ,QAAQ;YACRN,SAAS;YACTd,SAAS;YACTe,MAAM;QACV;QACA,MAAMC,OAAOrC,yBAAyB,aAAa;YAAEgC;QAAS,IAAIM,QAAQ,CAACC,OAAO,CAAC;YAC/E3B,WAAW;YACXC,aAAa;YACb2B,eAAe;QACnB;IACJ;IAEAT,GAAG,gDAAgD;QAC/C,MAAMC,WAAW7B,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;YAAE4B,IAAI;YAAMZ,SAAS;QAAO;QACvE,MAAMgB,OAAOrC,yBAAyB,aAAa;YAAEgC;QAAS,IAAIU,OAAO,CAACC,OAAO,CAAC;IACtF;IAEAZ,GAAG,wDAAwD;QACvD,MAAMa,OAAOzC,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;YAAE4B,IAAI;YAAMY,KAAK;QAAoC;QAC5F,MAAMR,OAAOtC,sBAAsB,aAAa;YAAE6C;QAAK,IAAIN,QAAQ,CAACQ,aAAa;QACjFT,OAAOO,MAAMG,oBAAoB;IACrC;IAEAhB,GAAG,qDAAqD;QACpD,MAAMa,OAAOzC,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;YAAE4B,IAAI;QAAM;QACnD,MAAMI,OAAOtC,sBAAsB,aAAa;YAAE6C;QAAK,IAAIF,OAAO,CAACC,OAAO,CAAC;IAC/E;IAEAZ,GAAG,uEAAuE;QACtE,MAAMiB,SAAS,IAAI/C;QACnB,MAAMgD,UAAU,IAAInD,aAAaU,QAAQ;YAAE0C,cAAcF;YAAQG,WAAW;gBAAEC,MAAM;oBAAEC,aAAalD,GAAGC,EAAE;gBAAG;YAAE;QAAE;QAC/GiC,OAAO,MAAMY,QAAQK,SAAS,IAAIC,IAAI,CAAC;QACvC,MAAMN,QAAQ/C,KAAK;QACnBmC,OAAOW,OAAO9C,KAAK,EAAE6C,oBAAoB;QACzCV,OAAO,MAAMY,QAAQK,SAAS,IAAIC,IAAI,CAAC;QACvC,MAAMN,QAAQO,IAAI;QAClBnB,OAAOW,OAAOzC,UAAU,EAAEwC,oBAAoB;QAC9CV,OAAO,MAAMY,QAAQK,SAAS,IAAIC,IAAI,CAAC;IAC3C;IAEAxB,GAAG,yDAAyD;QACxD,MAAMiB,SAAS,IAAI/C;QACnB,MAAMgD,UAAU,IAAInD,aAAaU,QAAQ;YAAE0C,cAAcF;YAAQG,WAAW;gBAAEC,MAAM;oBAAEC,aAAalD,GAAGC,EAAE;gBAAG;YAAE;QAAE;QAC/G,MAAM6C,QAAQ/C,KAAK;QACnB,MAAM+C,QAAQO,IAAI;QAClB,MAAMP,QAAQ/C,KAAK;QACnBmC,OAAOW,OAAOS,aAAa,CAAC,gBAAgBF,IAAI,CAAC;QACjDlB,OAAOW,OAAOS,aAAa,CAAC,gBAAgBF,IAAI,CAAC;QACjDP,OAAOU,IAAI,CAAC;QACZrB,OAAO,MAAMY,QAAQK,SAAS,IAAIC,IAAI,CAAC;QACvCP,OAAOU,IAAI,CAAC;QACZrB,OAAO,MAAMY,QAAQK,SAAS,IAAIC,IAAI,CAAC;IAC3C;IAEAxB,GAAG,oEAAoE;QACnE,MAAMiB,SAAS,IAAI/C;QACnB,MAAMgD,UAAU,IAAInD,aAAaU,QAAQ;YAAE0C,cAAcF;YAAQG,WAAW;gBAAEC,MAAM;oBAAEC,aAAalD,GAAGC,EAAE;gBAAG;YAAE;QAAE;QAC/G,MAAMuD,QAAkB,EAAE;QAC1BV,QAAQW,SAAS,CAACzD,GAAGC,EAAE,CAAC,OAAOyD;YAC3BF,MAAMG,IAAI,CAAC;YACXzB,OAAOwB,SAASE,aAAa,CAAC;gBAC1BC,aAAa;gBAASC,QAAQ;gBAAQC,QAAQ;gBAAQrD,aAAa;gBAAQsD,WAAW;gBAASxC,MAAM;YACzG;QACJ;QACA,MAAMsB,QAAQ/C,KAAK;QACnB,MAAMkE,OAAOpD;QACboD,KAAKjD,GAAG,CAACkD,kBAAkB,CAAC;YAAcV,MAAMG,IAAI,CAAC;QAAQ;QAC7Dd,OAAOU,IAAI,CAAC,eAAeU;QAC3B,MAAMjE,GAAGmE,OAAO,CAAC,IAAMjC,OAAOsB,OAAOpB,OAAO,CAAC;gBAAC;gBAAO;aAAU;QAC/DS,OAAOU,IAAI,CAAC,eAAeU;QAC3B,MAAM,IAAIG,QAAQ,CAACC,UAAYC,WAAWD,SAAS;QACnDnC,OAAOsB,OAAOpB,OAAO,CAAC;YAAC;YAAO;YAAW;SAAM;IACnD;IAEAR,GAAG2C,IAAI,CAAC;QACJ;YAAC;YAAmB,CAAC;YAAG;gBAAErD,SAAS;YAAO;SAAE;QAC5C;YAAC;YAAe;gBAAEoB,QAAQ;YAAO;YAAG,CAAC;SAAE;QACvC;YAAC;YAAgB;gBAAEf,MAAM;YAAQ;YAAG,CAAC;SAAE;QACvC;YAAC;YAAW;gBAAEiD,SAAS;YAAkB;YAAG,CAAC;SAAE;QAC/C;YAAC;YAAU;gBAAEnD,cAAc;YAAU;YAAG,CAAC;SAAE;KAC9C,EAAE,qBAAqB,OAAOoD,OAAOC,gBAAgBC;QAClD,MAAM9B,SAAS,IAAI/C;QACnB,MAAM8E,UAAU5E,GAAGC,EAAE;QACrB,MAAM6C,UAAU,IAAInD,aAAaU,QAAQ;YAAE0C,cAAcF;YAAQG,WAAW;gBAAEC,MAAM;oBAAEC,aAAalD,GAAGC,EAAE;gBAAG;YAAE;QAAE;QAC/G6C,QAAQW,SAAS,CAACmB;QAClB,MAAM9B,QAAQ/C,KAAK;QACnB,MAAMkE,OAAOpD,SAAS6D;QACtBG,OAAOC,MAAM,CAACb,KAAKhD,IAAI,EAAE0D;QACzB9B,OAAOU,IAAI,CAAC,eAAeU;QAC3B,MAAMjE,GAAGmE,OAAO,CAAC,IAAMjC,OAAO+B,KAAKjD,GAAG,EAAE+D,gBAAgB;QACxD7C,OAAO0C,SAASI,GAAG,CAACD,gBAAgB;IACxC;IAEAnD,GAAG,iEAAiE;QAChE,MAAMiB,SAAS,IAAI/C;QACnB,MAAM8E,UAAU5E,GAAGC,EAAE;QACrB,MAAM6C,UAAU,IAAInD,aAAaU,QAAQ;YACrC0C,cAAcF;YACdG,WAAW;gBAAEC,MAAM;oBAAEC,aAAalD,GAAGC,EAAE;gBAAG;YAAE;QAChD;QACA6C,QAAQW,SAAS,CAACmB;QAClB,MAAM9B,QAAQ/C,KAAK;QAEnB8C,OAAOU,IAAI,CAAC,eAAe1C,SAAS;YAAEW,MAAM;QAAY;QAExD,MAAMxB,GAAGmE,OAAO,CAAC,IAAMjC,OAAO0C,SAASK,oBAAoB,CAAC/C,OAAOgD,gBAAgB,CAAC;gBAChFpB,QAAQ;gBAAQC,QAAQ;gBAAQvC,MAAM;gBAAad,aAAa;YACpE;IACJ;IAEAkB,GAAG,oEAAoE;QACnE,MAAMiB,SAAS,IAAI/C;QACnB,MAAMoD,cAAclD,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;YAAE4B,IAAI;YAAML,IAAI;QAAQ;QACtE,MAAM0D,SAASnF,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;YAAE4B,IAAI;QAAK;QACpD,MAAMgB,UAAU,IAAInD,aAAaU,QAAQ;YAAE0C,cAAcF;YAAQG,WAAW;gBAAEC,MAAM;oBAAEC;oBAAaiC;gBAAO;YAAE;QAAE;QAC9G,MAAMjD,OAAOY,QAAQsC,YAAY,CAAC,QAAQ;YACtCC,IAAI;YAAMC,QAAQ;YAASC,UAAU;YAAcC,WAAW;YAC9DC,SAAS;gBAAC;oBAAEC,OAAO;oBAAQC,aAAa;oBAAaC,OAAO;gBAAI;aAAE;QACtE,IAAIzD,QAAQ,CAACC,OAAO,CAAC;YAAE4B,WAAW;QAAQ;QAC1C9B,OAAOgB,aAAa+B,oBAAoB,CAAC/C,OAAOgD,gBAAgB,CAAC;YAC7D5D,SAAS;YAAQE,MAAM;YAAqBqE,QAAQ3D,OAAO4D,GAAG,CAACC;QACnE;QACA,MAAMjD,QAAQkD,mBAAmB,CAAC,QAAQ;QAC1C9D,OAAOiD,QAAQF,oBAAoB,CAAC;YAAE3D,SAAS;YAAQG,IAAI;YAASoE,QAAQ,EAAE;QAAC;IACnF;IAEAjE,GAAG,0DAA0D;QACzD,MAAMsB,cAAclD,GAAGC,EAAE,GAAGC,iBAAiB,CAAC;YAAE4B,IAAI;YAAML,IAAI;QAAQ;QACtE,MAAMqB,UAAU,IAAInD,aAAaU,QAAQ;YACrC0C,cAAc,IAAIjD;YAClBkD,WAAW;gBAAEC,MAAM;oBAAEC;gBAAY;YAAE;QACvC;QACA,MAAMJ,QAAQsC,YAAY,CAAC,QAAQ;YAC/BC,IAAI;YAAMC,QAAQ;YAAcC,UAAU;YAA2BC,WAAW;YAChFC,SAAS;gBAAC;oBAAEC,OAAO;oBAAQE,OAAO;gBAAI;aAAE;QAC5C;QACA1D,OAAOgB,aAAa+B,oBAAoB,CAAC/C,OAAOgD,gBAAgB,CAAC;YAC7D1D,MAAM;QACV;IACJ;IAEAI,GAAG,8DAA8D;QAC7D,MAAMiB,SAAS,IAAI/C;QACnB,MAAM8E,UAAU5E,GAAGC,EAAE;QACrB,MAAM6C,UAAU,IAAInD,aAAaU,QAAQ;YAAE0C,cAAcF;YAAQG,WAAW;gBAAEC,MAAM;oBAAEC,aAAalD,GAAGC,EAAE;gBAAG;YAAE;QAAE;QAC/G6C,QAAQmD,aAAa,CAACrB;QACtB,MAAM9B,QAAQ/C,KAAK;QACnB,MAAMkE,OAAO;YACTjD,KAAKhB,GAAGC,EAAE,GAAGC,iBAAiB,CAACC;YAC/Bc,MAAM;gBACFF,MAAM;gBAAiBkB,MAAM;oBAAEoD,IAAI;gBAAO;gBAAG9D,MAAM;oBAAE8D,IAAI;gBAAO;gBAChE/D,SAAS;oBAAE+D,IAAI;gBAAO;gBAAGa,WAAW;oBAAEC,YAAY;gBAAQ;gBAC1DC,SAAS;oBAAC;wBAAEC,WAAW;wBAAsBC,WAAW;wBAASV,OAAO;oBAAO;iBAAE;YACrF;QACJ;QACA/C,OAAOU,IAAI,CAAC,eAAeU;QAC3B,MAAMjE,GAAGmE,OAAO,CAAC,IAAMjC,OAAO0C,SAAShC,oBAAoB;QAC3DV,OAAO+B,KAAKjD,GAAG,EAAE4B,oBAAoB;QACrCV,OAAO0C,SAASK,oBAAoB,CAAC/C,OAAOgD,gBAAgB,CAAC;YACzDxE,aAAa;YAAQoD,QAAQ;YAAQC,QAAQ;YAAQC,WAAW;YAAS4B,OAAO;QACpF;QACA/C,OAAOU,IAAI,CAAC,eAAeU;QAC3B,MAAM,IAAIG,QAAQ,CAACC,UAAYC,WAAWD,SAAS;QACnDnC,OAAO0C,SAAShC,oBAAoB;QACpCV,OAAO+B,KAAKjD,GAAG,EAAEuF,qBAAqB,CAAC;IAC3C;IAEA3E,GAAG,wFAAwF;QACvF,MAAMiB,SAAS,IAAI/C;QACnB,MAAMgD,UAAU,IAAInD,aAAaU,QAAQ;YAAE0C,cAAcF;YAAQG,WAAW;gBAAEC,MAAM;oBAAEC,aAAalD,GAAGC,EAAE;gBAAG;YAAE;QAAE;QAC/G,MAAM6C,QAAQ/C,KAAK;QACnB,MAAMiB,MAAMhB,GAAGC,EAAE,GAAGC,iBAAiB,CAACC;QACtC0C,OAAOU,IAAI,CAAC,eAAe;YAAExC,MAAM;YAAeC;YAAKC,MAAM,CAAC;QAAE;QAChE,MAAM,IAAImD,QAAQ,CAACC,UAAYC,WAAWD,SAAS;QACnDnC,OAAOlB,KAAKgE,GAAG,CAACD,gBAAgB;IACpC;IAEAnD,GAAG,6EAA6E;QAC5E,MAAMiB,SAAS,IAAI/C;QACnB,MAAM8E,UAAU5E,GAAGC,EAAE;QACrB,MAAM6C,UAAU,IAAInD,aAAaU,QAAQ;YAAE0C,cAAcF;YAAQG,WAAW;gBAAEC,MAAM;oBAAEC,aAAalD,GAAGC,EAAE;gBAAG;YAAE;QAAE;QAC/G6C,QAAQmD,aAAa,CAACrB;QACtB,MAAM9B,QAAQ/C,KAAK;QACnB,MAAMiB,MAAMhB,GAAGC,EAAE,GAAGC,iBAAiB,CAACC;QACtC0C,OAAOU,IAAI,CAAC,eAAe;YAAEvC;YAAKC,MAAM;gBAAEF,MAAM;gBAAiBkB,MAAM;oBAAEoD,IAAI;gBAAO;gBAAGe,SAAS,EAAE;YAAC;QAAE;QACrG,MAAMpG,GAAGmE,OAAO,CAAC,IAAMjC,OAAOlB,KAAK+D,gBAAgB;QACnD7C,OAAO0C,SAASI,GAAG,CAACD,gBAAgB;IACxC;AACJ"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { SlackDeliveryQueue } from '../../utils/SlackDeliveryQueue.js';
|
|
2
|
+
describe('SlackDeliveryQueue', ()=>{
|
|
3
|
+
it('serializes long output as a parent followed by threaded continuations', async ()=>{
|
|
4
|
+
const postMessage = vi.fn().mockResolvedValueOnce({
|
|
5
|
+
ok: true,
|
|
6
|
+
ts: '100.1'
|
|
7
|
+
}).mockResolvedValue({
|
|
8
|
+
ok: true,
|
|
9
|
+
ts: '100.2'
|
|
10
|
+
});
|
|
11
|
+
const queue = new SlackDeliveryQueue({
|
|
12
|
+
postMessage
|
|
13
|
+
}, {
|
|
14
|
+
maxMessageLength: 20
|
|
15
|
+
});
|
|
16
|
+
const result = await queue.send('D123', 'first paragraph\n\nsecond paragraph that is long');
|
|
17
|
+
expect(result).toEqual({
|
|
18
|
+
messageId: '100.1',
|
|
19
|
+
threadId: '100.1'
|
|
20
|
+
});
|
|
21
|
+
expect(postMessage).toHaveBeenCalledTimes(3);
|
|
22
|
+
expect(postMessage.mock.calls[0][0]).not.toHaveProperty('thread_ts');
|
|
23
|
+
expect(postMessage.mock.calls[1][0]).toMatchObject({
|
|
24
|
+
channel: 'D123',
|
|
25
|
+
thread_ts: '100.1'
|
|
26
|
+
});
|
|
27
|
+
expect(postMessage.mock.calls[2][0]).toMatchObject({
|
|
28
|
+
channel: 'D123',
|
|
29
|
+
thread_ts: '100.1'
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it('waits for retryAfter before retrying a rate-limited API call', async ()=>{
|
|
33
|
+
const error = Object.assign(new Error('rate limited'), {
|
|
34
|
+
retryAfter: 2
|
|
35
|
+
});
|
|
36
|
+
const postMessage = vi.fn().mockRejectedValueOnce(error).mockResolvedValue({
|
|
37
|
+
ok: true,
|
|
38
|
+
ts: '200.1'
|
|
39
|
+
});
|
|
40
|
+
const sleep = vi.fn().mockResolvedValue(undefined);
|
|
41
|
+
const queue = new SlackDeliveryQueue({
|
|
42
|
+
postMessage
|
|
43
|
+
}, {
|
|
44
|
+
sleep
|
|
45
|
+
});
|
|
46
|
+
await expect(queue.send('D123', 'hello')).resolves.toEqual({
|
|
47
|
+
messageId: '200.1',
|
|
48
|
+
threadId: '200.1'
|
|
49
|
+
});
|
|
50
|
+
expect(sleep).toHaveBeenCalledWith(2000);
|
|
51
|
+
expect(postMessage).toHaveBeenCalledTimes(2);
|
|
52
|
+
});
|
|
53
|
+
it('caps an excessive rate-limit delay', async ()=>{
|
|
54
|
+
const error = Object.assign(new Error('rate limited'), {
|
|
55
|
+
retryAfter: 86_400
|
|
56
|
+
});
|
|
57
|
+
const postMessage = vi.fn().mockRejectedValueOnce(error).mockResolvedValue({
|
|
58
|
+
ok: true,
|
|
59
|
+
ts: '200.1'
|
|
60
|
+
});
|
|
61
|
+
const sleep = vi.fn().mockResolvedValue(undefined);
|
|
62
|
+
const queue = new SlackDeliveryQueue({
|
|
63
|
+
postMessage
|
|
64
|
+
}, {
|
|
65
|
+
sleep
|
|
66
|
+
});
|
|
67
|
+
await queue.send('D123', 'hello');
|
|
68
|
+
expect(sleep).toHaveBeenCalledWith(60_000);
|
|
69
|
+
});
|
|
70
|
+
it('rejects overflow while a conversation send is pending', async ()=>{
|
|
71
|
+
let release;
|
|
72
|
+
const pending = new Promise((resolve)=>{
|
|
73
|
+
release = ()=>resolve({
|
|
74
|
+
ok: true,
|
|
75
|
+
ts: '300.1'
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
const postMessage = vi.fn().mockReturnValue(pending);
|
|
79
|
+
const queue = new SlackDeliveryQueue({
|
|
80
|
+
postMessage
|
|
81
|
+
}, {
|
|
82
|
+
maxQueueSize: 1
|
|
83
|
+
});
|
|
84
|
+
const first = queue.send('D123', 'first');
|
|
85
|
+
await expect(queue.send('D123', 'second')).rejects.toThrow('Slack delivery queue is full');
|
|
86
|
+
release();
|
|
87
|
+
await first;
|
|
88
|
+
});
|
|
89
|
+
it('rejects empty output and responses without a timestamp', async ()=>{
|
|
90
|
+
const postMessage = vi.fn().mockResolvedValue({
|
|
91
|
+
ok: true
|
|
92
|
+
});
|
|
93
|
+
const queue = new SlackDeliveryQueue({
|
|
94
|
+
postMessage
|
|
95
|
+
});
|
|
96
|
+
await expect(queue.send('D123', '')).rejects.toThrow('Cannot send an empty Slack message');
|
|
97
|
+
await expect(queue.send('D123', 'hello')).rejects.toThrow('Slack did not return a message timestamp');
|
|
98
|
+
});
|
|
99
|
+
it('propagates permanent and invalid rate-limit errors without sleeping', async ()=>{
|
|
100
|
+
const sleep = vi.fn();
|
|
101
|
+
const permanent = new Error('invalid_auth');
|
|
102
|
+
const postMessage = vi.fn().mockRejectedValue(permanent);
|
|
103
|
+
const queue = new SlackDeliveryQueue({
|
|
104
|
+
postMessage
|
|
105
|
+
}, {
|
|
106
|
+
sleep
|
|
107
|
+
});
|
|
108
|
+
await expect(queue.send('D123', 'hello')).rejects.toBe(permanent);
|
|
109
|
+
expect(sleep).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
it('allows separate conversation queues to progress independently', async ()=>{
|
|
112
|
+
const postMessage = vi.fn().mockImplementation(async ({ channel })=>({
|
|
113
|
+
ok: true,
|
|
114
|
+
ts: `${channel}.1`
|
|
115
|
+
}));
|
|
116
|
+
const queue = new SlackDeliveryQueue({
|
|
117
|
+
postMessage
|
|
118
|
+
});
|
|
119
|
+
await expect(Promise.all([
|
|
120
|
+
queue.send('D1', 'one'),
|
|
121
|
+
queue.send('D2', 'two')
|
|
122
|
+
])).resolves.toHaveLength(2);
|
|
123
|
+
expect(postMessage).toHaveBeenCalledTimes(2);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=SlackDeliveryQueue.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/utils/SlackDeliveryQueue.test.ts"],"sourcesContent":["import { SlackDeliveryQueue } from '../../utils/SlackDeliveryQueue.js';\n\ndescribe('SlackDeliveryQueue', () => {\n it('serializes long output as a parent followed by threaded continuations', async () => {\n const postMessage = vi.fn()\n .mockResolvedValueOnce({ ok: true, ts: '100.1' })\n .mockResolvedValue({ ok: true, ts: '100.2' });\n const queue = new SlackDeliveryQueue({ postMessage }, { maxMessageLength: 20 });\n const result = await queue.send('D123', 'first paragraph\\n\\nsecond paragraph that is long');\n expect(result).toEqual({ messageId: '100.1', threadId: '100.1' });\n expect(postMessage).toHaveBeenCalledTimes(3);\n expect(postMessage.mock.calls[0][0]).not.toHaveProperty('thread_ts');\n expect(postMessage.mock.calls[1][0]).toMatchObject({ channel: 'D123', thread_ts: '100.1' });\n expect(postMessage.mock.calls[2][0]).toMatchObject({ channel: 'D123', thread_ts: '100.1' });\n });\n\n it('waits for retryAfter before retrying a rate-limited API call', async () => {\n const error = Object.assign(new Error('rate limited'), { retryAfter: 2 });\n const postMessage = vi.fn().mockRejectedValueOnce(error).mockResolvedValue({ ok: true, ts: '200.1' });\n const sleep = vi.fn().mockResolvedValue(undefined);\n const queue = new SlackDeliveryQueue({ postMessage }, { sleep });\n await expect(queue.send('D123', 'hello')).resolves.toEqual({ messageId: '200.1', threadId: '200.1' });\n expect(sleep).toHaveBeenCalledWith(2000);\n expect(postMessage).toHaveBeenCalledTimes(2);\n });\n\n it('caps an excessive rate-limit delay', async () => {\n const error = Object.assign(new Error('rate limited'), { retryAfter: 86_400 });\n const postMessage = vi.fn().mockRejectedValueOnce(error).mockResolvedValue({ ok: true, ts: '200.1' });\n const sleep = vi.fn().mockResolvedValue(undefined);\n const queue = new SlackDeliveryQueue({ postMessage }, { sleep });\n await queue.send('D123', 'hello');\n expect(sleep).toHaveBeenCalledWith(60_000);\n });\n\n it('rejects overflow while a conversation send is pending', async () => {\n let release!: () => void;\n const pending = new Promise<{ ok: true; ts: string }>((resolve) => {\n release = () => resolve({ ok: true, ts: '300.1' });\n });\n const postMessage = vi.fn().mockReturnValue(pending);\n const queue = new SlackDeliveryQueue({ postMessage }, { maxQueueSize: 1 });\n const first = queue.send('D123', 'first');\n await expect(queue.send('D123', 'second')).rejects.toThrow('Slack delivery queue is full');\n release();\n await first;\n });\n\n it('rejects empty output and responses without a timestamp', async () => {\n const postMessage = vi.fn().mockResolvedValue({ ok: true });\n const queue = new SlackDeliveryQueue({ postMessage });\n await expect(queue.send('D123', '')).rejects.toThrow('Cannot send an empty Slack message');\n await expect(queue.send('D123', 'hello')).rejects.toThrow('Slack did not return a message timestamp');\n });\n\n it('propagates permanent and invalid rate-limit errors without sleeping', async () => {\n const sleep = vi.fn();\n const permanent = new Error('invalid_auth');\n const postMessage = vi.fn().mockRejectedValue(permanent);\n const queue = new SlackDeliveryQueue({ postMessage }, { sleep });\n await expect(queue.send('D123', 'hello')).rejects.toBe(permanent);\n expect(sleep).not.toHaveBeenCalled();\n });\n\n it('allows separate conversation queues to progress independently', async () => {\n const postMessage = vi.fn().mockImplementation(async ({ channel }) => ({ ok: true, ts: `${channel}.1` }));\n const queue = new SlackDeliveryQueue({ postMessage });\n await expect(Promise.all([queue.send('D1', 'one'), queue.send('D2', 'two')])).resolves.toHaveLength(2);\n expect(postMessage).toHaveBeenCalledTimes(2);\n });\n});\n"],"names":["SlackDeliveryQueue","describe","it","postMessage","vi","fn","mockResolvedValueOnce","ok","ts","mockResolvedValue","queue","maxMessageLength","result","send","expect","toEqual","messageId","threadId","toHaveBeenCalledTimes","mock","calls","not","toHaveProperty","toMatchObject","channel","thread_ts","error","Object","assign","Error","retryAfter","mockRejectedValueOnce","sleep","undefined","resolves","toHaveBeenCalledWith","release","pending","Promise","resolve","mockReturnValue","maxQueueSize","first","rejects","toThrow","permanent","mockRejectedValue","toBe","toHaveBeenCalled","mockImplementation","all","toHaveLength"],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SLACK_MAX_MESSAGE_LENGTH, chunkMarkdownForSlack, markdownToSlackMrkdwn } from '../../utils/slackMarkdown.js';
|
|
2
|
+
describe('Slack Markdown', ()=>{
|
|
3
|
+
it('renders common Markdown without activating broad mentions', ()=>{
|
|
4
|
+
expect(markdownToSlackMrkdwn('# Result\n\n**Passed** & <safe> @channel [docs](https://example.com)')).toBe('*Result*\n\n*Passed* & <safe> @channel <https://example.com|docs>');
|
|
5
|
+
});
|
|
6
|
+
it('preserves inline and fenced code while escaping Slack control characters', ()=>{
|
|
7
|
+
expect(markdownToSlackMrkdwn('Use `<tag>`\n\n```ts\nconst x = "<ok>";\n```')).toContain('`<tag>`\n\n```\nconst x = "<ok>";\n```');
|
|
8
|
+
});
|
|
9
|
+
it('chunks long fenced code into independently fenced Slack messages', ()=>{
|
|
10
|
+
const chunks = chunkMarkdownForSlack(`\`\`\`ts\n${'const value = 1;\n'.repeat(400)}\`\`\``);
|
|
11
|
+
expect(chunks.length).toBeGreaterThan(1);
|
|
12
|
+
expect(chunks.every((chunk)=>chunk.length <= SLACK_MAX_MESSAGE_LENGTH)).toBe(true);
|
|
13
|
+
expect(chunks.every((chunk)=>chunk.startsWith('```') && chunk.endsWith('```'))).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it('accounts for escaped control-character expansion in code chunks', ()=>{
|
|
16
|
+
const chunks = chunkMarkdownForSlack(`\`\`\`\n${'<&>'.repeat(2000)}\n\`\`\``);
|
|
17
|
+
expect(chunks.length).toBeGreaterThan(1);
|
|
18
|
+
expect(chunks.every((chunk)=>chunk.length <= SLACK_MAX_MESSAGE_LENGTH)).toBe(true);
|
|
19
|
+
expect(chunks.every((chunk)=>chunk.startsWith('```') && chunk.endsWith('```'))).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
it('splits Unicode text without breaking code points', ()=>{
|
|
22
|
+
const chunks = chunkMarkdownForSlack('🎉'.repeat(5000));
|
|
23
|
+
expect(chunks.join('')).toBe('🎉'.repeat(5000));
|
|
24
|
+
expect(chunks.every((chunk)=>chunk.length <= SLACK_MAX_MESSAGE_LENGTH)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
it('renders lists, quotes, emphasis, strike, breaks, and rules conservatively', ()=>{
|
|
27
|
+
const rendered = markdownToSlackMrkdwn('1. _one_\n2. ~~two~~\n\n> quoted \n> next\n\n---');
|
|
28
|
+
expect(rendered).toContain('1. _one_');
|
|
29
|
+
expect(rendered).toContain('2. ~two~');
|
|
30
|
+
expect(rendered).toContain('> quoted');
|
|
31
|
+
expect(rendered).toContain('> next');
|
|
32
|
+
expect(rendered).toContain('—');
|
|
33
|
+
});
|
|
34
|
+
it('packs short semantic blocks and splits oversized plain paragraphs', ()=>{
|
|
35
|
+
expect(chunkMarkdownForSlack('one\n\ntwo', 20)).toEqual([
|
|
36
|
+
'one\n\ntwo'
|
|
37
|
+
]);
|
|
38
|
+
const chunks = chunkMarkdownForSlack('word '.repeat(30), 25);
|
|
39
|
+
expect(chunks.length).toBeGreaterThan(1);
|
|
40
|
+
expect(chunks.every((chunk)=>chunk.length <= 25)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=slackMarkdown.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/utils/slackMarkdown.test.ts"],"sourcesContent":["import {\n SLACK_MAX_MESSAGE_LENGTH,\n chunkMarkdownForSlack,\n markdownToSlackMrkdwn,\n} from '../../utils/slackMarkdown.js';\n\ndescribe('Slack Markdown', () => {\n it('renders common Markdown without activating broad mentions', () => {\n expect(markdownToSlackMrkdwn('# Result\\n\\n**Passed** & <safe> @channel [docs](https://example.com)'))\n .toBe('*Result*\\n\\n*Passed* & <safe> @channel <https://example.com|docs>');\n });\n\n it('preserves inline and fenced code while escaping Slack control characters', () => {\n expect(markdownToSlackMrkdwn('Use `<tag>`\\n\\n```ts\\nconst x = \"<ok>\";\\n```'))\n .toContain('`<tag>`\\n\\n```\\nconst x = \"<ok>\";\\n```');\n });\n\n it('chunks long fenced code into independently fenced Slack messages', () => {\n const chunks = chunkMarkdownForSlack(`\\`\\`\\`ts\\n${'const value = 1;\\n'.repeat(400)}\\`\\`\\``);\n expect(chunks.length).toBeGreaterThan(1);\n expect(chunks.every((chunk) => chunk.length <= SLACK_MAX_MESSAGE_LENGTH)).toBe(true);\n expect(chunks.every((chunk) => chunk.startsWith('```') && chunk.endsWith('```'))).toBe(true);\n });\n\n it('accounts for escaped control-character expansion in code chunks', () => {\n const chunks = chunkMarkdownForSlack(`\\`\\`\\`\\n${'<&>'.repeat(2000)}\\n\\`\\`\\``);\n expect(chunks.length).toBeGreaterThan(1);\n expect(chunks.every((chunk) => chunk.length <= SLACK_MAX_MESSAGE_LENGTH)).toBe(true);\n expect(chunks.every((chunk) => chunk.startsWith('```') && chunk.endsWith('```'))).toBe(true);\n });\n\n it('splits Unicode text without breaking code points', () => {\n const chunks = chunkMarkdownForSlack('🎉'.repeat(5000));\n expect(chunks.join('')).toBe('🎉'.repeat(5000));\n expect(chunks.every((chunk) => chunk.length <= SLACK_MAX_MESSAGE_LENGTH)).toBe(true);\n });\n\n it('renders lists, quotes, emphasis, strike, breaks, and rules conservatively', () => {\n const rendered = markdownToSlackMrkdwn('1. _one_\\n2. ~~two~~\\n\\n> quoted \\n> next\\n\\n---');\n expect(rendered).toContain('1. _one_');\n expect(rendered).toContain('2. ~two~');\n expect(rendered).toContain('> quoted');\n expect(rendered).toContain('> next');\n expect(rendered).toContain('—');\n });\n\n it('packs short semantic blocks and splits oversized plain paragraphs', () => {\n expect(chunkMarkdownForSlack('one\\n\\ntwo', 20)).toEqual(['one\\n\\ntwo']);\n const chunks = chunkMarkdownForSlack('word '.repeat(30), 25);\n expect(chunks.length).toBeGreaterThan(1);\n expect(chunks.every((chunk) => chunk.length <= 25)).toBe(true);\n });\n});\n"],"names":["SLACK_MAX_MESSAGE_LENGTH","chunkMarkdownForSlack","markdownToSlackMrkdwn","describe","it","expect","toBe","toContain","chunks","repeat","length","toBeGreaterThan","every","chunk","startsWith","endsWith","join","rendered","toEqual"],"mappings":"AAAA,SACIA,wBAAwB,EACxBC,qBAAqB,EACrBC,qBAAqB,QAClB,+BAA+B;AAEtCC,SAAS,kBAAkB;IACvBC,GAAG,6DAA6D;QAC5DC,OAAOH,sBAAsB,yEACxBI,IAAI,CAAC;IACd;IAEAF,GAAG,4EAA4E;QAC3EC,OAAOH,sBAAsB,iDACxBK,SAAS,CAAC;IACnB;IAEAH,GAAG,oEAAoE;QACnE,MAAMI,SAASP,sBAAsB,CAAC,UAAU,EAAE,qBAAqBQ,MAAM,CAAC,KAAK,MAAM,CAAC;QAC1FJ,OAAOG,OAAOE,MAAM,EAAEC,eAAe,CAAC;QACtCN,OAAOG,OAAOI,KAAK,CAAC,CAACC,QAAUA,MAAMH,MAAM,IAAIV,2BAA2BM,IAAI,CAAC;QAC/ED,OAAOG,OAAOI,KAAK,CAAC,CAACC,QAAUA,MAAMC,UAAU,CAAC,UAAUD,MAAME,QAAQ,CAAC,SAAST,IAAI,CAAC;IAC3F;IAEAF,GAAG,mEAAmE;QAClE,MAAMI,SAASP,sBAAsB,CAAC,QAAQ,EAAE,MAAMQ,MAAM,CAAC,MAAM,QAAQ,CAAC;QAC5EJ,OAAOG,OAAOE,MAAM,EAAEC,eAAe,CAAC;QACtCN,OAAOG,OAAOI,KAAK,CAAC,CAACC,QAAUA,MAAMH,MAAM,IAAIV,2BAA2BM,IAAI,CAAC;QAC/ED,OAAOG,OAAOI,KAAK,CAAC,CAACC,QAAUA,MAAMC,UAAU,CAAC,UAAUD,MAAME,QAAQ,CAAC,SAAST,IAAI,CAAC;IAC3F;IAEAF,GAAG,oDAAoD;QACnD,MAAMI,SAASP,sBAAsB,KAAKQ,MAAM,CAAC;QACjDJ,OAAOG,OAAOQ,IAAI,CAAC,KAAKV,IAAI,CAAC,KAAKG,MAAM,CAAC;QACzCJ,OAAOG,OAAOI,KAAK,CAAC,CAACC,QAAUA,MAAMH,MAAM,IAAIV,2BAA2BM,IAAI,CAAC;IACnF;IAEAF,GAAG,6EAA6E;QAC5E,MAAMa,WAAWf,sBAAsB;QACvCG,OAAOY,UAAUV,SAAS,CAAC;QAC3BF,OAAOY,UAAUV,SAAS,CAAC;QAC3BF,OAAOY,UAAUV,SAAS,CAAC;QAC3BF,OAAOY,UAAUV,SAAS,CAAC;QAC3BF,OAAOY,UAAUV,SAAS,CAAC;IAC/B;IAEAH,GAAG,qEAAqE;QACpEC,OAAOJ,sBAAsB,cAAc,KAAKiB,OAAO,CAAC;YAAC;SAAa;QACtE,MAAMV,SAASP,sBAAsB,QAAQQ,MAAM,CAAC,KAAK;QACzDJ,OAAOG,OAAOE,MAAM,EAAEC,eAAe,CAAC;QACtCN,OAAOG,OAAOI,KAAK,CAAC,CAACC,QAAUA,MAAMH,MAAM,IAAI,KAAKJ,IAAI,CAAC;IAC7D;AACJ"}
|
|
@@ -7,10 +7,11 @@ describe('markdownToTelegramHtml', ()=>{
|
|
|
7
7
|
expect(out).toContain('<s>s</s>');
|
|
8
8
|
});
|
|
9
9
|
it('renders inline code and fenced code with language', ()=>{
|
|
10
|
-
const md = 'Run `npm test`\n\n```ts\nconst x = 1;\n```';
|
|
10
|
+
const md = 'Run `npm test`\n\n```ts\nconst x = 1;\n```\n\n```\nplain\n```';
|
|
11
11
|
const out = markdownToTelegramHtml(md);
|
|
12
12
|
expect(out).toContain('<code>npm test</code>');
|
|
13
13
|
expect(out).toContain('<pre><code class="language-ts">const x = 1;</code></pre>');
|
|
14
|
+
expect(out).toContain('<pre><code>plain</code></pre>');
|
|
14
15
|
});
|
|
15
16
|
it('renders links and converts headings to bold', ()=>{
|
|
16
17
|
const md = '# Title\n\nSee [docs](https://x.com).';
|
|
@@ -45,14 +46,57 @@ describe('markdownToTelegramHtml', ()=>{
|
|
|
45
46
|
expect(out).toContain('2. two');
|
|
46
47
|
expect(out).not.toContain('<ol>');
|
|
47
48
|
});
|
|
49
|
+
it('renders inline markup as HTML inside ordered tight lists', ()=>{
|
|
50
|
+
const out = markdownToTelegramHtml('1. **bold** `code` *italic*');
|
|
51
|
+
expect(out).toContain('1. <b>bold</b> <code>code</code> <i>italic</i>');
|
|
52
|
+
expect(out).not.toContain('<b>');
|
|
53
|
+
expect(out).not.toMatch(/\*\*|`/);
|
|
54
|
+
});
|
|
55
|
+
it('renders inline markup as HTML inside unordered tight lists', ()=>{
|
|
56
|
+
const out = markdownToTelegramHtml('- **bold** `code` *italic*');
|
|
57
|
+
expect(out).toContain('• <b>bold</b> <code>code</code> <i>italic</i>');
|
|
58
|
+
expect(out).not.toMatch(/<(?:b|code|i)>/);
|
|
59
|
+
});
|
|
60
|
+
it('renders inline markup in the middle of list item text', ()=>{
|
|
61
|
+
const out = markdownToTelegramHtml('- While editing, `/x` is a **literal** character.');
|
|
62
|
+
expect(out).toContain('• While editing, <code>/x</code> is a <b>literal</b> character.');
|
|
63
|
+
});
|
|
64
|
+
it('renders inline markup in loose multi-paragraph lists', ()=>{
|
|
65
|
+
const out = markdownToTelegramHtml('- First **bold** paragraph.\n\n Second paragraph with *italic*.\n\n- `code` item.');
|
|
66
|
+
expect(out).toContain('• First <b>bold</b> paragraph.');
|
|
67
|
+
expect(out).toContain('Second paragraph with <i>italic</i>.');
|
|
68
|
+
expect(out).toContain('• <code>code</code> item.');
|
|
69
|
+
expect(out).not.toMatch(/<(?:b|code|i)>/);
|
|
70
|
+
});
|
|
71
|
+
it('renders inline markup in nested list items', ()=>{
|
|
72
|
+
const out = markdownToTelegramHtml('- **parent**\n - child with `code`');
|
|
73
|
+
expect(out).toContain('• <b>parent</b>');
|
|
74
|
+
expect(out).toContain('• child with <code>code</code>');
|
|
75
|
+
});
|
|
76
|
+
it('escapes raw HTML inside list items', ()=>{
|
|
77
|
+
const out = markdownToTelegramHtml('- literal <script>alert(1)</script> text');
|
|
78
|
+
expect(out).toContain('<script>');
|
|
79
|
+
expect(out).not.toContain('<script>');
|
|
80
|
+
});
|
|
81
|
+
it('preserves ordered list start numbering with inline markup', ()=>{
|
|
82
|
+
const out = markdownToTelegramHtml('3. **three**\n4. *four*');
|
|
83
|
+
expect(out).toContain('3. <b>three</b>');
|
|
84
|
+
expect(out).toContain('4. <i>four</i>');
|
|
85
|
+
});
|
|
86
|
+
it('renders links as HTML inside list items', ()=>{
|
|
87
|
+
const out = markdownToTelegramHtml('- See [docs](https://example.com?a=1&b=2).');
|
|
88
|
+
expect(out).toContain('• See <a href="https://example.com?a=1&b=2">docs</a>.');
|
|
89
|
+
expect(out).not.toContain('<a href=');
|
|
90
|
+
});
|
|
48
91
|
it('renders tables as ASCII inside <pre>', ()=>{
|
|
49
|
-
const md = '| a | b |\n|---|---|\n| 1 | 2 |';
|
|
92
|
+
const md = '| a | b |\n|---|---|\n| 1 | 2 |\n| 3 |';
|
|
50
93
|
const out = markdownToTelegramHtml(md);
|
|
51
94
|
expect(out.startsWith('<pre>')).toBe(true);
|
|
52
95
|
expect(out).toContain('a');
|
|
53
96
|
expect(out).toContain('b');
|
|
54
97
|
expect(out).toContain('1');
|
|
55
98
|
expect(out).toContain('2');
|
|
99
|
+
expect(out).toContain('3');
|
|
56
100
|
expect(out).not.toContain('<table>');
|
|
57
101
|
});
|
|
58
102
|
it('uses <blockquote> for quotes', ()=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/__tests__/utils/telegramHtml.test.ts"],"sourcesContent":["import { markdownToTelegramHtml } from '../../utils/telegramHtml.js';\n\ndescribe('markdownToTelegramHtml', () => {\n it('renders bold, italic, strikethrough', () => {\n const out = markdownToTelegramHtml('**b** _i_ ~~s~~');\n expect(out).toContain('<b>b</b>');\n expect(out).toContain('<i>i</i>');\n expect(out).toContain('<s>s</s>');\n });\n\n it('renders inline code and fenced code with language', () => {\n const md = 'Run `npm test`\\n\\n```ts\\nconst x = 1;\\n```';\n const out = markdownToTelegramHtml(md);\n expect(out).toContain('<code>npm test</code>');\n expect(out).toContain('<pre><code class=\"language-ts\">const x = 1;</code></pre>');\n });\n\n it('renders links and converts headings to bold', () => {\n const md = '# Title\\n\\nSee [docs](https://x.com).';\n const out = markdownToTelegramHtml(md);\n expect(out).toContain('<b>Title</b>');\n expect(out).toContain('<a href=\"https://x.com\">docs</a>');\n });\n\n it('renders images as alt-text links', () => {\n const out = markdownToTelegramHtml('');\n expect(out).toBe('<a href=\"https://x.com/d.png\">diagram</a>');\n });\n\n it('falls back to URL when image has no alt text', () => {\n const out = markdownToTelegramHtml('');\n expect(out).toContain('<a href=\"https://x.com/d.png\">https://x.com/d.png</a>');\n });\n\n it('renders unordered lists with bullets', () => {\n const out = markdownToTelegramHtml('- one\\n- two\\n- three');\n expect(out).toContain('• one');\n expect(out).toContain('• two');\n expect(out).toContain('• three');\n expect(out).not.toContain('<ul>');\n });\n\n it('renders nested lists without throwing', () => {\n const out = markdownToTelegramHtml('- agent\\n - Status');\n\n expect(out).toContain('• agent');\n expect(out).toContain('• Status');\n expect(out).not.toContain('<ul>');\n });\n\n it('renders ordered lists with numbers', () => {\n const out = markdownToTelegramHtml('1. one\\n2. two');\n expect(out).toContain('1. one');\n expect(out).toContain('2. two');\n expect(out).not.toContain('<ol>');\n });\n\n it('renders tables as ASCII inside <pre>', () => {\n const md = '| a | b |\\n|---|---|\\n| 1 | 2 |';\n const out = markdownToTelegramHtml(md);\n expect(out.startsWith('<pre>')).toBe(true);\n expect(out).toContain('a');\n expect(out).toContain('b');\n expect(out).toContain('1');\n expect(out).toContain('2');\n expect(out).not.toContain('<table>');\n });\n\n it('uses <blockquote> for quotes', () => {\n const out = markdownToTelegramHtml('> quoted');\n expect(out).toContain('<blockquote>');\n expect(out).toContain('quoted');\n expect(out).toContain('</blockquote>');\n });\n\n it('escapes HTML special chars in plain text', () => {\n const out = markdownToTelegramHtml('a < b && c > d');\n expect(out).toContain('<');\n expect(out).toContain('&');\n expect(out).toContain('>');\n expect(out).not.toContain(' < ');\n });\n\n it('escapes HTML special chars inside code', () => {\n const out = markdownToTelegramHtml('`<script>`');\n expect(out).toContain('<code><script></code>');\n });\n\n it('strips raw HTML blocks', () => {\n const out = markdownToTelegramHtml('hello\\n\\n<div>raw</div>\\n\\nworld');\n expect(out).not.toContain('<div>');\n expect(out).toContain('hello');\n expect(out).toContain('world');\n });\n\n it('renders horizontal rule as a divider', () => {\n const out = markdownToTelegramHtml('above\\n\\n---\\n\\nbelow');\n expect(out).toContain('above');\n expect(out).toContain('———');\n expect(out).toContain('below');\n });\n\n it('passes plain text through unchanged', () => {\n expect(markdownToTelegramHtml('hello world')).toBe('hello world');\n });\n});\n"],"names":["markdownToTelegramHtml","describe","it","out","expect","toContain","md","toBe","not","startsWith"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/utils/telegramHtml.test.ts"],"sourcesContent":["import { markdownToTelegramHtml } from '../../utils/telegramHtml.js';\n\ndescribe('markdownToTelegramHtml', () => {\n it('renders bold, italic, strikethrough', () => {\n const out = markdownToTelegramHtml('**b** _i_ ~~s~~');\n expect(out).toContain('<b>b</b>');\n expect(out).toContain('<i>i</i>');\n expect(out).toContain('<s>s</s>');\n });\n\n it('renders inline code and fenced code with language', () => {\n const md = 'Run `npm test`\\n\\n```ts\\nconst x = 1;\\n```\\n\\n```\\nplain\\n```';\n const out = markdownToTelegramHtml(md);\n expect(out).toContain('<code>npm test</code>');\n expect(out).toContain('<pre><code class=\"language-ts\">const x = 1;</code></pre>');\n expect(out).toContain('<pre><code>plain</code></pre>');\n });\n\n it('renders links and converts headings to bold', () => {\n const md = '# Title\\n\\nSee [docs](https://x.com).';\n const out = markdownToTelegramHtml(md);\n expect(out).toContain('<b>Title</b>');\n expect(out).toContain('<a href=\"https://x.com\">docs</a>');\n });\n\n it('renders images as alt-text links', () => {\n const out = markdownToTelegramHtml('');\n expect(out).toBe('<a href=\"https://x.com/d.png\">diagram</a>');\n });\n\n it('falls back to URL when image has no alt text', () => {\n const out = markdownToTelegramHtml('');\n expect(out).toContain('<a href=\"https://x.com/d.png\">https://x.com/d.png</a>');\n });\n\n it('renders unordered lists with bullets', () => {\n const out = markdownToTelegramHtml('- one\\n- two\\n- three');\n expect(out).toContain('• one');\n expect(out).toContain('• two');\n expect(out).toContain('• three');\n expect(out).not.toContain('<ul>');\n });\n\n it('renders nested lists without throwing', () => {\n const out = markdownToTelegramHtml('- agent\\n - Status');\n\n expect(out).toContain('• agent');\n expect(out).toContain('• Status');\n expect(out).not.toContain('<ul>');\n });\n\n it('renders ordered lists with numbers', () => {\n const out = markdownToTelegramHtml('1. one\\n2. two');\n expect(out).toContain('1. one');\n expect(out).toContain('2. two');\n expect(out).not.toContain('<ol>');\n });\n\n it('renders inline markup as HTML inside ordered tight lists', () => {\n const out = markdownToTelegramHtml('1. **bold** `code` *italic*');\n\n expect(out).toContain('1. <b>bold</b> <code>code</code> <i>italic</i>');\n expect(out).not.toContain('<b>');\n expect(out).not.toMatch(/\\*\\*|`/);\n });\n\n it('renders inline markup as HTML inside unordered tight lists', () => {\n const out = markdownToTelegramHtml('- **bold** `code` *italic*');\n\n expect(out).toContain('• <b>bold</b> <code>code</code> <i>italic</i>');\n expect(out).not.toMatch(/<(?:b|code|i)>/);\n });\n\n it('renders inline markup in the middle of list item text', () => {\n const out = markdownToTelegramHtml(\n '- While editing, `/x` is a **literal** character.'\n );\n\n expect(out).toContain(\n '• While editing, <code>/x</code> is a <b>literal</b> character.'\n );\n });\n\n it('renders inline markup in loose multi-paragraph lists', () => {\n const out = markdownToTelegramHtml(\n '- First **bold** paragraph.\\n\\n Second paragraph with *italic*.\\n\\n- `code` item.'\n );\n\n expect(out).toContain('• First <b>bold</b> paragraph.');\n expect(out).toContain('Second paragraph with <i>italic</i>.');\n expect(out).toContain('• <code>code</code> item.');\n expect(out).not.toMatch(/<(?:b|code|i)>/);\n });\n\n it('renders inline markup in nested list items', () => {\n const out = markdownToTelegramHtml('- **parent**\\n - child with `code`');\n\n expect(out).toContain('• <b>parent</b>');\n expect(out).toContain('• child with <code>code</code>');\n });\n\n it('escapes raw HTML inside list items', () => {\n const out = markdownToTelegramHtml('- literal <script>alert(1)</script> text');\n\n expect(out).toContain('<script>');\n expect(out).not.toContain('<script>');\n });\n\n it('preserves ordered list start numbering with inline markup', () => {\n const out = markdownToTelegramHtml('3. **three**\\n4. *four*');\n\n expect(out).toContain('3. <b>three</b>');\n expect(out).toContain('4. <i>four</i>');\n });\n\n it('renders links as HTML inside list items', () => {\n const out = markdownToTelegramHtml('- See [docs](https://example.com?a=1&b=2).');\n\n expect(out).toContain(\n '• See <a href=\"https://example.com?a=1&b=2\">docs</a>.'\n );\n expect(out).not.toContain('<a href=');\n });\n\n it('renders tables as ASCII inside <pre>', () => {\n const md = '| a | b |\\n|---|---|\\n| 1 | 2 |\\n| 3 |';\n const out = markdownToTelegramHtml(md);\n expect(out.startsWith('<pre>')).toBe(true);\n expect(out).toContain('a');\n expect(out).toContain('b');\n expect(out).toContain('1');\n expect(out).toContain('2');\n expect(out).toContain('3');\n expect(out).not.toContain('<table>');\n });\n\n it('uses <blockquote> for quotes', () => {\n const out = markdownToTelegramHtml('> quoted');\n expect(out).toContain('<blockquote>');\n expect(out).toContain('quoted');\n expect(out).toContain('</blockquote>');\n });\n\n it('escapes HTML special chars in plain text', () => {\n const out = markdownToTelegramHtml('a < b && c > d');\n expect(out).toContain('<');\n expect(out).toContain('&');\n expect(out).toContain('>');\n expect(out).not.toContain(' < ');\n });\n\n it('escapes HTML special chars inside code', () => {\n const out = markdownToTelegramHtml('`<script>`');\n expect(out).toContain('<code><script></code>');\n });\n\n it('strips raw HTML blocks', () => {\n const out = markdownToTelegramHtml('hello\\n\\n<div>raw</div>\\n\\nworld');\n expect(out).not.toContain('<div>');\n expect(out).toContain('hello');\n expect(out).toContain('world');\n });\n\n it('renders horizontal rule as a divider', () => {\n const out = markdownToTelegramHtml('above\\n\\n---\\n\\nbelow');\n expect(out).toContain('above');\n expect(out).toContain('———');\n expect(out).toContain('below');\n });\n\n it('passes plain text through unchanged', () => {\n expect(markdownToTelegramHtml('hello world')).toBe('hello world');\n });\n});\n"],"names":["markdownToTelegramHtml","describe","it","out","expect","toContain","md","toBe","not","toMatch","startsWith"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IncomingMessage } from '../types.js';
|
|
1
|
+
import type { ChannelQuestion, IncomingInteraction, IncomingMessage, SendMessageOptions, SentMessage } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Interface for messaging platform adapters.
|
|
4
4
|
*
|
|
@@ -17,7 +17,7 @@ export interface ChannelAdapter {
|
|
|
17
17
|
* Implementations should handle platform-specific limits
|
|
18
18
|
* (e.g., chunking at 4096 chars for Telegram).
|
|
19
19
|
*/
|
|
20
|
-
sendMessage(chatId: string, text: string): Promise<void>;
|
|
20
|
+
sendMessage(chatId: string, text: string, options?: SendMessageOptions): Promise<SentMessage | void>;
|
|
21
21
|
/**
|
|
22
22
|
* Register a handler for incoming text messages.
|
|
23
23
|
* Fire-and-forget — handler returns void.
|
|
@@ -27,4 +27,10 @@ export interface ChannelAdapter {
|
|
|
27
27
|
/** Check if the adapter is connected and healthy */
|
|
28
28
|
isHealthy(): Promise<boolean>;
|
|
29
29
|
}
|
|
30
|
+
export interface InteractiveChannelAdapter extends ChannelAdapter {
|
|
31
|
+
onInteraction(handler: (interaction: IncomingInteraction) => Promise<void>): void;
|
|
32
|
+
sendQuestion(chatId: string, question: ChannelQuestion): Promise<SentMessage>;
|
|
33
|
+
finalizeInteraction(chatId: string, messageId: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare function isInteractiveChannelAdapter(adapter: ChannelAdapter): adapter is InteractiveChannelAdapter;
|
|
30
36
|
//# sourceMappingURL=ChannelAdapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/ChannelAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ChannelAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/ChannelAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,WAAW,EACd,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB;;;;OAIG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAErG;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAElE,oDAAoD;IACpD,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,yBAA0B,SAAQ,cAAc;IAC7D,aAAa,CAAC,OAAO,EAAE,CAAC,WAAW,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClF,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9E,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,IAAI,yBAAyB,CAIzG"}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Implementations connect to a specific platform (Telegram, Slack, etc.)
|
|
5
|
-
* and provide a generic send/receive abstraction.
|
|
6
|
-
*/ export { };
|
|
1
|
+
export function isInteractiveChannelAdapter(adapter) {
|
|
2
|
+
return 'onInteraction' in adapter && 'sendQuestion' in adapter && 'finalizeInteraction' in adapter;
|
|
3
|
+
}
|
|
7
4
|
|
|
8
5
|
//# sourceMappingURL=ChannelAdapter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/ChannelAdapter.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/ChannelAdapter.ts"],"sourcesContent":["import type {\n ChannelQuestion,\n IncomingInteraction,\n IncomingMessage,\n SendMessageOptions,\n SentMessage,\n} from '../types.js';\n\n/**\n * Interface for messaging platform adapters.\n *\n * Implementations connect to a specific platform (Telegram, Slack, etc.)\n * and provide a generic send/receive abstraction.\n */\nexport interface ChannelAdapter {\n /** Identifier for this channel type (e.g., 'telegram') */\n readonly type: string;\n\n /** Start listening for incoming messages */\n start(): Promise<void>;\n\n /** Stop listening and clean up resources */\n stop(): Promise<void>;\n\n /**\n * Send a message to a specific chat.\n * Implementations should handle platform-specific limits\n * (e.g., chunking at 4096 chars for Telegram).\n */\n sendMessage(chatId: string, text: string, options?: SendMessageOptions): Promise<SentMessage | void>;\n\n /**\n * Register a handler for incoming text messages.\n * Fire-and-forget — handler returns void.\n * Responses are sent separately via sendMessage().\n */\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void;\n\n /** Check if the adapter is connected and healthy */\n isHealthy(): Promise<boolean>;\n}\n\nexport interface InteractiveChannelAdapter extends ChannelAdapter {\n onInteraction(handler: (interaction: IncomingInteraction) => Promise<void>): void;\n sendQuestion(chatId: string, question: ChannelQuestion): Promise<SentMessage>;\n finalizeInteraction(chatId: string, messageId: string): Promise<void>;\n}\n\nexport function isInteractiveChannelAdapter(adapter: ChannelAdapter): adapter is InteractiveChannelAdapter {\n return 'onInteraction' in adapter\n && 'sendQuestion' in adapter\n && 'finalizeInteraction' in adapter;\n}\n"],"names":["isInteractiveChannelAdapter","adapter"],"mappings":"AAgDA,OAAO,SAASA,4BAA4BC,OAAuB;IAC/D,OAAO,mBAAmBA,WACnB,kBAAkBA,WAClB,yBAAyBA;AACpC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { InteractiveChannelAdapter } from './ChannelAdapter.js';
|
|
2
|
+
import type { ChannelQuestion, IncomingInteraction, IncomingMessage, SentMessage, SlackConfig } from '../types.js';
|
|
3
|
+
export declare const SLACK_CHANNEL_TYPE = "slack";
|
|
4
|
+
interface SlackAuthClient {
|
|
5
|
+
authTest(): Promise<{
|
|
6
|
+
ok?: boolean;
|
|
7
|
+
app_id?: string;
|
|
8
|
+
user_id?: string;
|
|
9
|
+
team_id?: string;
|
|
10
|
+
team?: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export interface SlackIdentity {
|
|
14
|
+
appId?: string;
|
|
15
|
+
botUserId: string;
|
|
16
|
+
workspaceId: string;
|
|
17
|
+
workspaceName?: string;
|
|
18
|
+
}
|
|
19
|
+
interface SlackConnectionsClient {
|
|
20
|
+
open(): Promise<{
|
|
21
|
+
ok?: boolean;
|
|
22
|
+
url?: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export declare function validateSlackAppToken(appToken: string, client?: SlackConnectionsClient): Promise<void>;
|
|
26
|
+
export declare function validateSlackCredentials(botToken: string, client?: SlackAuthClient): Promise<SlackIdentity>;
|
|
27
|
+
interface SocketClientLike {
|
|
28
|
+
on(event: string, listener: (payload?: unknown) => void): unknown;
|
|
29
|
+
start(): Promise<unknown>;
|
|
30
|
+
disconnect(): Promise<unknown>;
|
|
31
|
+
}
|
|
32
|
+
interface WebClientLike {
|
|
33
|
+
chat: {
|
|
34
|
+
postMessage(input: Record<string, unknown>): Promise<{
|
|
35
|
+
ok?: boolean;
|
|
36
|
+
ts?: string;
|
|
37
|
+
}>;
|
|
38
|
+
update?(input: Record<string, unknown>): Promise<unknown>;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
interface SlackAdapterDependencies {
|
|
42
|
+
socketClient?: SocketClientLike;
|
|
43
|
+
webClient?: WebClientLike;
|
|
44
|
+
}
|
|
45
|
+
export declare class SlackAdapter implements InteractiveChannelAdapter {
|
|
46
|
+
private readonly config;
|
|
47
|
+
readonly type = "slack";
|
|
48
|
+
private readonly socket;
|
|
49
|
+
private readonly web;
|
|
50
|
+
private readonly delivery;
|
|
51
|
+
private readonly recentEventIds;
|
|
52
|
+
private messageHandler;
|
|
53
|
+
private interactionHandler;
|
|
54
|
+
private running;
|
|
55
|
+
private listenersRegistered;
|
|
56
|
+
constructor(config: SlackConfig, dependencies?: SlackAdapterDependencies);
|
|
57
|
+
start(): Promise<void>;
|
|
58
|
+
stop(): Promise<void>;
|
|
59
|
+
onMessage(handler: (message: IncomingMessage) => Promise<void>): void;
|
|
60
|
+
onInteraction(handler: (interaction: IncomingInteraction) => Promise<void>): void;
|
|
61
|
+
sendMessage(chatId: string, text: string): Promise<SentMessage>;
|
|
62
|
+
isHealthy(): Promise<boolean>;
|
|
63
|
+
sendQuestion(chatId: string, question: ChannelQuestion): Promise<SentMessage>;
|
|
64
|
+
finalizeInteraction(chatId: string, messageId: string): Promise<void>;
|
|
65
|
+
private handleSlackEvent;
|
|
66
|
+
private isValidDirectMessage;
|
|
67
|
+
private rememberEvent;
|
|
68
|
+
private handleInteraction;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=SlackAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SlackAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/SlackAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,KAAK,EACR,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,WAAW,EACX,WAAW,EACd,MAAM,aAAa,CAAC;AAIrB,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAE1C,UAAU,eAAe;IACrB,QAAQ,IAAI,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7G;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,UAAU,sBAAsB;IAC5B,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD;AAED,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5G;AAED,wBAAsB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,CAcjH;AAED,UAAU,gBAAgB;IACtB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC;IAClE,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1B,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAClC;AAED,UAAU,aAAa;IACnB,IAAI,EAAE;QACF,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;YAAE,EAAE,CAAC,EAAE,OAAO,CAAC;YAAC,EAAE,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACpF,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAC7D,CAAC;CACL;AAED,UAAU,wBAAwB;IAC9B,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,SAAS,CAAC,EAAE,aAAa,CAAC;CAC7B;AAaD,qBAAa,YAAa,YAAW,yBAAyB;IAW9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IAVnC,QAAQ,CAAC,IAAI,WAAsB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,cAAc,CAA8D;IACpF,OAAO,CAAC,kBAAkB,CAAsE;IAChG,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,mBAAmB,CAAS;gBAEP,MAAM,EAAE,WAAW,EAAE,YAAY,GAAE,wBAA6B;IAQvF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIrE,aAAa,CAAC,OAAO,EAAE,CAAC,WAAW,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAI3E,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAI/D,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAI7B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IA6B7E,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAK7D,gBAAgB;IA4B9B,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,aAAa;YASP,iBAAiB;CAoClC"}
|