@toolpack-sdk/agents 2.1.1 → 2.2.0
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 +72 -4
- package/dist/channels/index.cjs +2 -2
- package/dist/channels/index.d.cts +1 -1
- package/dist/channels/index.d.ts +1 -1
- package/dist/channels/index.js +2 -2
- package/dist/eval-report-BCGixIYd.d.cts +343 -0
- package/dist/eval-report-CpdAMa2b.d.ts +343 -0
- package/dist/{index-Du6S0eG7.d.cts → index-DVhRtkNq.d.cts} +75 -1
- package/dist/{index-o8Lbzv5N.d.ts → index-dhKoHWTy.d.ts} +75 -1
- package/dist/index.cjs +40 -26
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +40 -26
- package/dist/interceptors/index.cjs +1 -1
- package/dist/interceptors/index.d.cts +88 -1
- package/dist/interceptors/index.d.ts +88 -1
- package/dist/interceptors/index.js +1 -1
- package/dist/testing/index.cjs +16 -2
- package/dist/testing/index.d.cts +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +16 -2
- package/package.json +18 -9
package/README.md
CHANGED
|
@@ -8,12 +8,14 @@ Build production-ready AI agents with channels, workflows, and event-driven arch
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
10
|
- **4 Built-in Agents** — Research, Coding, Data, Browser
|
|
11
|
-
- **
|
|
11
|
+
- **8 Channel Types** — Slack, Telegram, Discord, Email, SMS, Webhook, Scheduled, MCP
|
|
12
12
|
- **Event-Driven** — Full lifecycle hooks and events
|
|
13
13
|
- **Human-in-the-Loop** — `ask()` support for two-way channels
|
|
14
14
|
- **Knowledge Integration** — Built-in RAG support with knowledge bases
|
|
15
|
+
- **Agent Mind** — Persistent cognitive layer: goals, beliefs, reflections, cross-run recall
|
|
16
|
+
- **Evals** — `EvalDataset`, `EvalRunner`, 4 scorer types, regression reports
|
|
17
|
+
- **OTel Tracing** — OpenTelemetry interceptor for distributed traces
|
|
15
18
|
- **Type-Safe** — Full TypeScript support
|
|
16
|
-
- **Production-Ready** — 573 tests passing
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
19
21
|
|
|
@@ -239,6 +241,27 @@ const smsOutbound = new SMSChannel({
|
|
|
239
241
|
});
|
|
240
242
|
```
|
|
241
243
|
|
|
244
|
+
### McpChannel (Two-way)
|
|
245
|
+
|
|
246
|
+
Exposes a Toolpack agent as a tool in an MCP server. The agent appears in `tools/list` as `agent.<name>` and is callable by any MCP client.
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { McpChannel } from '@toolpack-sdk/agents';
|
|
250
|
+
import { Toolpack } from 'toolpack-sdk';
|
|
251
|
+
|
|
252
|
+
const ch = new McpChannel({ name: 'mcp' });
|
|
253
|
+
const agent = new PrReviewerAgent({ channels: [ch] });
|
|
254
|
+
await agent.start();
|
|
255
|
+
|
|
256
|
+
const sdk = await Toolpack.init({ provider: 'anthropic', tools: true });
|
|
257
|
+
await sdk.startMcpServer({
|
|
258
|
+
transport: 'stdio', // or 'http' with port
|
|
259
|
+
agents: [ch.asAgentDefinition(agent)],
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`ch.asAgentDefinition(agent)` produces the entry that `startMcpServer` registers in `tools/list`. Each MCP `tools/call` for `agent.<name>` is routed through the channel to `agent.invokeAgent()` and the output is returned as the tool result.
|
|
264
|
+
|
|
242
265
|
## Creating Custom Agents
|
|
243
266
|
|
|
244
267
|
Extend `BaseAgent` to create custom agents:
|
|
@@ -763,6 +786,7 @@ class MyAgent extends BaseAgent {
|
|
|
763
786
|
| `createCaptureInterceptor` | Persist inbound and outbound messages to conversation history (auto-registered) |
|
|
764
787
|
| `createDepthGuardInterceptor` | Reject delegation chains that exceed a configured depth |
|
|
765
788
|
| `createTracerInterceptor` | Structured logging of each chain hop for debugging |
|
|
789
|
+
| `createOTelTracerInterceptor` | OpenTelemetry span per invocation — compatible with any OTel-compliant backend |
|
|
766
790
|
|
|
767
791
|
## Capabilities
|
|
768
792
|
|
|
@@ -818,14 +842,58 @@ const result = await summarizer.invokeAgent({
|
|
|
818
842
|
const summary = JSON.parse(result.output) as SummarizerOutput;
|
|
819
843
|
```
|
|
820
844
|
|
|
845
|
+
## Evals — LLM Quality Evaluation
|
|
846
|
+
|
|
847
|
+
Unit tests verify wiring; evals verify agent **quality**. Use the eval primitives to build regression suites and track answer quality over time.
|
|
848
|
+
|
|
849
|
+
```typescript
|
|
850
|
+
import {
|
|
851
|
+
EvalDataset,
|
|
852
|
+
EvalRunner,
|
|
853
|
+
ContainsScorer,
|
|
854
|
+
LLMJudgeScorer,
|
|
855
|
+
compareEvalRuns,
|
|
856
|
+
formatEvalReport,
|
|
857
|
+
} from '@toolpack-sdk/agents';
|
|
858
|
+
|
|
859
|
+
const dataset = new EvalDataset([
|
|
860
|
+
{ id: 'q1', input: 'What is 2+2?', expectedOutput: '4' },
|
|
861
|
+
{ id: 'q2', input: 'Capital of France?', expectedOutput: 'Paris' },
|
|
862
|
+
]);
|
|
863
|
+
|
|
864
|
+
const runner = new EvalRunner({
|
|
865
|
+
agent: myAgent,
|
|
866
|
+
dataset,
|
|
867
|
+
scorers: [new ContainsScorer()],
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
const run = await runner.run();
|
|
871
|
+
console.log(`Average score: ${(run.averageScore * 100).toFixed(1)}%`);
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
**Four built-in scorers:**
|
|
875
|
+
|
|
876
|
+
| Scorer | When to use |
|
|
877
|
+
|---|---|
|
|
878
|
+
| `ExactMatchScorer` | Deterministic outputs — exact string match |
|
|
879
|
+
| `ContainsScorer` | Output must contain the expected string |
|
|
880
|
+
| `LLMJudgeScorer` | Open-ended answers — ask an LLM to grade on 0–1 |
|
|
881
|
+
| `CustomScorer` | Any custom scoring logic |
|
|
882
|
+
|
|
883
|
+
**Regression detection:**
|
|
884
|
+
|
|
885
|
+
```typescript
|
|
886
|
+
const report = compareEvalRuns(baselineRun, currentRun);
|
|
887
|
+
console.log(formatEvalReport(report));
|
|
888
|
+
expect(report.regressions).toHaveLength(0); // CI gate
|
|
889
|
+
```
|
|
890
|
+
|
|
821
891
|
## Testing
|
|
822
892
|
|
|
823
893
|
```bash
|
|
824
894
|
npm test
|
|
825
895
|
```
|
|
826
896
|
|
|
827
|
-
**Test Coverage:** 573 tests passing across 29 test files.
|
|
828
|
-
|
|
829
897
|
## License
|
|
830
898
|
|
|
831
899
|
Apache 2.0 © Toolpack SDK
|
package/dist/channels/index.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var M=Object.create;var C=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var R=Object.getPrototypeOf,N=Object.prototype.hasOwnProperty;var O=(a,e)=>{for(var n in e)C(a,n,{get:e[n],enumerable:!0})},_=(a,e,n,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of E(e))!N.call(a,r)&&r!==n&&C(a,r,{get:()=>e[r],enumerable:!(t=D(e,r))||t.enumerable});return a};var p=(a,e,n)=>(n=a!=null?M(R(a)):{},_(e||!a||!a.__esModule?C(n,"default",{value:a,enumerable:!0}):n,a)),U=a=>_(C({},"__esModule",{value:!0}),a);var z={};O(z,{BaseChannel:()=>d,DiscordChannel:()=>I,EmailChannel:()=>T,SMSChannel:()=>P,ScheduledChannel:()=>S,SlackChannel:()=>v,TelegramChannel:()=>b,WebhookChannel:()=>y});module.exports=U(z);var d=class{name;_handler;onMessage(e){this._handler=e}async handleMessage(e){this._handler&&await this._handler(e)}};var k=require("crypto");var v=class extends d{isTriggerChannel=!1;config;server;participantCache=new Map;botUserId;allowedChannels;constructor(e){super(),this.config={port:3e3,...e},this.name=e.name;let n=e.channel;this.allowedChannels=n==null?null:Array.isArray(n)?n:[n]}listen(){typeof process<"u"&&import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[SlackChannel] Listening on port ${this.config.port}`),this.runStartupCheck().catch(()=>{})})}).catch(e=>{console.error("[SlackChannel] Failed to start HTTP server:",e)})}async runStartupCheck(){try{let n=await(await fetch("https://slack.com/api/auth.test",{method:"POST",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"}})).json();n.ok?(this.botUserId=n.user_id,console.log(`[SlackChannel] Connected as @${n.user} (${n.user_id}) in workspace "${n.team}" \u2014 ${n.url}`)):console.warn(`[SlackChannel] auth.test failed: ${n.error}. Check your bot token.`)}catch(e){console.warn("[SlackChannel] Startup self-check failed (network error):",e)}}verifySignature(e,n){let t=e["x-slack-request-timestamp"],r=e["x-slack-signature"];if(!t||!r||Array.isArray(t)||Array.isArray(r))return!1;let i=parseInt(t,10),s=Math.floor(Date.now()/1e3);if(isNaN(i)||Math.abs(s-i)>300)return!1;let o=`v0:${t}:${n}`,c=`v0=${(0,k.createHmac)("sha256",this.config.signingSecret).update(o).digest("hex")}`;if(c.length!==r.length)return!1;try{return(0,k.timingSafeEqual)(Buffer.from(c),Buffer.from(r))}catch{return!1}}async send(e){let n=e.metadata?.threadTs??e.metadata?.thread_ts??e.metadata?.threadId,r=e.metadata?.channelId??(this.allowedChannels&&this.allowedChannels.length>0?this.allowedChannels[0]:void 0);if(!r)throw new Error("[SlackChannel] Cannot send: no channel configured and metadata.channelId is missing. Provide a target via SlackChannelConfig.channel or output.metadata.channelId.");let i={channel:r,text:e.output};n&&(i.thread_ts=n);let s=await fetch("https://slack.com/api/chat.postMessage",{method:"POST",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok)throw new Error(`Failed to send Slack message: ${s.statusText}`);let o=await s.json();if(!o.ok)throw new Error(`Slack API error: ${o.error}`)}normalize(e){let n=e,r=(n.text||"").replace(/<(https?:\/\/[^|>]+)\|([^>]+)>/g,"$2 ($1)").replace(/<(https?:\/\/[^>]+)>/g,"$1").replace(/<!here>/g,"@here").replace(/<!channel>/g,"@channel").replace(/<!everyone>/g,"@everyone"),i=n.ts,s=n.thread_ts,o=s!==void 0&&s!==i,l=n.user,c=l?{kind:"user",id:l}:void 0,f=/<@([A-Z0-9]+)>/g,u=[],m;for(;(m=f.exec(r))!==null;)u.push(m[1]);let g=n.channel;return{message:r,conversationId:o?s:g||i||"",data:n,participant:c,context:{user:l,channel:g,team:n.team,channelType:n.channel_type,threadId:o?s:void 0,mentions:u.length>0?u:void 0,channelId:g,channelName:typeof this.config.channel=="string"?this.config.channel:g}}}async resolveParticipant(e){let n=e.participant?.id??e.context?.user;if(!n)return;let t=this.participantCache.get(n);if(t)return t;try{let r=await fetch(`https://slack.com/api/users.info?user=${encodeURIComponent(n)}`,{method:"GET",headers:{Authorization:`Bearer ${this.config.token}`}});if(!r.ok)return{kind:"user",id:n};let i=await r.json();if(!i.ok||!i.user){let l={kind:"user",id:n};return this.participantCache.set(n,l),l}let s=i.user.profile?.display_name||i.user.profile?.real_name||i.user.real_name||i.user.name||n,o={kind:"user",id:n,displayName:s,metadata:{slackUser:i.user}};return this.participantCache.set(n,o),o}catch{return{kind:"user",id:n}}}invalidateParticipant(e){this.participantCache.delete(e)}shouldProcessEvent(e){let n=e.type;if(n!=="message"&&n!=="app_mention")return!1;if(this.allowedChannels!==null){let s=e.channel_type;if(!(s==="im"||s==="mpim")){let l=e.channel;if(!l||!this.allowedChannels.includes(l))return!1}}let t=e.user;if(this.botUserId&&t===this.botUserId)return!1;let r=e.bot_id;if(!r)return!0;let i=this.config.blockedBotIds??[];if(i.includes(r)||t!==void 0&&i.includes(t))return!1;if(this.config.allowedBotIds!==void 0){let s=this.config.allowedBotIds;return s.includes(r)||t!==void 0&&s.includes(t)}return!0}handleRequest(e,n){if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",r=>{t+=r.toString()}),e.on("end",()=>{if(!this.verifySignature(e.headers,t)){n.writeHead(401),n.end("Invalid signature");return}try{let r=JSON.parse(t);if(r.type==="url_verification"){n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({challenge:r.challenge}));return}if(r.type==="event_callback"&&r.event){let i=r.event;if(this.shouldProcessEvent(i)){let s=this.normalize(i);this.handleMessage(s)}else i.type==="user_change"&&i.user&&this.invalidateParticipant(i.user.id);n.writeHead(200),n.end("OK");return}n.writeHead(200),n.end("OK")}catch(r){console.error("[SlackChannel] Error handling request:",r),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};var y=class extends d{isTriggerChannel=!1;config;server;pendingResponses=new Map;constructor(e){super(),this.name=e.name,this.config={port:e.port??3e3,path:e.path??"/webhook"}}listen(){typeof process<"u"&&import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[WebhookChannel] Listening on port ${this.config.port}${this.config.path}`)})}).catch(e=>{console.error("[WebhookChannel] Failed to start HTTP server:",e)})}async send(e){let n=e.metadata?.conversationId;if(n&&this.pendingResponses.has(n)){let t=this.pendingResponses.get(n);this.pendingResponses.delete(n),t.resolve({output:e.output,metadata:e.metadata})}}normalize(e){let n=e,t=n.headers||{},r=t["x-session-id"]||t["X-Session-Id"]||n.sessionId||n.conversationId||this.generateSessionId();return{message:n.message||n.text||"",intent:n.intent,conversationId:r,data:n,context:{headers:n.headers,method:n.method,sessionId:r}}}handleRequest(e,n){if(e.url!==this.config.path){n.writeHead(404),n.end("Not found");return}if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",r=>{t+=r.toString()}),e.on("end",()=>{try{let r=JSON.parse(t),i=this.normalize(r),s=i.conversationId||this.generateSessionId(),o=new Promise((l,c)=>{this.pendingResponses.set(s,{resolve:l,reject:c}),setTimeout(()=>{this.pendingResponses.has(s)&&(this.pendingResponses.delete(s),c(new Error("Agent response timeout")))},3e4)});this.handleMessage({...i,conversationId:s,context:{...i.context,sessionId:s}}),o.then(l=>{n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify(l))}).catch(l=>{n.writeHead(500,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:l.message}))})}catch(r){console.error("[WebhookChannel] Error handling request:",r),n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Bad request"}))}})}generateSessionId(){return`webhook-${Date.now()}-${Math.random().toString(36).substring(2,9)}`}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};var x=require("cron-parser"),S=class extends d{isTriggerChannel=!0;config;timer;_stopped=!1;_generation=0;constructor(e){if(super(),!e.cron&&!e.store)throw new Error("ScheduledChannel: provide at least one of `cron` (static schedule) or `store` (dynamic scheduling).");if(e.cron)try{x.CronExpressionParser.parse(e.cron)}catch(n){throw new Error(`ScheduledChannel: invalid cron expression '${e.cron}': ${n.message}`)}if(e.store&&!e.name&&console.warn("[ScheduledChannel] A `store` was provided without a `name`. All store queries will be unscoped and will pick up jobs from every channel. Set `name` to scope this channel to its own jobs."),e.idlePollMs!==void 0&&e.idlePollMs<1e3)throw new Error(`ScheduledChannel: idlePollMs must be at least 1000ms (got ${e.idlePollMs}). Values below 1 second create a tight polling loop.`);this.config=e,this.name=e.name}listen(){this._generation++,this.timer&&(clearTimeout(this.timer),this.timer=void 0),this._stopped=!1,this.config.store?this._listenWithStore():this._listenStatic()}async stop(){this._stopped=!0,this.timer&&(clearTimeout(this.timer),this.timer=void 0)}async send(e){}normalize(e){let n=e,t=new Date,r=`${t.getFullYear()}-${t.getMonth()+1}-${t.getDate()}`;return{intent:n?.intent??this.config.intent,message:n?.message??this.config.message??`Scheduled task triggered at ${t.toISOString()}`,conversationId:`scheduled:${this.name??"default"}:${r}`,data:{...n?.payload??{},scheduled:!0,jobId:n?.id,cron:n?.cron??this.config.cron,timestamp:t.toISOString()}}}_listenStatic(){this._scheduleNextStatic(this._generation)}_scheduleNextStatic(e){if(this._stopped||e!==this._generation)return;let n=this._nextRunFromCron(this.config.cron),t=n.getTime()-Date.now();if(t<=0){this.timer=setTimeout(()=>this._scheduleNextStatic(e),0);return}console.log(`[ScheduledChannel:${this.name??"default"}] Next run: ${n.toISOString()}`),this.timer=setTimeout(async()=>{this._stopped||e!==this._generation||(await this._triggerStatic(),this._scheduleNextStatic(e))},t)}async _triggerStatic(){if(!this._handler){console.warn(`[ScheduledChannel:${this.name??"default"}] Cron fired but no message handler is registered. Call onMessage() before listen() to avoid silently losing triggers.`);return}let e=this.normalize(null);try{await this.handleMessage(e)}catch(n){console.error(`[ScheduledChannel:${this.name??"default"}] Error on trigger:`,n)}}_listenWithStore(){let e=this.config.store,n=this._generation;if(n===1){let r=e.resetStuck(this.name);r>0&&console.log(`[ScheduledChannel:${this.name??"default"}] Reset ${r} stuck 'running' job(s) to 'pending'.`)}if(this.config.cron){let{duplicate:r}=e.create({channelName:this.name,cron:this.config.cron,intent:this.config.intent,message:this.config.message});r||console.log(`[ScheduledChannel:${this.name??"default"}] Seeded static cron '${this.config.cron}' into store.`)}let t=e.getDue(Date.now(),this.name);t.length>0&&(console.log(`[ScheduledChannel:${this.name??"default"}] Recovering ${t.length} overdue job(s).`),Promise.allSettled(t.map(r=>this._triggerJob(r)))),this._scheduleNextFromStore(n)}_scheduleNextFromStore(e){if(this._stopped||e!==this._generation)return;let n=this.config.store,t=n.getNextPending(this.name);if(!t){let i=this.config.idlePollMs??3e4;this.timer=setTimeout(()=>this._scheduleNextFromStore(e),i);return}let r=Math.max(0,t.nextRunAt-Date.now());console.log(`[ScheduledChannel:${this.name??"default"}] Next store job at ${new Date(t.nextRunAt).toISOString()} (in ${Math.round(r/1e3)}s)`),this.timer=setTimeout(async()=>{if(this._stopped||e!==this._generation)return;let i=n.getDue(Date.now(),this.name);await Promise.allSettled(i.map(s=>this._triggerJob(s))),this._scheduleNextFromStore(e)},r)}async _triggerJob(e){let n=this.config.store;if(!this._handler){console.warn(`[ScheduledChannel:${this.name??"default"}] Job ${e.id} fired but no message handler is registered. Call onMessage() before listen() to avoid silently losing jobs.`),n.markFailed(e.id,"No message handler registered");return}n.markRunning(e.id);let t=this.normalize(e);try{await this.handleMessage(t),n.markCompleted(e.id)}catch(r){let i=r instanceof Error?r.message:String(r);console.error(`[ScheduledChannel:${this.name??"default"}] Job ${e.id} failed:`,r),n.markFailed(e.id,i)}}_nextRunFromCron(e){return x.CronExpressionParser.parse(e,{currentDate:new Date}).next().toDate()}};var b=class extends d{isTriggerChannel=!1;config;offset=0;pollingInterval;server;botUserId;botUsername;constructor(e){super(),this.name=e.name,this.config=e}listen(){this.runStartupCheck().catch(()=>{}),this.config.webhookUrl?this.startWebhook():this.startPolling()}async runStartupCheck(){try{let n=await(await fetch(`https://api.telegram.org/bot${this.config.token}/getMe`)).json();if(n.ok&&n.result){let t=n.result;this.botUserId=t.id!=null?String(t.id):void 0,this.botUsername=t.username,console.log(`[TelegramChannel] Connected as @${t.username} (id: ${t.id}, name: ${t.first_name})`)}else console.warn(`[TelegramChannel] getMe failed: ${n.description??"unknown error"}. Check your bot token.`)}catch(e){console.warn("[TelegramChannel] Startup self-check failed (network error):",e)}}async send(e){let n=e.metadata?.chatId;if(!n)throw new Error("Telegram send requires chatId in metadata");let t=await fetch(`https://api.telegram.org/bot${this.config.token}/sendMessage`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:n,text:e.output,parse_mode:"Markdown"})});if(!t.ok)throw new Error(`Failed to send Telegram message: ${t.statusText}`);let r=await t.json();if(!r.ok)throw new Error(`Telegram API error: ${r.description}`)}normalize(e){let n=e,t=n.message||n.edited_message||{},r=t.text||"",i=t.chat||{},s=t.from||{},o=s.id!=null?String(s.id):void 0,l=s.first_name||s.username||o,c=o?{kind:"user",id:o,displayName:l??o}:void 0,f=t.entities??[],u=[];for(let w of f)if(w.type==="text_mention"&&w.user){let $=w.user;$.id!=null&&u.push(String($.id))}let m=i.type,g=i.id!=null?String(i.id):"";return{message:r,conversationId:g,data:n,participant:c,context:{chatId:i.id,userId:s.id,username:s.username,firstName:s.first_name,lastName:s.last_name,messageId:t.message_id,channelType:m,channelId:g,channelName:i.title,mentions:u.length>0?u:void 0}}}startPolling(){console.log("[TelegramChannel] Starting polling mode"),this.pollingInterval=setInterval(async()=>{try{await this.pollUpdates()}catch(e){console.error("[TelegramChannel] Polling error:",e)}},5e3)}async pollUpdates(){let e=`https://api.telegram.org/bot${this.config.token}/getUpdates?offset=${this.offset}&limit=100`,n=await fetch(e);if(!n.ok)throw new Error(`Telegram getUpdates failed: ${n.statusText}`);let t=await n.json();if(!t.ok)throw new Error("Telegram getUpdates returned not ok");for(let r of t.result){let i=r.update_id;i>=this.offset&&(this.offset=i+1);try{let s=this.normalize(r);await this.handleMessage(s)}catch(s){console.error("[TelegramChannel] Error processing update:",s)}}}startWebhook(){typeof process>"u"||(console.log("[TelegramChannel] Starting webhook mode"),import("http").then(e=>{this.server=e.createServer((r,i)=>{this.handleWebhookRequest(r,i)});let n=new URL(this.config.webhookUrl||"http://localhost:3000"),t=parseInt(n.port,10)||3e3;this.server.listen(t,()=>{console.log(`[TelegramChannel] Webhook server listening on port ${t}`)}),this.setWebhook()}).catch(e=>{console.error("[TelegramChannel] Failed to start webhook server:",e)}))}async setWebhook(){if(!this.config.webhookUrl)return;let e=await fetch(`https://api.telegram.org/bot${this.config.token}/setWebhook`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:this.config.webhookUrl})});if(!e.ok){console.error("[TelegramChannel] Failed to set webhook");return}let n=await e.json();n.ok?console.log("[TelegramChannel] Webhook set successfully"):console.error("[TelegramChannel] Failed to set webhook:",n.description)}handleWebhookRequest(e,n){if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",r=>{t+=r.toString()}),e.on("end",()=>{try{let r=JSON.parse(t);this.handleMessage(this.normalize(r)).catch(i=>{console.error("[TelegramChannel] Error processing webhook:",i)}),n.writeHead(200),n.end("OK")}catch(r){console.error("[TelegramChannel] Error parsing webhook:",r),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.pollingInterval&&(clearInterval(this.pollingInterval),this.pollingInterval=void 0),this.server)return new Promise(e=>{this.server.close(e)});if(this.config.webhookUrl)try{await fetch(`https://api.telegram.org/bot${this.config.token}/deleteWebhook`,{method:"POST"})}catch(e){console.error("[TelegramChannel] Failed to delete webhook:",e)}}};var B=new Set([1,3]),A=/<@!?(\d+)>/g,F="https://discord.com/api/v10",I=class extends d{isTriggerChannel=!1;config;allowedChannelIds;botUserId;participantCache=new Map;client;constructor(e){super(),this.config=e,this.name=e.name,e.channelId==null?this.allowedChannelIds=new Set:Array.isArray(e.channelId)?this.allowedChannelIds=new Set(e.channelId):this.allowedChannelIds=new Set([e.channelId])}shouldProcessEvent(e){return!e.author||e.webhookId||this.botUserId&&e.author.id===this.botUserId||this.config.guildId&&e.guildId!==this.config.guildId||this.allowedChannelIds.size>0&&(!e.channelId||!this.allowedChannelIds.has(e.channelId))?!1:e.author.bot||e.author.system?this.config.blockedBotIds?.includes(e.author.id)?!1:this.config.allowedBotIds?this.config.allowedBotIds.includes(e.author.id):!1:!0}normalize(e){let n=e,t=n.channelId,r=(t??"")+(n.thread?.id?`:${n.thread.id}`:""),i=n.channel?.type,s=i!==void 0&&B.has(i),o=n.author?.id,l=n.author?.globalName??n.author?.username,c=[];if(n.content){A.lastIndex=0;let f;for(;(f=A.exec(n.content))!==null;)c.push(f[1])}return{message:n.content,conversationId:r,data:n,participant:o?{kind:"user",id:o,displayName:l??void 0}:void 0,context:{userId:o,username:n.author?.username,channelType:s?"dm":"channel",channelId:t,channelName:n.channel?.name,guildId:n.guildId,threadId:n.thread?.id,messageId:n.id,mentions:c.length>0?c:void 0,isMentioned:this.botUserId?c.includes(this.botUserId):void 0}}}async resolveParticipant(e){let n=e.participant?.id??e.context?.userId;if(!n)return;let t=this.participantCache.get(n);if(t)return t;try{let r=await fetch(`${F}/users/${n}`,{headers:{Authorization:`Bot ${this.config.token}`}});if(!r.ok)return console.warn(`[DiscordChannel] Failed to resolve user ${n}: HTTP ${r.status}`),{kind:"user",id:n};let i=await r.json(),s=i.global_name??i.username,o={kind:"user",id:i.id,displayName:s};return this.participantCache.set(n,o),o}catch(r){return console.warn(`[DiscordChannel] resolveParticipant error for ${n}:`,r),{kind:"user",id:n}}}invalidateParticipant(e){this.participantCache.delete(e)}async send(e){if(!this.client)throw new Error("[DiscordChannel] Client not initialized. Did you call listen()?");let n=e.metadata?.threadId;if(n){let i=await this.client.channels.fetch(n);if(i&&"send"in i)try{await i.send({content:e.output});return}catch(s){throw console.error("[DiscordChannel] Failed to send to thread:",s),new Error(`[DiscordChannel] Failed to send Discord message: ${s instanceof Error?s.message:String(s)}`)}console.warn(`[DiscordChannel] Thread ${n} not found or not sendable; falling back to channel send.`)}let t=e.metadata?.channelId??(this.allowedChannelIds.size>0?[...this.allowedChannelIds][0]:void 0);if(!t)throw new Error("[DiscordChannel] No channel ID available for sending. Configure channelId or pass output.metadata.channelId.");let r=await this.client.channels.fetch(t);if(!r||!("send"in r))throw new Error(`[DiscordChannel] Channel ${t} not found or is not a text channel`);try{await r.send({content:e.output})}catch(i){throw console.error("[DiscordChannel] Failed to send to channel:",i),new Error(`[DiscordChannel] Failed to send Discord message: ${i instanceof Error?i.message:String(i)}`)}}listen(){typeof process>"u"||import("discord.js").then(e=>{let{Client:n,GatewayIntentBits:t}=e;this.client=new n({intents:[t.Guilds,t.GuildMessages,t.MessageContent,t.DirectMessages]}),this.client.on("ready",()=>{let r=this.client.user;this.botUserId=r?.id;let i=this.allowedChannelIds.size===0?"all channels":[...this.allowedChannelIds].join(", "),s=this.config.guildId??"all guilds";console.log(`[DiscordChannel] Logged in as ${r?.tag??"unknown"} (botUserId=${this.botUserId??"unknown"}) | guild=${s} | channels=${i}`)}),this.client.on("messageCreate",r=>{let i=r;if(!this.shouldProcessEvent(i))return;let s=this.normalize(i);this.handleMessage(s)}),this.client.on("userUpdate",(r,i)=>{let s=i;s?.id&&this.invalidateParticipant(s.id)}),this.client.login(this.config.token).catch(r=>{console.error("[DiscordChannel] Failed to login to Discord:",r)})}).catch(e=>{console.error("[DiscordChannel] Failed to initialize Discord client:",e),console.error("[DiscordChannel] Make sure discord.js is installed: npm install discord.js")})}async stop(){this.client&&(await this.client.destroy(),this.client=void 0)}};var T=class extends d{isTriggerChannel=!0;config;transporter;constructor(e){super(),this.config=e,this.name=e.name}listen(){typeof process<"u"&&import("nodemailer").then(e=>{this.transporter=e.default.createTransport({host:this.config.smtp.host,port:this.config.smtp.port,secure:this.config.smtp.secure??this.config.smtp.port===465,auth:{user:this.config.smtp.auth.user,pass:this.config.smtp.auth.pass}}),console.log(`[EmailChannel] Email transporter initialized for ${this.config.from}`)}).catch(e=>{console.error("[EmailChannel] Failed to initialize nodemailer:",e),console.error("[EmailChannel] Make sure to install nodemailer: npm install nodemailer")})}async send(e){if(!this.transporter)throw new Error("Email transporter not initialized. Did you call listen()?");let n=Array.isArray(this.config.to)?this.config.to:[this.config.to],t=this.config.subject||"Message from Agent",r={from:this.config.from,to:n.join(", "),subject:t,text:e.output,html:this.formatAsHtml(e.output)};try{await this.transporter.sendMail(r)}catch(i){throw console.error("[EmailChannel] Failed to send email:",i),new Error(`Failed to send email: ${i instanceof Error?i.message:String(i)}`)}}normalize(e){throw new Error("EmailChannel is outbound-only. Use WebhookChannel with email webhook events for inbound email.")}formatAsHtml(e){return e.split(`
|
|
1
|
+
"use strict";var D=Object.create;var C=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var O=Object.getPrototypeOf,N=Object.prototype.hasOwnProperty;var U=(a,e)=>{for(var n in e)C(a,n,{get:e[n],enumerable:!0})},A=(a,e,n,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of R(e))!N.call(a,i)&&i!==n&&C(a,i,{get:()=>e[i],enumerable:!(t=E(e,i))||t.enumerable});return a};var p=(a,e,n)=>(n=a!=null?D(O(a)):{},A(e||!a||!a.__esModule?C(n,"default",{value:a,enumerable:!0}):n,a)),B=a=>A(C({},"__esModule",{value:!0}),a);var j={};U(j,{BaseChannel:()=>d,DiscordChannel:()=>I,EmailChannel:()=>T,McpChannel:()=>x,SMSChannel:()=>P,ScheduledChannel:()=>S,SlackChannel:()=>v,TelegramChannel:()=>b,WebhookChannel:()=>k});module.exports=B(j);var d=class{name;_handler;onMessage(e){this._handler=e}async handleMessage(e){this._handler&&await this._handler(e)}};var y=require("crypto");var v=class extends d{isTriggerChannel=!1;config;server;participantCache=new Map;botUserId;allowedChannels;constructor(e){super(),this.config={port:3e3,...e},this.name=e.name;let n=e.channel;this.allowedChannels=n==null?null:Array.isArray(n)?n:[n]}listen(){typeof process<"u"&&import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[SlackChannel] Listening on port ${this.config.port}`),this.runStartupCheck().catch(()=>{})})}).catch(e=>{console.error("[SlackChannel] Failed to start HTTP server:",e)})}async runStartupCheck(){try{let n=await(await fetch("https://slack.com/api/auth.test",{method:"POST",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"}})).json();n.ok?(this.botUserId=n.user_id,console.log(`[SlackChannel] Connected as @${n.user} (${n.user_id}) in workspace "${n.team}" \u2014 ${n.url}`)):console.warn(`[SlackChannel] auth.test failed: ${n.error}. Check your bot token.`)}catch(e){console.warn("[SlackChannel] Startup self-check failed (network error):",e)}}verifySignature(e,n){let t=e["x-slack-request-timestamp"],i=e["x-slack-signature"];if(!t||!i||Array.isArray(t)||Array.isArray(i))return!1;let r=parseInt(t,10),s=Math.floor(Date.now()/1e3);if(isNaN(r)||Math.abs(s-r)>300)return!1;let o=`v0:${t}:${n}`,c=`v0=${(0,y.createHmac)("sha256",this.config.signingSecret).update(o).digest("hex")}`;if(c.length!==i.length)return!1;try{return(0,y.timingSafeEqual)(Buffer.from(c),Buffer.from(i))}catch{return!1}}async send(e){let n=e.metadata?.threadTs??e.metadata?.thread_ts??e.metadata?.threadId,i=e.metadata?.channelId??(this.allowedChannels&&this.allowedChannels.length>0?this.allowedChannels[0]:void 0);if(!i)throw new Error("[SlackChannel] Cannot send: no channel configured and metadata.channelId is missing. Provide a target via SlackChannelConfig.channel or output.metadata.channelId.");let r={channel:i,text:e.output};n&&(r.thread_ts=n);let s=await fetch("https://slack.com/api/chat.postMessage",{method:"POST",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"},body:JSON.stringify(r)});if(!s.ok)throw new Error(`Failed to send Slack message: ${s.statusText}`);let o=await s.json();if(!o.ok)throw new Error(`Slack API error: ${o.error}`)}normalize(e){let n=e,i=(n.text||"").replace(/<(https?:\/\/[^|>]+)\|([^>]+)>/g,"$2 ($1)").replace(/<(https?:\/\/[^>]+)>/g,"$1").replace(/<!here>/g,"@here").replace(/<!channel>/g,"@channel").replace(/<!everyone>/g,"@everyone"),r=n.ts,s=n.thread_ts,o=s!==void 0&&s!==r,l=n.user,c=l?{kind:"user",id:l}:void 0,m=/<@([A-Z0-9]+)>/g,g=[],f;for(;(f=m.exec(i))!==null;)g.push(f[1]);let u=n.channel;return{message:i,conversationId:o?s:u||r||"",data:n,participant:c,context:{user:l,channel:u,team:n.team,channelType:n.channel_type,threadId:o?s:void 0,mentions:g.length>0?g:void 0,channelId:u,channelName:typeof this.config.channel=="string"?this.config.channel:u}}}async resolveParticipant(e){let n=e.participant?.id??e.context?.user;if(!n)return;let t=this.participantCache.get(n);if(t)return t;try{let i=await fetch(`https://slack.com/api/users.info?user=${encodeURIComponent(n)}`,{method:"GET",headers:{Authorization:`Bearer ${this.config.token}`}});if(!i.ok)return{kind:"user",id:n};let r=await i.json();if(!r.ok||!r.user){let l={kind:"user",id:n};return this.participantCache.set(n,l),l}let s=r.user.profile?.display_name||r.user.profile?.real_name||r.user.real_name||r.user.name||n,o={kind:"user",id:n,displayName:s,metadata:{slackUser:r.user}};return this.participantCache.set(n,o),o}catch{return{kind:"user",id:n}}}invalidateParticipant(e){this.participantCache.delete(e)}shouldProcessEvent(e){let n=e.type;if(n!=="message"&&n!=="app_mention")return!1;if(this.allowedChannels!==null){let s=e.channel_type;if(!(s==="im"||s==="mpim")){let l=e.channel;if(!l||!this.allowedChannels.includes(l))return!1}}let t=e.user;if(this.botUserId&&t===this.botUserId)return!1;let i=e.bot_id;if(!i)return!0;let r=this.config.blockedBotIds??[];if(r.includes(i)||t!==void 0&&r.includes(t))return!1;if(this.config.allowedBotIds!==void 0){let s=this.config.allowedBotIds;return s.includes(i)||t!==void 0&&s.includes(t)}return!0}handleRequest(e,n){if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",i=>{t+=i.toString()}),e.on("end",()=>{if(!this.verifySignature(e.headers,t)){n.writeHead(401),n.end("Invalid signature");return}try{let i=JSON.parse(t);if(i.type==="url_verification"){n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({challenge:i.challenge}));return}if(i.type==="event_callback"&&i.event){let r=i.event;if(this.shouldProcessEvent(r)){let s=this.normalize(r);this.handleMessage(s)}else r.type==="user_change"&&r.user&&this.invalidateParticipant(r.user.id);n.writeHead(200),n.end("OK");return}n.writeHead(200),n.end("OK")}catch(i){console.error("[SlackChannel] Error handling request:",i),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};var k=class extends d{isTriggerChannel=!1;config;server;pendingResponses=new Map;constructor(e){super(),this.name=e.name,this.config={port:e.port??3e3,path:e.path??"/webhook"}}listen(){typeof process<"u"&&import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[WebhookChannel] Listening on port ${this.config.port}${this.config.path}`)})}).catch(e=>{console.error("[WebhookChannel] Failed to start HTTP server:",e)})}async send(e){let n=e.metadata?.conversationId;if(n&&this.pendingResponses.has(n)){let t=this.pendingResponses.get(n);this.pendingResponses.delete(n),t.resolve({output:e.output,metadata:e.metadata})}}normalize(e){let n=e,t=n.headers||{},i=t["x-session-id"]||t["X-Session-Id"]||n.sessionId||n.conversationId||this.generateSessionId();return{message:n.message||n.text||"",intent:n.intent,conversationId:i,data:n,context:{headers:n.headers,method:n.method,sessionId:i}}}handleRequest(e,n){if(e.url!==this.config.path){n.writeHead(404),n.end("Not found");return}if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",i=>{t+=i.toString()}),e.on("end",()=>{try{let i=JSON.parse(t),r=this.normalize(i),s=r.conversationId||this.generateSessionId(),o=new Promise((l,c)=>{this.pendingResponses.set(s,{resolve:l,reject:c}),setTimeout(()=>{this.pendingResponses.has(s)&&(this.pendingResponses.delete(s),c(new Error("Agent response timeout")))},3e4)});this.handleMessage({...r,conversationId:s,context:{...r.context,sessionId:s}}),o.then(l=>{n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify(l))}).catch(l=>{n.writeHead(500,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:l.message}))})}catch(i){console.error("[WebhookChannel] Error handling request:",i),n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Bad request"}))}})}generateSessionId(){return`webhook-${Date.now()}-${Math.random().toString(36).substring(2,9)}`}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};var _=require("cron-parser"),S=class extends d{isTriggerChannel=!0;config;timer;_stopped=!1;_generation=0;constructor(e){if(super(),!e.cron&&!e.store)throw new Error("ScheduledChannel: provide at least one of `cron` (static schedule) or `store` (dynamic scheduling).");if(e.cron)try{_.CronExpressionParser.parse(e.cron)}catch(n){throw new Error(`ScheduledChannel: invalid cron expression '${e.cron}': ${n.message}`)}if(e.store&&!e.name&&console.warn("[ScheduledChannel] A `store` was provided without a `name`. All store queries will be unscoped and will pick up jobs from every channel. Set `name` to scope this channel to its own jobs."),e.idlePollMs!==void 0&&e.idlePollMs<1e3)throw new Error(`ScheduledChannel: idlePollMs must be at least 1000ms (got ${e.idlePollMs}). Values below 1 second create a tight polling loop.`);this.config=e,this.name=e.name}listen(){this._generation++,this.timer&&(clearTimeout(this.timer),this.timer=void 0),this._stopped=!1,this.config.store?this._listenWithStore():this._listenStatic()}async stop(){this._stopped=!0,this.timer&&(clearTimeout(this.timer),this.timer=void 0)}async send(e){}normalize(e){let n=e,t=new Date,i=`${t.getFullYear()}-${t.getMonth()+1}-${t.getDate()}`;return{intent:n?.intent??this.config.intent,message:n?.message??this.config.message??`Scheduled task triggered at ${t.toISOString()}`,conversationId:`scheduled:${this.name??"default"}:${i}`,data:{...n?.payload??{},scheduled:!0,jobId:n?.id,cron:n?.cron??this.config.cron,timestamp:t.toISOString()}}}_listenStatic(){this._scheduleNextStatic(this._generation)}_scheduleNextStatic(e){if(this._stopped||e!==this._generation)return;let n=this._nextRunFromCron(this.config.cron),t=n.getTime()-Date.now();if(t<=0){this.timer=setTimeout(()=>this._scheduleNextStatic(e),0);return}console.log(`[ScheduledChannel:${this.name??"default"}] Next run: ${n.toISOString()}`),this.timer=setTimeout(async()=>{this._stopped||e!==this._generation||(await this._triggerStatic(),this._scheduleNextStatic(e))},t)}async _triggerStatic(){if(!this._handler){console.warn(`[ScheduledChannel:${this.name??"default"}] Cron fired but no message handler is registered. Call onMessage() before listen() to avoid silently losing triggers.`);return}let e=this.normalize(null);try{await this.handleMessage(e)}catch(n){console.error(`[ScheduledChannel:${this.name??"default"}] Error on trigger:`,n)}}_listenWithStore(){let e=this.config.store,n=this._generation;if(n===1){let i=e.resetStuck(this.name);i>0&&console.log(`[ScheduledChannel:${this.name??"default"}] Reset ${i} stuck 'running' job(s) to 'pending'.`)}if(this.config.cron){let{duplicate:i}=e.create({channelName:this.name,cron:this.config.cron,intent:this.config.intent,message:this.config.message});i||console.log(`[ScheduledChannel:${this.name??"default"}] Seeded static cron '${this.config.cron}' into store.`)}let t=e.getDue(Date.now(),this.name);t.length>0&&(console.log(`[ScheduledChannel:${this.name??"default"}] Recovering ${t.length} overdue job(s).`),Promise.allSettled(t.map(i=>this._triggerJob(i)))),this._scheduleNextFromStore(n)}_scheduleNextFromStore(e){if(this._stopped||e!==this._generation)return;let n=this.config.store,t=n.getNextPending(this.name);if(!t){let r=this.config.idlePollMs??3e4;this.timer=setTimeout(()=>this._scheduleNextFromStore(e),r);return}let i=Math.max(0,t.nextRunAt-Date.now());console.log(`[ScheduledChannel:${this.name??"default"}] Next store job at ${new Date(t.nextRunAt).toISOString()} (in ${Math.round(i/1e3)}s)`),this.timer=setTimeout(async()=>{if(this._stopped||e!==this._generation)return;let r=n.getDue(Date.now(),this.name);await Promise.allSettled(r.map(s=>this._triggerJob(s))),this._scheduleNextFromStore(e)},i)}async _triggerJob(e){let n=this.config.store;if(!this._handler){console.warn(`[ScheduledChannel:${this.name??"default"}] Job ${e.id} fired but no message handler is registered. Call onMessage() before listen() to avoid silently losing jobs.`),n.markFailed(e.id,"No message handler registered");return}n.markRunning(e.id);let t=this.normalize(e);try{await this.handleMessage(t),n.markCompleted(e.id)}catch(i){let r=i instanceof Error?i.message:String(i);console.error(`[ScheduledChannel:${this.name??"default"}] Job ${e.id} failed:`,i),n.markFailed(e.id,r)}}_nextRunFromCron(e){return _.CronExpressionParser.parse(e,{currentDate:new Date}).next().toDate()}};var b=class extends d{isTriggerChannel=!1;config;offset=0;pollingInterval;server;botUserId;botUsername;constructor(e){super(),this.name=e.name,this.config=e}listen(){this.runStartupCheck().catch(()=>{}),this.config.webhookUrl?this.startWebhook():this.startPolling()}async runStartupCheck(){try{let n=await(await fetch(`https://api.telegram.org/bot${this.config.token}/getMe`)).json();if(n.ok&&n.result){let t=n.result;this.botUserId=t.id!=null?String(t.id):void 0,this.botUsername=t.username,console.log(`[TelegramChannel] Connected as @${t.username} (id: ${t.id}, name: ${t.first_name})`)}else console.warn(`[TelegramChannel] getMe failed: ${n.description??"unknown error"}. Check your bot token.`)}catch(e){console.warn("[TelegramChannel] Startup self-check failed (network error):",e)}}async send(e){let n=e.metadata?.chatId;if(!n)throw new Error("Telegram send requires chatId in metadata");let t=await fetch(`https://api.telegram.org/bot${this.config.token}/sendMessage`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:n,text:e.output,parse_mode:"Markdown"})});if(!t.ok)throw new Error(`Failed to send Telegram message: ${t.statusText}`);let i=await t.json();if(!i.ok)throw new Error(`Telegram API error: ${i.description}`)}normalize(e){let n=e,t=n.message||n.edited_message||{},i=t.text||"",r=t.chat||{},s=t.from||{},o=s.id!=null?String(s.id):void 0,l=s.first_name||s.username||o,c=o?{kind:"user",id:o,displayName:l??o}:void 0,m=t.entities??[],g=[];for(let w of m)if(w.type==="text_mention"&&w.user){let $=w.user;$.id!=null&&g.push(String($.id))}let f=r.type,u=r.id!=null?String(r.id):"";return{message:i,conversationId:u,data:n,participant:c,context:{chatId:r.id,userId:s.id,username:s.username,firstName:s.first_name,lastName:s.last_name,messageId:t.message_id,channelType:f,channelId:u,channelName:r.title,mentions:g.length>0?g:void 0}}}startPolling(){console.log("[TelegramChannel] Starting polling mode"),this.pollingInterval=setInterval(async()=>{try{await this.pollUpdates()}catch(e){console.error("[TelegramChannel] Polling error:",e)}},5e3)}async pollUpdates(){let e=`https://api.telegram.org/bot${this.config.token}/getUpdates?offset=${this.offset}&limit=100`,n=await fetch(e);if(!n.ok)throw new Error(`Telegram getUpdates failed: ${n.statusText}`);let t=await n.json();if(!t.ok)throw new Error("Telegram getUpdates returned not ok");for(let i of t.result){let r=i.update_id;r>=this.offset&&(this.offset=r+1);try{let s=this.normalize(i);await this.handleMessage(s)}catch(s){console.error("[TelegramChannel] Error processing update:",s)}}}startWebhook(){typeof process>"u"||(console.log("[TelegramChannel] Starting webhook mode"),import("http").then(e=>{this.server=e.createServer((i,r)=>{this.handleWebhookRequest(i,r)});let n=new URL(this.config.webhookUrl||"http://localhost:3000"),t=parseInt(n.port,10)||3e3;this.server.listen(t,()=>{console.log(`[TelegramChannel] Webhook server listening on port ${t}`)}),this.setWebhook()}).catch(e=>{console.error("[TelegramChannel] Failed to start webhook server:",e)}))}async setWebhook(){if(!this.config.webhookUrl)return;let e=await fetch(`https://api.telegram.org/bot${this.config.token}/setWebhook`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:this.config.webhookUrl})});if(!e.ok){console.error("[TelegramChannel] Failed to set webhook");return}let n=await e.json();n.ok?console.log("[TelegramChannel] Webhook set successfully"):console.error("[TelegramChannel] Failed to set webhook:",n.description)}handleWebhookRequest(e,n){if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",i=>{t+=i.toString()}),e.on("end",()=>{try{let i=JSON.parse(t);this.handleMessage(this.normalize(i)).catch(r=>{console.error("[TelegramChannel] Error processing webhook:",r)}),n.writeHead(200),n.end("OK")}catch(i){console.error("[TelegramChannel] Error parsing webhook:",i),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.pollingInterval&&(clearInterval(this.pollingInterval),this.pollingInterval=void 0),this.server)return new Promise(e=>{this.server.close(e)});if(this.config.webhookUrl)try{await fetch(`https://api.telegram.org/bot${this.config.token}/deleteWebhook`,{method:"POST"})}catch(e){console.error("[TelegramChannel] Failed to delete webhook:",e)}}};var z=new Set([1,3]),M=/<@!?(\d+)>/g,F="https://discord.com/api/v10",I=class extends d{isTriggerChannel=!1;config;allowedChannelIds;botUserId;participantCache=new Map;client;constructor(e){super(),this.config=e,this.name=e.name,e.channelId==null?this.allowedChannelIds=new Set:Array.isArray(e.channelId)?this.allowedChannelIds=new Set(e.channelId):this.allowedChannelIds=new Set([e.channelId])}shouldProcessEvent(e){return!e.author||e.webhookId||this.botUserId&&e.author.id===this.botUserId||this.config.guildId&&e.guildId!==this.config.guildId||this.allowedChannelIds.size>0&&(!e.channelId||!this.allowedChannelIds.has(e.channelId))?!1:e.author.bot||e.author.system?this.config.blockedBotIds?.includes(e.author.id)?!1:this.config.allowedBotIds?this.config.allowedBotIds.includes(e.author.id):!1:!0}normalize(e){let n=e,t=n.channelId,i=(t??"")+(n.thread?.id?`:${n.thread.id}`:""),r=n.channel?.type,s=r!==void 0&&z.has(r),o=n.author?.id,l=n.author?.globalName??n.author?.username,c=[];if(n.content){M.lastIndex=0;let m;for(;(m=M.exec(n.content))!==null;)c.push(m[1])}return{message:n.content,conversationId:i,data:n,participant:o?{kind:"user",id:o,displayName:l??void 0}:void 0,context:{userId:o,username:n.author?.username,channelType:s?"dm":"channel",channelId:t,channelName:n.channel?.name,guildId:n.guildId,threadId:n.thread?.id,messageId:n.id,mentions:c.length>0?c:void 0,isMentioned:this.botUserId?c.includes(this.botUserId):void 0}}}async resolveParticipant(e){let n=e.participant?.id??e.context?.userId;if(!n)return;let t=this.participantCache.get(n);if(t)return t;try{let i=await fetch(`${F}/users/${n}`,{headers:{Authorization:`Bot ${this.config.token}`}});if(!i.ok)return console.warn(`[DiscordChannel] Failed to resolve user ${n}: HTTP ${i.status}`),{kind:"user",id:n};let r=await i.json(),s=r.global_name??r.username,o={kind:"user",id:r.id,displayName:s};return this.participantCache.set(n,o),o}catch(i){return console.warn(`[DiscordChannel] resolveParticipant error for ${n}:`,i),{kind:"user",id:n}}}invalidateParticipant(e){this.participantCache.delete(e)}async send(e){if(!this.client)throw new Error("[DiscordChannel] Client not initialized. Did you call listen()?");let n=e.metadata?.threadId;if(n){let r=await this.client.channels.fetch(n);if(r&&"send"in r)try{await r.send({content:e.output});return}catch(s){throw console.error("[DiscordChannel] Failed to send to thread:",s),new Error(`[DiscordChannel] Failed to send Discord message: ${s instanceof Error?s.message:String(s)}`)}console.warn(`[DiscordChannel] Thread ${n} not found or not sendable; falling back to channel send.`)}let t=e.metadata?.channelId??(this.allowedChannelIds.size>0?[...this.allowedChannelIds][0]:void 0);if(!t)throw new Error("[DiscordChannel] No channel ID available for sending. Configure channelId or pass output.metadata.channelId.");let i=await this.client.channels.fetch(t);if(!i||!("send"in i))throw new Error(`[DiscordChannel] Channel ${t} not found or is not a text channel`);try{await i.send({content:e.output})}catch(r){throw console.error("[DiscordChannel] Failed to send to channel:",r),new Error(`[DiscordChannel] Failed to send Discord message: ${r instanceof Error?r.message:String(r)}`)}}listen(){typeof process>"u"||import("discord.js").then(e=>{let{Client:n,GatewayIntentBits:t}=e;this.client=new n({intents:[t.Guilds,t.GuildMessages,t.MessageContent,t.DirectMessages]}),this.client.on("ready",()=>{let i=this.client.user;this.botUserId=i?.id;let r=this.allowedChannelIds.size===0?"all channels":[...this.allowedChannelIds].join(", "),s=this.config.guildId??"all guilds";console.log(`[DiscordChannel] Logged in as ${i?.tag??"unknown"} (botUserId=${this.botUserId??"unknown"}) | guild=${s} | channels=${r}`)}),this.client.on("messageCreate",i=>{let r=i;if(!this.shouldProcessEvent(r))return;let s=this.normalize(r);this.handleMessage(s)}),this.client.on("userUpdate",(i,r)=>{let s=r;s?.id&&this.invalidateParticipant(s.id)}),this.client.login(this.config.token).catch(i=>{console.error("[DiscordChannel] Failed to login to Discord:",i)})}).catch(e=>{console.error("[DiscordChannel] Failed to initialize Discord client:",e),console.error("[DiscordChannel] Make sure discord.js is installed: npm install discord.js")})}async stop(){this.client&&(await this.client.destroy(),this.client=void 0)}};var T=class extends d{isTriggerChannel=!0;config;transporter;constructor(e){super(),this.config=e,this.name=e.name}listen(){typeof process<"u"&&import("nodemailer").then(e=>{this.transporter=e.default.createTransport({host:this.config.smtp.host,port:this.config.smtp.port,secure:this.config.smtp.secure??this.config.smtp.port===465,auth:{user:this.config.smtp.auth.user,pass:this.config.smtp.auth.pass}}),console.log(`[EmailChannel] Email transporter initialized for ${this.config.from}`)}).catch(e=>{console.error("[EmailChannel] Failed to initialize nodemailer:",e),console.error("[EmailChannel] Make sure to install nodemailer: npm install nodemailer")})}async send(e){if(!this.transporter)throw new Error("Email transporter not initialized. Did you call listen()?");let n=Array.isArray(this.config.to)?this.config.to:[this.config.to],t=this.config.subject||"Message from Agent",i={from:this.config.from,to:n.join(", "),subject:t,text:e.output,html:this.formatAsHtml(e.output)};try{await this.transporter.sendMail(i)}catch(r){throw console.error("[EmailChannel] Failed to send email:",r),new Error(`Failed to send email: ${r instanceof Error?r.message:String(r)}`)}}normalize(e){throw new Error("EmailChannel is outbound-only. Use WebhookChannel with email webhook events for inbound email.")}formatAsHtml(e){return e.split(`
|
|
2
2
|
|
|
3
|
-
`).map(n=>`<p>${n.replace(/\n/g,"<br>")}</p>`).join("")}async stop(){this.transporter&&(this.transporter.close(),this.transporter=void 0)}};var P=class extends d{config;twilioClient;server;constructor(e){super(),this.config={port:3e3,...e},this.name=e.name}get isTriggerChannel(){return!this.config.webhookPath}listen(){typeof process<"u"&&import("twilio").then(e=>{this.twilioClient=e.default(this.config.accountSid,this.config.authToken),console.log("[SMSChannel] Twilio client initialized"),this.config.webhookPath&&this.startWebhookServer()}).catch(e=>{console.error("[SMSChannel] Failed to initialize Twilio client:",e),console.error("[SMSChannel] Make sure to install twilio: npm install twilio")})}async send(e){if(!this.twilioClient)throw new Error("Twilio client not initialized. Did you call listen()?");let n=e.metadata?.from||this.config.to;if(!n)throw new Error('No recipient phone number specified. Set "to" in config or provide in output.metadata.from');try{await this.twilioClient.messages.create({body:e.output,from:this.config.from,to:n})}catch(t){throw console.error("[SMSChannel] Failed to send SMS:",t),new Error(`Failed to send SMS: ${t instanceof Error?t.message:String(t)}`)}}normalize(e){let n=e,t=n.From,
|
|
3
|
+
`).map(n=>`<p>${n.replace(/\n/g,"<br>")}</p>`).join("")}async stop(){this.transporter&&(this.transporter.close(),this.transporter=void 0)}};var P=class extends d{config;twilioClient;server;constructor(e){super(),this.config={port:3e3,...e},this.name=e.name}get isTriggerChannel(){return!this.config.webhookPath}listen(){typeof process<"u"&&import("twilio").then(e=>{this.twilioClient=e.default(this.config.accountSid,this.config.authToken),console.log("[SMSChannel] Twilio client initialized"),this.config.webhookPath&&this.startWebhookServer()}).catch(e=>{console.error("[SMSChannel] Failed to initialize Twilio client:",e),console.error("[SMSChannel] Make sure to install twilio: npm install twilio")})}async send(e){if(!this.twilioClient)throw new Error("Twilio client not initialized. Did you call listen()?");let n=e.metadata?.from||this.config.to;if(!n)throw new Error('No recipient phone number specified. Set "to" in config or provide in output.metadata.from');try{await this.twilioClient.messages.create({body:e.output,from:this.config.from,to:n})}catch(t){throw console.error("[SMSChannel] Failed to send SMS:",t),new Error(`Failed to send SMS: ${t instanceof Error?t.message:String(t)}`)}}normalize(e){let n=e,t=n.From,i=n.Body,r=n.MessageSid;return{message:i,conversationId:t,data:n,context:{from:t,to:n.To,messageSid:r}}}startWebhookServer(){import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleWebhookRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[SMSChannel] Webhook server listening on port ${this.config.port} at ${this.config.webhookPath}`)})}).catch(e=>{console.error("[SMSChannel] Failed to start webhook server:",e)})}handleWebhookRequest(e,n){if(e.method!=="POST"||e.url!==this.config.webhookPath){n.writeHead(404),n.end("Not found");return}let t="";e.on("data",i=>{t+=i.toString()}),e.on("end",()=>{try{let i=new URLSearchParams(t),r={};i.forEach((o,l)=>{r[l]=o});let s=this.normalize(r);this.handleMessage(s),n.writeHead(200,{"Content-Type":"text/xml"}),n.end('<?xml version="1.0" encoding="UTF-8"?><Response></Response>')}catch(i){console.error("[SMSChannel] Error handling webhook:",i),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};var x=class extends d{isTriggerChannel=!1;_timeout;_pendingResolve;constructor(e={}){super(),this._timeout=e.timeout??12e4}listen(){}async send(e){this._pendingResolve?.(e),this._pendingResolve=void 0}normalize(e){let n=e;return{message:typeof n.message=="string"?n.message:JSON.stringify(n),data:n,conversationId:`mcp-${Date.now()}-${Math.random().toString(36).slice(2,7)}`}}async trigger(e){let n=this.normalize(e);return new Promise((t,i)=>{let r=setTimeout(()=>{this._pendingResolve=void 0,i(new Error(`McpChannel: agent did not respond within ${this._timeout}ms`))},this._timeout);this._pendingResolve=s=>{clearTimeout(r),t(s.output)},this.handleMessage(n).catch(s=>{clearTimeout(r),this._pendingResolve=void 0,i(s instanceof Error?s:new Error(String(s)))})})}asAgentDefinition(e,n){return{name:e.name,description:e.description,...n!==void 0&&{inputSchema:n},invoke:t=>this.trigger(t)}}};0&&(module.exports={BaseChannel,DiscordChannel,EmailChannel,McpChannel,SMSChannel,ScheduledChannel,SlackChannel,TelegramChannel,WebhookChannel});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BaseChannel, D as DiscordChannel, b as DiscordChannelConfig, E as EmailChannel, c as EmailChannelConfig, d as
|
|
1
|
+
export { B as BaseChannel, D as DiscordChannel, b as DiscordChannelConfig, E as EmailChannel, c as EmailChannelConfig, M as McpChannel, d as McpChannelConfig, e as SMSChannel, f as SMSChannelConfig, g as ScheduledChannel, h as ScheduledChannelConfig, j as SlackChannel, k as SlackChannelConfig, T as TelegramChannel, l as TelegramChannelConfig, W as WebhookChannel, m as WebhookChannelConfig } from '../index-DVhRtkNq.cjs';
|
|
2
2
|
import '../types-TB6yypig.cjs';
|
|
3
3
|
import 'toolpack-sdk';
|
|
4
4
|
import 'events';
|
package/dist/channels/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BaseChannel, D as DiscordChannel, b as DiscordChannelConfig, E as EmailChannel, c as EmailChannelConfig, d as
|
|
1
|
+
export { B as BaseChannel, D as DiscordChannel, b as DiscordChannelConfig, E as EmailChannel, c as EmailChannelConfig, M as McpChannel, d as McpChannelConfig, e as SMSChannel, f as SMSChannelConfig, g as ScheduledChannel, h as ScheduledChannelConfig, j as SlackChannel, k as SlackChannelConfig, T as TelegramChannel, l as TelegramChannelConfig, W as WebhookChannel, m as WebhookChannelConfig } from '../index-dhKoHWTy.js';
|
|
2
2
|
import '../types-TB6yypig.js';
|
|
3
3
|
import 'toolpack-sdk';
|
|
4
4
|
import 'events';
|
package/dist/channels/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var l=class{name;_handler;onMessage(e){this._handler=e}async handleMessage(e){this._handler&&await this._handler(e)}};import{createHmac as $,timingSafeEqual as _}from"crypto";var C=class extends l{isTriggerChannel=!1;config;server;participantCache=new Map;botUserId;allowedChannels;constructor(e){super(),this.config={port:3e3,...e},this.name=e.name;let n=e.channel;this.allowedChannels=n==null?null:Array.isArray(n)?n:[n]}listen(){typeof process<"u"&&import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[SlackChannel] Listening on port ${this.config.port}`),this.runStartupCheck().catch(()=>{})})}).catch(e=>{console.error("[SlackChannel] Failed to start HTTP server:",e)})}async runStartupCheck(){try{let n=await(await fetch("https://slack.com/api/auth.test",{method:"POST",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"}})).json();n.ok?(this.botUserId=n.user_id,console.log(`[SlackChannel] Connected as @${n.user} (${n.user_id}) in workspace "${n.team}" \u2014 ${n.url}`)):console.warn(`[SlackChannel] auth.test failed: ${n.error}. Check your bot token.`)}catch(e){console.warn("[SlackChannel] Startup self-check failed (network error):",e)}}verifySignature(e,n){let t=e["x-slack-request-timestamp"],r=e["x-slack-signature"];if(!t||!r||Array.isArray(t)||Array.isArray(r))return!1;let i=parseInt(t,10),s=Math.floor(Date.now()/1e3);if(isNaN(i)||Math.abs(s-i)>300)return!1;let o=`v0:${t}:${n}`,d=`v0=${$("sha256",this.config.signingSecret).update(o).digest("hex")}`;if(d.length!==r.length)return!1;try{return _(Buffer.from(d),Buffer.from(r))}catch{return!1}}async send(e){let n=e.metadata?.threadTs??e.metadata?.thread_ts??e.metadata?.threadId,r=e.metadata?.channelId??(this.allowedChannels&&this.allowedChannels.length>0?this.allowedChannels[0]:void 0);if(!r)throw new Error("[SlackChannel] Cannot send: no channel configured and metadata.channelId is missing. Provide a target via SlackChannelConfig.channel or output.metadata.channelId.");let i={channel:r,text:e.output};n&&(i.thread_ts=n);let s=await fetch("https://slack.com/api/chat.postMessage",{method:"POST",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok)throw new Error(`Failed to send Slack message: ${s.statusText}`);let o=await s.json();if(!o.ok)throw new Error(`Slack API error: ${o.error}`)}normalize(e){let n=e,r=(n.text||"").replace(/<(https?:\/\/[^|>]+)\|([^>]+)>/g,"$2 ($1)").replace(/<(https?:\/\/[^>]+)>/g,"$1").replace(/<!here>/g,"@here").replace(/<!channel>/g,"@channel").replace(/<!everyone>/g,"@everyone"),i=n.ts,s=n.thread_ts,o=s!==void 0&&s!==i,a=n.user,d=a?{kind:"user",id:a}:void 0,f=/<@([A-Z0-9]+)>/g,g=[],m;for(;(m=f.exec(r))!==null;)g.push(m[1]);let p=n.channel;return{message:r,conversationId:o?s:p||i||"",data:n,participant:d,context:{user:a,channel:p,team:n.team,channelType:n.channel_type,threadId:o?s:void 0,mentions:g.length>0?g:void 0,channelId:p,channelName:typeof this.config.channel=="string"?this.config.channel:p}}}async resolveParticipant(e){let n=e.participant?.id??e.context?.user;if(!n)return;let t=this.participantCache.get(n);if(t)return t;try{let r=await fetch(`https://slack.com/api/users.info?user=${encodeURIComponent(n)}`,{method:"GET",headers:{Authorization:`Bearer ${this.config.token}`}});if(!r.ok)return{kind:"user",id:n};let i=await r.json();if(!i.ok||!i.user){let a={kind:"user",id:n};return this.participantCache.set(n,a),a}let s=i.user.profile?.display_name||i.user.profile?.real_name||i.user.real_name||i.user.name||n,o={kind:"user",id:n,displayName:s,metadata:{slackUser:i.user}};return this.participantCache.set(n,o),o}catch{return{kind:"user",id:n}}}invalidateParticipant(e){this.participantCache.delete(e)}shouldProcessEvent(e){let n=e.type;if(n!=="message"&&n!=="app_mention")return!1;if(this.allowedChannels!==null){let s=e.channel_type;if(!(s==="im"||s==="mpim")){let a=e.channel;if(!a||!this.allowedChannels.includes(a))return!1}}let t=e.user;if(this.botUserId&&t===this.botUserId)return!1;let r=e.bot_id;if(!r)return!0;let i=this.config.blockedBotIds??[];if(i.includes(r)||t!==void 0&&i.includes(t))return!1;if(this.config.allowedBotIds!==void 0){let s=this.config.allowedBotIds;return s.includes(r)||t!==void 0&&s.includes(t)}return!0}handleRequest(e,n){if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",r=>{t+=r.toString()}),e.on("end",()=>{if(!this.verifySignature(e.headers,t)){n.writeHead(401),n.end("Invalid signature");return}try{let r=JSON.parse(t);if(r.type==="url_verification"){n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({challenge:r.challenge}));return}if(r.type==="event_callback"&&r.event){let i=r.event;if(this.shouldProcessEvent(i)){let s=this.normalize(i);this.handleMessage(s)}else i.type==="user_change"&&i.user&&this.invalidateParticipant(i.user.id);n.writeHead(200),n.end("OK");return}n.writeHead(200),n.end("OK")}catch(r){console.error("[SlackChannel] Error handling request:",r),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};var v=class extends l{isTriggerChannel=!1;config;server;pendingResponses=new Map;constructor(e){super(),this.name=e.name,this.config={port:e.port??3e3,path:e.path??"/webhook"}}listen(){typeof process<"u"&&import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[WebhookChannel] Listening on port ${this.config.port}${this.config.path}`)})}).catch(e=>{console.error("[WebhookChannel] Failed to start HTTP server:",e)})}async send(e){let n=e.metadata?.conversationId;if(n&&this.pendingResponses.has(n)){let t=this.pendingResponses.get(n);this.pendingResponses.delete(n),t.resolve({output:e.output,metadata:e.metadata})}}normalize(e){let n=e,t=n.headers||{},r=t["x-session-id"]||t["X-Session-Id"]||n.sessionId||n.conversationId||this.generateSessionId();return{message:n.message||n.text||"",intent:n.intent,conversationId:r,data:n,context:{headers:n.headers,method:n.method,sessionId:r}}}handleRequest(e,n){if(e.url!==this.config.path){n.writeHead(404),n.end("Not found");return}if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",r=>{t+=r.toString()}),e.on("end",()=>{try{let r=JSON.parse(t),i=this.normalize(r),s=i.conversationId||this.generateSessionId(),o=new Promise((a,d)=>{this.pendingResponses.set(s,{resolve:a,reject:d}),setTimeout(()=>{this.pendingResponses.has(s)&&(this.pendingResponses.delete(s),d(new Error("Agent response timeout")))},3e4)});this.handleMessage({...i,conversationId:s,context:{...i.context,sessionId:s}}),o.then(a=>{n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify(a))}).catch(a=>{n.writeHead(500,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:a.message}))})}catch(r){console.error("[WebhookChannel] Error handling request:",r),n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Bad request"}))}})}generateSessionId(){return`webhook-${Date.now()}-${Math.random().toString(36).substring(2,9)}`}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};import{CronExpressionParser as P}from"cron-parser";var k=class extends l{isTriggerChannel=!0;config;timer;_stopped=!1;_generation=0;constructor(e){if(super(),!e.cron&&!e.store)throw new Error("ScheduledChannel: provide at least one of `cron` (static schedule) or `store` (dynamic scheduling).");if(e.cron)try{P.parse(e.cron)}catch(n){throw new Error(`ScheduledChannel: invalid cron expression '${e.cron}': ${n.message}`)}if(e.store&&!e.name&&console.warn("[ScheduledChannel] A `store` was provided without a `name`. All store queries will be unscoped and will pick up jobs from every channel. Set `name` to scope this channel to its own jobs."),e.idlePollMs!==void 0&&e.idlePollMs<1e3)throw new Error(`ScheduledChannel: idlePollMs must be at least 1000ms (got ${e.idlePollMs}). Values below 1 second create a tight polling loop.`);this.config=e,this.name=e.name}listen(){this._generation++,this.timer&&(clearTimeout(this.timer),this.timer=void 0),this._stopped=!1,this.config.store?this._listenWithStore():this._listenStatic()}async stop(){this._stopped=!0,this.timer&&(clearTimeout(this.timer),this.timer=void 0)}async send(e){}normalize(e){let n=e,t=new Date,r=`${t.getFullYear()}-${t.getMonth()+1}-${t.getDate()}`;return{intent:n?.intent??this.config.intent,message:n?.message??this.config.message??`Scheduled task triggered at ${t.toISOString()}`,conversationId:`scheduled:${this.name??"default"}:${r}`,data:{...n?.payload??{},scheduled:!0,jobId:n?.id,cron:n?.cron??this.config.cron,timestamp:t.toISOString()}}}_listenStatic(){this._scheduleNextStatic(this._generation)}_scheduleNextStatic(e){if(this._stopped||e!==this._generation)return;let n=this._nextRunFromCron(this.config.cron),t=n.getTime()-Date.now();if(t<=0){this.timer=setTimeout(()=>this._scheduleNextStatic(e),0);return}console.log(`[ScheduledChannel:${this.name??"default"}] Next run: ${n.toISOString()}`),this.timer=setTimeout(async()=>{this._stopped||e!==this._generation||(await this._triggerStatic(),this._scheduleNextStatic(e))},t)}async _triggerStatic(){if(!this._handler){console.warn(`[ScheduledChannel:${this.name??"default"}] Cron fired but no message handler is registered. Call onMessage() before listen() to avoid silently losing triggers.`);return}let e=this.normalize(null);try{await this.handleMessage(e)}catch(n){console.error(`[ScheduledChannel:${this.name??"default"}] Error on trigger:`,n)}}_listenWithStore(){let e=this.config.store,n=this._generation;if(n===1){let r=e.resetStuck(this.name);r>0&&console.log(`[ScheduledChannel:${this.name??"default"}] Reset ${r} stuck 'running' job(s) to 'pending'.`)}if(this.config.cron){let{duplicate:r}=e.create({channelName:this.name,cron:this.config.cron,intent:this.config.intent,message:this.config.message});r||console.log(`[ScheduledChannel:${this.name??"default"}] Seeded static cron '${this.config.cron}' into store.`)}let t=e.getDue(Date.now(),this.name);t.length>0&&(console.log(`[ScheduledChannel:${this.name??"default"}] Recovering ${t.length} overdue job(s).`),Promise.allSettled(t.map(r=>this._triggerJob(r)))),this._scheduleNextFromStore(n)}_scheduleNextFromStore(e){if(this._stopped||e!==this._generation)return;let n=this.config.store,t=n.getNextPending(this.name);if(!t){let i=this.config.idlePollMs??3e4;this.timer=setTimeout(()=>this._scheduleNextFromStore(e),i);return}let r=Math.max(0,t.nextRunAt-Date.now());console.log(`[ScheduledChannel:${this.name??"default"}] Next store job at ${new Date(t.nextRunAt).toISOString()} (in ${Math.round(r/1e3)}s)`),this.timer=setTimeout(async()=>{if(this._stopped||e!==this._generation)return;let i=n.getDue(Date.now(),this.name);await Promise.allSettled(i.map(s=>this._triggerJob(s))),this._scheduleNextFromStore(e)},r)}async _triggerJob(e){let n=this.config.store;if(!this._handler){console.warn(`[ScheduledChannel:${this.name??"default"}] Job ${e.id} fired but no message handler is registered. Call onMessage() before listen() to avoid silently losing jobs.`),n.markFailed(e.id,"No message handler registered");return}n.markRunning(e.id);let t=this.normalize(e);try{await this.handleMessage(t),n.markCompleted(e.id)}catch(r){let i=r instanceof Error?r.message:String(r);console.error(`[ScheduledChannel:${this.name??"default"}] Job ${e.id} failed:`,r),n.markFailed(e.id,i)}}_nextRunFromCron(e){return P.parse(e,{currentDate:new Date}).next().toDate()}};var y=class extends l{isTriggerChannel=!1;config;offset=0;pollingInterval;server;botUserId;botUsername;constructor(e){super(),this.name=e.name,this.config=e}listen(){this.runStartupCheck().catch(()=>{}),this.config.webhookUrl?this.startWebhook():this.startPolling()}async runStartupCheck(){try{let n=await(await fetch(`https://api.telegram.org/bot${this.config.token}/getMe`)).json();if(n.ok&&n.result){let t=n.result;this.botUserId=t.id!=null?String(t.id):void 0,this.botUsername=t.username,console.log(`[TelegramChannel] Connected as @${t.username} (id: ${t.id}, name: ${t.first_name})`)}else console.warn(`[TelegramChannel] getMe failed: ${n.description??"unknown error"}. Check your bot token.`)}catch(e){console.warn("[TelegramChannel] Startup self-check failed (network error):",e)}}async send(e){let n=e.metadata?.chatId;if(!n)throw new Error("Telegram send requires chatId in metadata");let t=await fetch(`https://api.telegram.org/bot${this.config.token}/sendMessage`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:n,text:e.output,parse_mode:"Markdown"})});if(!t.ok)throw new Error(`Failed to send Telegram message: ${t.statusText}`);let r=await t.json();if(!r.ok)throw new Error(`Telegram API error: ${r.description}`)}normalize(e){let n=e,t=n.message||n.edited_message||{},r=t.text||"",i=t.chat||{},s=t.from||{},o=s.id!=null?String(s.id):void 0,a=s.first_name||s.username||o,d=o?{kind:"user",id:o,displayName:a??o}:void 0,f=t.entities??[],g=[];for(let w of f)if(w.type==="text_mention"&&w.user){let T=w.user;T.id!=null&&g.push(String(T.id))}let m=i.type,p=i.id!=null?String(i.id):"";return{message:r,conversationId:p,data:n,participant:d,context:{chatId:i.id,userId:s.id,username:s.username,firstName:s.first_name,lastName:s.last_name,messageId:t.message_id,channelType:m,channelId:p,channelName:i.title,mentions:g.length>0?g:void 0}}}startPolling(){console.log("[TelegramChannel] Starting polling mode"),this.pollingInterval=setInterval(async()=>{try{await this.pollUpdates()}catch(e){console.error("[TelegramChannel] Polling error:",e)}},5e3)}async pollUpdates(){let e=`https://api.telegram.org/bot${this.config.token}/getUpdates?offset=${this.offset}&limit=100`,n=await fetch(e);if(!n.ok)throw new Error(`Telegram getUpdates failed: ${n.statusText}`);let t=await n.json();if(!t.ok)throw new Error("Telegram getUpdates returned not ok");for(let r of t.result){let i=r.update_id;i>=this.offset&&(this.offset=i+1);try{let s=this.normalize(r);await this.handleMessage(s)}catch(s){console.error("[TelegramChannel] Error processing update:",s)}}}startWebhook(){typeof process>"u"||(console.log("[TelegramChannel] Starting webhook mode"),import("http").then(e=>{this.server=e.createServer((r,i)=>{this.handleWebhookRequest(r,i)});let n=new URL(this.config.webhookUrl||"http://localhost:3000"),t=parseInt(n.port,10)||3e3;this.server.listen(t,()=>{console.log(`[TelegramChannel] Webhook server listening on port ${t}`)}),this.setWebhook()}).catch(e=>{console.error("[TelegramChannel] Failed to start webhook server:",e)}))}async setWebhook(){if(!this.config.webhookUrl)return;let e=await fetch(`https://api.telegram.org/bot${this.config.token}/setWebhook`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:this.config.webhookUrl})});if(!e.ok){console.error("[TelegramChannel] Failed to set webhook");return}let n=await e.json();n.ok?console.log("[TelegramChannel] Webhook set successfully"):console.error("[TelegramChannel] Failed to set webhook:",n.description)}handleWebhookRequest(e,n){if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",r=>{t+=r.toString()}),e.on("end",()=>{try{let r=JSON.parse(t);this.handleMessage(this.normalize(r)).catch(i=>{console.error("[TelegramChannel] Error processing webhook:",i)}),n.writeHead(200),n.end("OK")}catch(r){console.error("[TelegramChannel] Error parsing webhook:",r),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.pollingInterval&&(clearInterval(this.pollingInterval),this.pollingInterval=void 0),this.server)return new Promise(e=>{this.server.close(e)});if(this.config.webhookUrl)try{await fetch(`https://api.telegram.org/bot${this.config.token}/deleteWebhook`,{method:"POST"})}catch(e){console.error("[TelegramChannel] Failed to delete webhook:",e)}}};var A=new Set([1,3]),x=/<@!?(\d+)>/g,M="https://discord.com/api/v10",S=class extends l{isTriggerChannel=!1;config;allowedChannelIds;botUserId;participantCache=new Map;client;constructor(e){super(),this.config=e,this.name=e.name,e.channelId==null?this.allowedChannelIds=new Set:Array.isArray(e.channelId)?this.allowedChannelIds=new Set(e.channelId):this.allowedChannelIds=new Set([e.channelId])}shouldProcessEvent(e){return!e.author||e.webhookId||this.botUserId&&e.author.id===this.botUserId||this.config.guildId&&e.guildId!==this.config.guildId||this.allowedChannelIds.size>0&&(!e.channelId||!this.allowedChannelIds.has(e.channelId))?!1:e.author.bot||e.author.system?this.config.blockedBotIds?.includes(e.author.id)?!1:this.config.allowedBotIds?this.config.allowedBotIds.includes(e.author.id):!1:!0}normalize(e){let n=e,t=n.channelId,r=(t??"")+(n.thread?.id?`:${n.thread.id}`:""),i=n.channel?.type,s=i!==void 0&&A.has(i),o=n.author?.id,a=n.author?.globalName??n.author?.username,d=[];if(n.content){x.lastIndex=0;let f;for(;(f=x.exec(n.content))!==null;)d.push(f[1])}return{message:n.content,conversationId:r,data:n,participant:o?{kind:"user",id:o,displayName:a??void 0}:void 0,context:{userId:o,username:n.author?.username,channelType:s?"dm":"channel",channelId:t,channelName:n.channel?.name,guildId:n.guildId,threadId:n.thread?.id,messageId:n.id,mentions:d.length>0?d:void 0,isMentioned:this.botUserId?d.includes(this.botUserId):void 0}}}async resolveParticipant(e){let n=e.participant?.id??e.context?.userId;if(!n)return;let t=this.participantCache.get(n);if(t)return t;try{let r=await fetch(`${M}/users/${n}`,{headers:{Authorization:`Bot ${this.config.token}`}});if(!r.ok)return console.warn(`[DiscordChannel] Failed to resolve user ${n}: HTTP ${r.status}`),{kind:"user",id:n};let i=await r.json(),s=i.global_name??i.username,o={kind:"user",id:i.id,displayName:s};return this.participantCache.set(n,o),o}catch(r){return console.warn(`[DiscordChannel] resolveParticipant error for ${n}:`,r),{kind:"user",id:n}}}invalidateParticipant(e){this.participantCache.delete(e)}async send(e){if(!this.client)throw new Error("[DiscordChannel] Client not initialized. Did you call listen()?");let n=e.metadata?.threadId;if(n){let i=await this.client.channels.fetch(n);if(i&&"send"in i)try{await i.send({content:e.output});return}catch(s){throw console.error("[DiscordChannel] Failed to send to thread:",s),new Error(`[DiscordChannel] Failed to send Discord message: ${s instanceof Error?s.message:String(s)}`)}console.warn(`[DiscordChannel] Thread ${n} not found or not sendable; falling back to channel send.`)}let t=e.metadata?.channelId??(this.allowedChannelIds.size>0?[...this.allowedChannelIds][0]:void 0);if(!t)throw new Error("[DiscordChannel] No channel ID available for sending. Configure channelId or pass output.metadata.channelId.");let r=await this.client.channels.fetch(t);if(!r||!("send"in r))throw new Error(`[DiscordChannel] Channel ${t} not found or is not a text channel`);try{await r.send({content:e.output})}catch(i){throw console.error("[DiscordChannel] Failed to send to channel:",i),new Error(`[DiscordChannel] Failed to send Discord message: ${i instanceof Error?i.message:String(i)}`)}}listen(){typeof process>"u"||import("discord.js").then(e=>{let{Client:n,GatewayIntentBits:t}=e;this.client=new n({intents:[t.Guilds,t.GuildMessages,t.MessageContent,t.DirectMessages]}),this.client.on("ready",()=>{let r=this.client.user;this.botUserId=r?.id;let i=this.allowedChannelIds.size===0?"all channels":[...this.allowedChannelIds].join(", "),s=this.config.guildId??"all guilds";console.log(`[DiscordChannel] Logged in as ${r?.tag??"unknown"} (botUserId=${this.botUserId??"unknown"}) | guild=${s} | channels=${i}`)}),this.client.on("messageCreate",r=>{let i=r;if(!this.shouldProcessEvent(i))return;let s=this.normalize(i);this.handleMessage(s)}),this.client.on("userUpdate",(r,i)=>{let s=i;s?.id&&this.invalidateParticipant(s.id)}),this.client.login(this.config.token).catch(r=>{console.error("[DiscordChannel] Failed to login to Discord:",r)})}).catch(e=>{console.error("[DiscordChannel] Failed to initialize Discord client:",e),console.error("[DiscordChannel] Make sure discord.js is installed: npm install discord.js")})}async stop(){this.client&&(await this.client.destroy(),this.client=void 0)}};var b=class extends l{isTriggerChannel=!0;config;transporter;constructor(e){super(),this.config=e,this.name=e.name}listen(){typeof process<"u"&&import("nodemailer").then(e=>{this.transporter=e.default.createTransport({host:this.config.smtp.host,port:this.config.smtp.port,secure:this.config.smtp.secure??this.config.smtp.port===465,auth:{user:this.config.smtp.auth.user,pass:this.config.smtp.auth.pass}}),console.log(`[EmailChannel] Email transporter initialized for ${this.config.from}`)}).catch(e=>{console.error("[EmailChannel] Failed to initialize nodemailer:",e),console.error("[EmailChannel] Make sure to install nodemailer: npm install nodemailer")})}async send(e){if(!this.transporter)throw new Error("Email transporter not initialized. Did you call listen()?");let n=Array.isArray(this.config.to)?this.config.to:[this.config.to],t=this.config.subject||"Message from Agent",r={from:this.config.from,to:n.join(", "),subject:t,text:e.output,html:this.formatAsHtml(e.output)};try{await this.transporter.sendMail(r)}catch(i){throw console.error("[EmailChannel] Failed to send email:",i),new Error(`Failed to send email: ${i instanceof Error?i.message:String(i)}`)}}normalize(e){throw new Error("EmailChannel is outbound-only. Use WebhookChannel with email webhook events for inbound email.")}formatAsHtml(e){return e.split(`
|
|
1
|
+
var l=class{name;_handler;onMessage(e){this._handler=e}async handleMessage(e){this._handler&&await this._handler(e)}};import{createHmac as $,timingSafeEqual as A}from"crypto";var C=class extends l{isTriggerChannel=!1;config;server;participantCache=new Map;botUserId;allowedChannels;constructor(e){super(),this.config={port:3e3,...e},this.name=e.name;let n=e.channel;this.allowedChannels=n==null?null:Array.isArray(n)?n:[n]}listen(){typeof process<"u"&&import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[SlackChannel] Listening on port ${this.config.port}`),this.runStartupCheck().catch(()=>{})})}).catch(e=>{console.error("[SlackChannel] Failed to start HTTP server:",e)})}async runStartupCheck(){try{let n=await(await fetch("https://slack.com/api/auth.test",{method:"POST",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"}})).json();n.ok?(this.botUserId=n.user_id,console.log(`[SlackChannel] Connected as @${n.user} (${n.user_id}) in workspace "${n.team}" \u2014 ${n.url}`)):console.warn(`[SlackChannel] auth.test failed: ${n.error}. Check your bot token.`)}catch(e){console.warn("[SlackChannel] Startup self-check failed (network error):",e)}}verifySignature(e,n){let t=e["x-slack-request-timestamp"],i=e["x-slack-signature"];if(!t||!i||Array.isArray(t)||Array.isArray(i))return!1;let r=parseInt(t,10),s=Math.floor(Date.now()/1e3);if(isNaN(r)||Math.abs(s-r)>300)return!1;let o=`v0:${t}:${n}`,d=`v0=${$("sha256",this.config.signingSecret).update(o).digest("hex")}`;if(d.length!==i.length)return!1;try{return A(Buffer.from(d),Buffer.from(i))}catch{return!1}}async send(e){let n=e.metadata?.threadTs??e.metadata?.thread_ts??e.metadata?.threadId,i=e.metadata?.channelId??(this.allowedChannels&&this.allowedChannels.length>0?this.allowedChannels[0]:void 0);if(!i)throw new Error("[SlackChannel] Cannot send: no channel configured and metadata.channelId is missing. Provide a target via SlackChannelConfig.channel or output.metadata.channelId.");let r={channel:i,text:e.output};n&&(r.thread_ts=n);let s=await fetch("https://slack.com/api/chat.postMessage",{method:"POST",headers:{Authorization:`Bearer ${this.config.token}`,"Content-Type":"application/json"},body:JSON.stringify(r)});if(!s.ok)throw new Error(`Failed to send Slack message: ${s.statusText}`);let o=await s.json();if(!o.ok)throw new Error(`Slack API error: ${o.error}`)}normalize(e){let n=e,i=(n.text||"").replace(/<(https?:\/\/[^|>]+)\|([^>]+)>/g,"$2 ($1)").replace(/<(https?:\/\/[^>]+)>/g,"$1").replace(/<!here>/g,"@here").replace(/<!channel>/g,"@channel").replace(/<!everyone>/g,"@everyone"),r=n.ts,s=n.thread_ts,o=s!==void 0&&s!==r,a=n.user,d=a?{kind:"user",id:a}:void 0,m=/<@([A-Z0-9]+)>/g,u=[],f;for(;(f=m.exec(i))!==null;)u.push(f[1]);let p=n.channel;return{message:i,conversationId:o?s:p||r||"",data:n,participant:d,context:{user:a,channel:p,team:n.team,channelType:n.channel_type,threadId:o?s:void 0,mentions:u.length>0?u:void 0,channelId:p,channelName:typeof this.config.channel=="string"?this.config.channel:p}}}async resolveParticipant(e){let n=e.participant?.id??e.context?.user;if(!n)return;let t=this.participantCache.get(n);if(t)return t;try{let i=await fetch(`https://slack.com/api/users.info?user=${encodeURIComponent(n)}`,{method:"GET",headers:{Authorization:`Bearer ${this.config.token}`}});if(!i.ok)return{kind:"user",id:n};let r=await i.json();if(!r.ok||!r.user){let a={kind:"user",id:n};return this.participantCache.set(n,a),a}let s=r.user.profile?.display_name||r.user.profile?.real_name||r.user.real_name||r.user.name||n,o={kind:"user",id:n,displayName:s,metadata:{slackUser:r.user}};return this.participantCache.set(n,o),o}catch{return{kind:"user",id:n}}}invalidateParticipant(e){this.participantCache.delete(e)}shouldProcessEvent(e){let n=e.type;if(n!=="message"&&n!=="app_mention")return!1;if(this.allowedChannels!==null){let s=e.channel_type;if(!(s==="im"||s==="mpim")){let a=e.channel;if(!a||!this.allowedChannels.includes(a))return!1}}let t=e.user;if(this.botUserId&&t===this.botUserId)return!1;let i=e.bot_id;if(!i)return!0;let r=this.config.blockedBotIds??[];if(r.includes(i)||t!==void 0&&r.includes(t))return!1;if(this.config.allowedBotIds!==void 0){let s=this.config.allowedBotIds;return s.includes(i)||t!==void 0&&s.includes(t)}return!0}handleRequest(e,n){if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",i=>{t+=i.toString()}),e.on("end",()=>{if(!this.verifySignature(e.headers,t)){n.writeHead(401),n.end("Invalid signature");return}try{let i=JSON.parse(t);if(i.type==="url_verification"){n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({challenge:i.challenge}));return}if(i.type==="event_callback"&&i.event){let r=i.event;if(this.shouldProcessEvent(r)){let s=this.normalize(r);this.handleMessage(s)}else r.type==="user_change"&&r.user&&this.invalidateParticipant(r.user.id);n.writeHead(200),n.end("OK");return}n.writeHead(200),n.end("OK")}catch(i){console.error("[SlackChannel] Error handling request:",i),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};var v=class extends l{isTriggerChannel=!1;config;server;pendingResponses=new Map;constructor(e){super(),this.name=e.name,this.config={port:e.port??3e3,path:e.path??"/webhook"}}listen(){typeof process<"u"&&import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[WebhookChannel] Listening on port ${this.config.port}${this.config.path}`)})}).catch(e=>{console.error("[WebhookChannel] Failed to start HTTP server:",e)})}async send(e){let n=e.metadata?.conversationId;if(n&&this.pendingResponses.has(n)){let t=this.pendingResponses.get(n);this.pendingResponses.delete(n),t.resolve({output:e.output,metadata:e.metadata})}}normalize(e){let n=e,t=n.headers||{},i=t["x-session-id"]||t["X-Session-Id"]||n.sessionId||n.conversationId||this.generateSessionId();return{message:n.message||n.text||"",intent:n.intent,conversationId:i,data:n,context:{headers:n.headers,method:n.method,sessionId:i}}}handleRequest(e,n){if(e.url!==this.config.path){n.writeHead(404),n.end("Not found");return}if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",i=>{t+=i.toString()}),e.on("end",()=>{try{let i=JSON.parse(t),r=this.normalize(i),s=r.conversationId||this.generateSessionId(),o=new Promise((a,d)=>{this.pendingResponses.set(s,{resolve:a,reject:d}),setTimeout(()=>{this.pendingResponses.has(s)&&(this.pendingResponses.delete(s),d(new Error("Agent response timeout")))},3e4)});this.handleMessage({...r,conversationId:s,context:{...r.context,sessionId:s}}),o.then(a=>{n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify(a))}).catch(a=>{n.writeHead(500,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:a.message}))})}catch(i){console.error("[WebhookChannel] Error handling request:",i),n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"Bad request"}))}})}generateSessionId(){return`webhook-${Date.now()}-${Math.random().toString(36).substring(2,9)}`}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};import{CronExpressionParser as x}from"cron-parser";var y=class extends l{isTriggerChannel=!0;config;timer;_stopped=!1;_generation=0;constructor(e){if(super(),!e.cron&&!e.store)throw new Error("ScheduledChannel: provide at least one of `cron` (static schedule) or `store` (dynamic scheduling).");if(e.cron)try{x.parse(e.cron)}catch(n){throw new Error(`ScheduledChannel: invalid cron expression '${e.cron}': ${n.message}`)}if(e.store&&!e.name&&console.warn("[ScheduledChannel] A `store` was provided without a `name`. All store queries will be unscoped and will pick up jobs from every channel. Set `name` to scope this channel to its own jobs."),e.idlePollMs!==void 0&&e.idlePollMs<1e3)throw new Error(`ScheduledChannel: idlePollMs must be at least 1000ms (got ${e.idlePollMs}). Values below 1 second create a tight polling loop.`);this.config=e,this.name=e.name}listen(){this._generation++,this.timer&&(clearTimeout(this.timer),this.timer=void 0),this._stopped=!1,this.config.store?this._listenWithStore():this._listenStatic()}async stop(){this._stopped=!0,this.timer&&(clearTimeout(this.timer),this.timer=void 0)}async send(e){}normalize(e){let n=e,t=new Date,i=`${t.getFullYear()}-${t.getMonth()+1}-${t.getDate()}`;return{intent:n?.intent??this.config.intent,message:n?.message??this.config.message??`Scheduled task triggered at ${t.toISOString()}`,conversationId:`scheduled:${this.name??"default"}:${i}`,data:{...n?.payload??{},scheduled:!0,jobId:n?.id,cron:n?.cron??this.config.cron,timestamp:t.toISOString()}}}_listenStatic(){this._scheduleNextStatic(this._generation)}_scheduleNextStatic(e){if(this._stopped||e!==this._generation)return;let n=this._nextRunFromCron(this.config.cron),t=n.getTime()-Date.now();if(t<=0){this.timer=setTimeout(()=>this._scheduleNextStatic(e),0);return}console.log(`[ScheduledChannel:${this.name??"default"}] Next run: ${n.toISOString()}`),this.timer=setTimeout(async()=>{this._stopped||e!==this._generation||(await this._triggerStatic(),this._scheduleNextStatic(e))},t)}async _triggerStatic(){if(!this._handler){console.warn(`[ScheduledChannel:${this.name??"default"}] Cron fired but no message handler is registered. Call onMessage() before listen() to avoid silently losing triggers.`);return}let e=this.normalize(null);try{await this.handleMessage(e)}catch(n){console.error(`[ScheduledChannel:${this.name??"default"}] Error on trigger:`,n)}}_listenWithStore(){let e=this.config.store,n=this._generation;if(n===1){let i=e.resetStuck(this.name);i>0&&console.log(`[ScheduledChannel:${this.name??"default"}] Reset ${i} stuck 'running' job(s) to 'pending'.`)}if(this.config.cron){let{duplicate:i}=e.create({channelName:this.name,cron:this.config.cron,intent:this.config.intent,message:this.config.message});i||console.log(`[ScheduledChannel:${this.name??"default"}] Seeded static cron '${this.config.cron}' into store.`)}let t=e.getDue(Date.now(),this.name);t.length>0&&(console.log(`[ScheduledChannel:${this.name??"default"}] Recovering ${t.length} overdue job(s).`),Promise.allSettled(t.map(i=>this._triggerJob(i)))),this._scheduleNextFromStore(n)}_scheduleNextFromStore(e){if(this._stopped||e!==this._generation)return;let n=this.config.store,t=n.getNextPending(this.name);if(!t){let r=this.config.idlePollMs??3e4;this.timer=setTimeout(()=>this._scheduleNextFromStore(e),r);return}let i=Math.max(0,t.nextRunAt-Date.now());console.log(`[ScheduledChannel:${this.name??"default"}] Next store job at ${new Date(t.nextRunAt).toISOString()} (in ${Math.round(i/1e3)}s)`),this.timer=setTimeout(async()=>{if(this._stopped||e!==this._generation)return;let r=n.getDue(Date.now(),this.name);await Promise.allSettled(r.map(s=>this._triggerJob(s))),this._scheduleNextFromStore(e)},i)}async _triggerJob(e){let n=this.config.store;if(!this._handler){console.warn(`[ScheduledChannel:${this.name??"default"}] Job ${e.id} fired but no message handler is registered. Call onMessage() before listen() to avoid silently losing jobs.`),n.markFailed(e.id,"No message handler registered");return}n.markRunning(e.id);let t=this.normalize(e);try{await this.handleMessage(t),n.markCompleted(e.id)}catch(i){let r=i instanceof Error?i.message:String(i);console.error(`[ScheduledChannel:${this.name??"default"}] Job ${e.id} failed:`,i),n.markFailed(e.id,r)}}_nextRunFromCron(e){return x.parse(e,{currentDate:new Date}).next().toDate()}};var k=class extends l{isTriggerChannel=!1;config;offset=0;pollingInterval;server;botUserId;botUsername;constructor(e){super(),this.name=e.name,this.config=e}listen(){this.runStartupCheck().catch(()=>{}),this.config.webhookUrl?this.startWebhook():this.startPolling()}async runStartupCheck(){try{let n=await(await fetch(`https://api.telegram.org/bot${this.config.token}/getMe`)).json();if(n.ok&&n.result){let t=n.result;this.botUserId=t.id!=null?String(t.id):void 0,this.botUsername=t.username,console.log(`[TelegramChannel] Connected as @${t.username} (id: ${t.id}, name: ${t.first_name})`)}else console.warn(`[TelegramChannel] getMe failed: ${n.description??"unknown error"}. Check your bot token.`)}catch(e){console.warn("[TelegramChannel] Startup self-check failed (network error):",e)}}async send(e){let n=e.metadata?.chatId;if(!n)throw new Error("Telegram send requires chatId in metadata");let t=await fetch(`https://api.telegram.org/bot${this.config.token}/sendMessage`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:n,text:e.output,parse_mode:"Markdown"})});if(!t.ok)throw new Error(`Failed to send Telegram message: ${t.statusText}`);let i=await t.json();if(!i.ok)throw new Error(`Telegram API error: ${i.description}`)}normalize(e){let n=e,t=n.message||n.edited_message||{},i=t.text||"",r=t.chat||{},s=t.from||{},o=s.id!=null?String(s.id):void 0,a=s.first_name||s.username||o,d=o?{kind:"user",id:o,displayName:a??o}:void 0,m=t.entities??[],u=[];for(let w of m)if(w.type==="text_mention"&&w.user){let P=w.user;P.id!=null&&u.push(String(P.id))}let f=r.type,p=r.id!=null?String(r.id):"";return{message:i,conversationId:p,data:n,participant:d,context:{chatId:r.id,userId:s.id,username:s.username,firstName:s.first_name,lastName:s.last_name,messageId:t.message_id,channelType:f,channelId:p,channelName:r.title,mentions:u.length>0?u:void 0}}}startPolling(){console.log("[TelegramChannel] Starting polling mode"),this.pollingInterval=setInterval(async()=>{try{await this.pollUpdates()}catch(e){console.error("[TelegramChannel] Polling error:",e)}},5e3)}async pollUpdates(){let e=`https://api.telegram.org/bot${this.config.token}/getUpdates?offset=${this.offset}&limit=100`,n=await fetch(e);if(!n.ok)throw new Error(`Telegram getUpdates failed: ${n.statusText}`);let t=await n.json();if(!t.ok)throw new Error("Telegram getUpdates returned not ok");for(let i of t.result){let r=i.update_id;r>=this.offset&&(this.offset=r+1);try{let s=this.normalize(i);await this.handleMessage(s)}catch(s){console.error("[TelegramChannel] Error processing update:",s)}}}startWebhook(){typeof process>"u"||(console.log("[TelegramChannel] Starting webhook mode"),import("http").then(e=>{this.server=e.createServer((i,r)=>{this.handleWebhookRequest(i,r)});let n=new URL(this.config.webhookUrl||"http://localhost:3000"),t=parseInt(n.port,10)||3e3;this.server.listen(t,()=>{console.log(`[TelegramChannel] Webhook server listening on port ${t}`)}),this.setWebhook()}).catch(e=>{console.error("[TelegramChannel] Failed to start webhook server:",e)}))}async setWebhook(){if(!this.config.webhookUrl)return;let e=await fetch(`https://api.telegram.org/bot${this.config.token}/setWebhook`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:this.config.webhookUrl})});if(!e.ok){console.error("[TelegramChannel] Failed to set webhook");return}let n=await e.json();n.ok?console.log("[TelegramChannel] Webhook set successfully"):console.error("[TelegramChannel] Failed to set webhook:",n.description)}handleWebhookRequest(e,n){if(e.method!=="POST"){n.writeHead(405),n.end("Method not allowed");return}let t="";e.on("data",i=>{t+=i.toString()}),e.on("end",()=>{try{let i=JSON.parse(t);this.handleMessage(this.normalize(i)).catch(r=>{console.error("[TelegramChannel] Error processing webhook:",r)}),n.writeHead(200),n.end("OK")}catch(i){console.error("[TelegramChannel] Error parsing webhook:",i),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.pollingInterval&&(clearInterval(this.pollingInterval),this.pollingInterval=void 0),this.server)return new Promise(e=>{this.server.close(e)});if(this.config.webhookUrl)try{await fetch(`https://api.telegram.org/bot${this.config.token}/deleteWebhook`,{method:"POST"})}catch(e){console.error("[TelegramChannel] Failed to delete webhook:",e)}}};var M=new Set([1,3]),_=/<@!?(\d+)>/g,D="https://discord.com/api/v10",S=class extends l{isTriggerChannel=!1;config;allowedChannelIds;botUserId;participantCache=new Map;client;constructor(e){super(),this.config=e,this.name=e.name,e.channelId==null?this.allowedChannelIds=new Set:Array.isArray(e.channelId)?this.allowedChannelIds=new Set(e.channelId):this.allowedChannelIds=new Set([e.channelId])}shouldProcessEvent(e){return!e.author||e.webhookId||this.botUserId&&e.author.id===this.botUserId||this.config.guildId&&e.guildId!==this.config.guildId||this.allowedChannelIds.size>0&&(!e.channelId||!this.allowedChannelIds.has(e.channelId))?!1:e.author.bot||e.author.system?this.config.blockedBotIds?.includes(e.author.id)?!1:this.config.allowedBotIds?this.config.allowedBotIds.includes(e.author.id):!1:!0}normalize(e){let n=e,t=n.channelId,i=(t??"")+(n.thread?.id?`:${n.thread.id}`:""),r=n.channel?.type,s=r!==void 0&&M.has(r),o=n.author?.id,a=n.author?.globalName??n.author?.username,d=[];if(n.content){_.lastIndex=0;let m;for(;(m=_.exec(n.content))!==null;)d.push(m[1])}return{message:n.content,conversationId:i,data:n,participant:o?{kind:"user",id:o,displayName:a??void 0}:void 0,context:{userId:o,username:n.author?.username,channelType:s?"dm":"channel",channelId:t,channelName:n.channel?.name,guildId:n.guildId,threadId:n.thread?.id,messageId:n.id,mentions:d.length>0?d:void 0,isMentioned:this.botUserId?d.includes(this.botUserId):void 0}}}async resolveParticipant(e){let n=e.participant?.id??e.context?.userId;if(!n)return;let t=this.participantCache.get(n);if(t)return t;try{let i=await fetch(`${D}/users/${n}`,{headers:{Authorization:`Bot ${this.config.token}`}});if(!i.ok)return console.warn(`[DiscordChannel] Failed to resolve user ${n}: HTTP ${i.status}`),{kind:"user",id:n};let r=await i.json(),s=r.global_name??r.username,o={kind:"user",id:r.id,displayName:s};return this.participantCache.set(n,o),o}catch(i){return console.warn(`[DiscordChannel] resolveParticipant error for ${n}:`,i),{kind:"user",id:n}}}invalidateParticipant(e){this.participantCache.delete(e)}async send(e){if(!this.client)throw new Error("[DiscordChannel] Client not initialized. Did you call listen()?");let n=e.metadata?.threadId;if(n){let r=await this.client.channels.fetch(n);if(r&&"send"in r)try{await r.send({content:e.output});return}catch(s){throw console.error("[DiscordChannel] Failed to send to thread:",s),new Error(`[DiscordChannel] Failed to send Discord message: ${s instanceof Error?s.message:String(s)}`)}console.warn(`[DiscordChannel] Thread ${n} not found or not sendable; falling back to channel send.`)}let t=e.metadata?.channelId??(this.allowedChannelIds.size>0?[...this.allowedChannelIds][0]:void 0);if(!t)throw new Error("[DiscordChannel] No channel ID available for sending. Configure channelId or pass output.metadata.channelId.");let i=await this.client.channels.fetch(t);if(!i||!("send"in i))throw new Error(`[DiscordChannel] Channel ${t} not found or is not a text channel`);try{await i.send({content:e.output})}catch(r){throw console.error("[DiscordChannel] Failed to send to channel:",r),new Error(`[DiscordChannel] Failed to send Discord message: ${r instanceof Error?r.message:String(r)}`)}}listen(){typeof process>"u"||import("discord.js").then(e=>{let{Client:n,GatewayIntentBits:t}=e;this.client=new n({intents:[t.Guilds,t.GuildMessages,t.MessageContent,t.DirectMessages]}),this.client.on("ready",()=>{let i=this.client.user;this.botUserId=i?.id;let r=this.allowedChannelIds.size===0?"all channels":[...this.allowedChannelIds].join(", "),s=this.config.guildId??"all guilds";console.log(`[DiscordChannel] Logged in as ${i?.tag??"unknown"} (botUserId=${this.botUserId??"unknown"}) | guild=${s} | channels=${r}`)}),this.client.on("messageCreate",i=>{let r=i;if(!this.shouldProcessEvent(r))return;let s=this.normalize(r);this.handleMessage(s)}),this.client.on("userUpdate",(i,r)=>{let s=r;s?.id&&this.invalidateParticipant(s.id)}),this.client.login(this.config.token).catch(i=>{console.error("[DiscordChannel] Failed to login to Discord:",i)})}).catch(e=>{console.error("[DiscordChannel] Failed to initialize Discord client:",e),console.error("[DiscordChannel] Make sure discord.js is installed: npm install discord.js")})}async stop(){this.client&&(await this.client.destroy(),this.client=void 0)}};var b=class extends l{isTriggerChannel=!0;config;transporter;constructor(e){super(),this.config=e,this.name=e.name}listen(){typeof process<"u"&&import("nodemailer").then(e=>{this.transporter=e.default.createTransport({host:this.config.smtp.host,port:this.config.smtp.port,secure:this.config.smtp.secure??this.config.smtp.port===465,auth:{user:this.config.smtp.auth.user,pass:this.config.smtp.auth.pass}}),console.log(`[EmailChannel] Email transporter initialized for ${this.config.from}`)}).catch(e=>{console.error("[EmailChannel] Failed to initialize nodemailer:",e),console.error("[EmailChannel] Make sure to install nodemailer: npm install nodemailer")})}async send(e){if(!this.transporter)throw new Error("Email transporter not initialized. Did you call listen()?");let n=Array.isArray(this.config.to)?this.config.to:[this.config.to],t=this.config.subject||"Message from Agent",i={from:this.config.from,to:n.join(", "),subject:t,text:e.output,html:this.formatAsHtml(e.output)};try{await this.transporter.sendMail(i)}catch(r){throw console.error("[EmailChannel] Failed to send email:",r),new Error(`Failed to send email: ${r instanceof Error?r.message:String(r)}`)}}normalize(e){throw new Error("EmailChannel is outbound-only. Use WebhookChannel with email webhook events for inbound email.")}formatAsHtml(e){return e.split(`
|
|
2
2
|
|
|
3
|
-
`).map(n=>`<p>${n.replace(/\n/g,"<br>")}</p>`).join("")}async stop(){this.transporter&&(this.transporter.close(),this.transporter=void 0)}};var I=class extends l{config;twilioClient;server;constructor(e){super(),this.config={port:3e3,...e},this.name=e.name}get isTriggerChannel(){return!this.config.webhookPath}listen(){typeof process<"u"&&import("twilio").then(e=>{this.twilioClient=e.default(this.config.accountSid,this.config.authToken),console.log("[SMSChannel] Twilio client initialized"),this.config.webhookPath&&this.startWebhookServer()}).catch(e=>{console.error("[SMSChannel] Failed to initialize Twilio client:",e),console.error("[SMSChannel] Make sure to install twilio: npm install twilio")})}async send(e){if(!this.twilioClient)throw new Error("Twilio client not initialized. Did you call listen()?");let n=e.metadata?.from||this.config.to;if(!n)throw new Error('No recipient phone number specified. Set "to" in config or provide in output.metadata.from');try{await this.twilioClient.messages.create({body:e.output,from:this.config.from,to:n})}catch(t){throw console.error("[SMSChannel] Failed to send SMS:",t),new Error(`Failed to send SMS: ${t instanceof Error?t.message:String(t)}`)}}normalize(e){let n=e,t=n.From,
|
|
3
|
+
`).map(n=>`<p>${n.replace(/\n/g,"<br>")}</p>`).join("")}async stop(){this.transporter&&(this.transporter.close(),this.transporter=void 0)}};var I=class extends l{config;twilioClient;server;constructor(e){super(),this.config={port:3e3,...e},this.name=e.name}get isTriggerChannel(){return!this.config.webhookPath}listen(){typeof process<"u"&&import("twilio").then(e=>{this.twilioClient=e.default(this.config.accountSid,this.config.authToken),console.log("[SMSChannel] Twilio client initialized"),this.config.webhookPath&&this.startWebhookServer()}).catch(e=>{console.error("[SMSChannel] Failed to initialize Twilio client:",e),console.error("[SMSChannel] Make sure to install twilio: npm install twilio")})}async send(e){if(!this.twilioClient)throw new Error("Twilio client not initialized. Did you call listen()?");let n=e.metadata?.from||this.config.to;if(!n)throw new Error('No recipient phone number specified. Set "to" in config or provide in output.metadata.from');try{await this.twilioClient.messages.create({body:e.output,from:this.config.from,to:n})}catch(t){throw console.error("[SMSChannel] Failed to send SMS:",t),new Error(`Failed to send SMS: ${t instanceof Error?t.message:String(t)}`)}}normalize(e){let n=e,t=n.From,i=n.Body,r=n.MessageSid;return{message:i,conversationId:t,data:n,context:{from:t,to:n.To,messageSid:r}}}startWebhookServer(){import("http").then(e=>{this.server=e.createServer((n,t)=>{this.handleWebhookRequest(n,t)}),this.server.listen(this.config.port,()=>{console.log(`[SMSChannel] Webhook server listening on port ${this.config.port} at ${this.config.webhookPath}`)})}).catch(e=>{console.error("[SMSChannel] Failed to start webhook server:",e)})}handleWebhookRequest(e,n){if(e.method!=="POST"||e.url!==this.config.webhookPath){n.writeHead(404),n.end("Not found");return}let t="";e.on("data",i=>{t+=i.toString()}),e.on("end",()=>{try{let i=new URLSearchParams(t),r={};i.forEach((o,a)=>{r[a]=o});let s=this.normalize(r);this.handleMessage(s),n.writeHead(200,{"Content-Type":"text/xml"}),n.end('<?xml version="1.0" encoding="UTF-8"?><Response></Response>')}catch(i){console.error("[SMSChannel] Error handling webhook:",i),n.writeHead(400),n.end("Bad request")}})}async stop(){if(this.server)return new Promise(e=>{this.server.close(e)})}};var T=class extends l{isTriggerChannel=!1;_timeout;_pendingResolve;constructor(e={}){super(),this._timeout=e.timeout??12e4}listen(){}async send(e){this._pendingResolve?.(e),this._pendingResolve=void 0}normalize(e){let n=e;return{message:typeof n.message=="string"?n.message:JSON.stringify(n),data:n,conversationId:`mcp-${Date.now()}-${Math.random().toString(36).slice(2,7)}`}}async trigger(e){let n=this.normalize(e);return new Promise((t,i)=>{let r=setTimeout(()=>{this._pendingResolve=void 0,i(new Error(`McpChannel: agent did not respond within ${this._timeout}ms`))},this._timeout);this._pendingResolve=s=>{clearTimeout(r),t(s.output)},this.handleMessage(n).catch(s=>{clearTimeout(r),this._pendingResolve=void 0,i(s instanceof Error?s:new Error(String(s)))})})}asAgentDefinition(e,n){return{name:e.name,description:e.description,...n!==void 0&&{inputSchema:n},invoke:t=>this.trigger(t)}}};export{l as BaseChannel,S as DiscordChannel,b as EmailChannel,T as McpChannel,I as SMSChannel,y as ScheduledChannel,C as SlackChannel,k as TelegramChannel,v as WebhookChannel};
|