@toolpack-sdk/agents 2.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,189 @@
1
+ import { EventEmitter } from 'events';
2
+ import { ModeConfig, ConversationStore, AssemblerOptions, Toolpack } from 'toolpack-sdk';
3
+ import { A as AgentInput, W as WorkflowStep, a as AgentResult, C as ChannelInterface, I as Interceptor, b as IAgentRegistry, B as BaseAgentOptions, c as AgentRunOptions, P as PendingAsk } from './types-BWoRx1ZE.cjs';
4
+
5
+ /**
6
+ * Abstract base class for all agents.
7
+ * Extend this to create custom agents with specific behaviors.
8
+ */
9
+ declare abstract class BaseAgent<TIntent extends string = string> extends EventEmitter {
10
+ /** Unique agent identifier */
11
+ abstract name: string;
12
+ /** Human-readable description of what this agent does */
13
+ abstract description: string;
14
+ /**
15
+ * Mode this agent runs in. Each agent owns a full ModeConfig including its
16
+ * system prompt, allowed tools, workflow, and tool-search policy. The mode
17
+ * is registered with the Toolpack on first run and activated for every
18
+ * invocation.
19
+ *
20
+ * Use built-in modes (AGENT_MODE, CHAT_MODE, CODING_MODE) as a base, or
21
+ * compose a custom ModeConfig.
22
+ */
23
+ abstract mode: ModeConfig | string;
24
+ /** Provider override (e.g., 'anthropic', 'openai') - inherits from Toolpack if not set */
25
+ provider?: string;
26
+ /** Model override - inherits from provider default if not set */
27
+ model?: string;
28
+ /** Workflow configuration merged on top of mode config */
29
+ workflow?: Record<string, unknown>;
30
+ /**
31
+ * Conversation history store. Auto-initialised to `InMemoryConversationStore` in the
32
+ * constructor so subclass field initialisers (e.g. `interceptors = [createCaptureInterceptor({
33
+ * store: this.conversationHistory })]`) can reference it safely. Replace with a
34
+ * database-backed implementation for production persistence.
35
+ */
36
+ conversationHistory: ConversationStore;
37
+ /**
38
+ * Options forwarded to `assemblePrompt()` when `run()` builds LLM context from history.
39
+ * Defaults to `assemblePrompt`'s own defaults (addressed-only mode on, 3 000-token budget).
40
+ */
41
+ assemblerOptions?: AssemblerOptions;
42
+ /** Channels this agent listens on and sends responses to */
43
+ channels: ChannelInterface[];
44
+ /** Interceptors applied to every inbound message before invokeAgent is called */
45
+ interceptors: Interceptor[];
46
+ /** Reference to the registry for sendTo() and delegation support */
47
+ _registry?: IAgentRegistry;
48
+ /**
49
+ * Invocation-scoped context fields — set by `_bindChannel` immediately before
50
+ * calling `invokeAgent` and read inside `run()`, `ask()`, and `delegate()`.
51
+ *
52
+ * KNOWN LIMITATION: these are instance-level fields, not async-local storage.
53
+ * Two different conversations processed concurrently by the same agent can
54
+ * clobber each other's values. The conversation lock serialises within a single
55
+ * conversationId, but distinct conversationIds run concurrently.
56
+ *
57
+ * Fix: replace with `AsyncLocalStorage` in a future release. For now, agents
58
+ * that call `this.run()` while processing multiple concurrent conversations
59
+ * should pass `conversationId` explicitly to avoid relying on these fields.
60
+ */
61
+ _triggeringChannel?: string;
62
+ _conversationId?: string;
63
+ _isTriggerChannel?: boolean;
64
+ protected toolpack: Toolpack;
65
+ private readonly _initConfig?;
66
+ private _ownedToolpack;
67
+ private readonly _conversationLocks;
68
+ constructor(options: BaseAgentOptions);
69
+ /**
70
+ * Ensure the Toolpack instance is ready.
71
+ * No-op if the toolpack was provided at construction time.
72
+ * Creates and owns the instance from `apiKey` if it was not.
73
+ */
74
+ _ensureToolpack(): Promise<void>;
75
+ /**
76
+ * Start the agent: initialise Toolpack (if needed), bind message handlers to all
77
+ * configured channels, and begin listening.
78
+ *
79
+ * When using AgentRegistry, the registry calls this after setting `_registry`.
80
+ * For standalone single-agent deployments, call this directly.
81
+ */
82
+ start(): Promise<void>;
83
+ /**
84
+ * Stop all channels and release resources owned by this agent.
85
+ */
86
+ stop(): Promise<void>;
87
+ /**
88
+ * Main entry point for agent invocation.
89
+ * Implement this to handle incoming messages and route to appropriate logic.
90
+ */
91
+ abstract invokeAgent(input: AgentInput<TIntent>): Promise<AgentResult>;
92
+ /**
93
+ * Execute the agent using the Toolpack SDK.
94
+ *
95
+ * @param message - The user message to process.
96
+ * @param _options - Optional per-run workflow overrides.
97
+ * @param context - Optional context overrides. Supply `conversationId` here when
98
+ * invoking from `invokeAgent()` to avoid the instance-level `_conversationId`
99
+ * race that occurs when the same agent handles multiple concurrent conversations.
100
+ */
101
+ protected run(message: string, _options?: AgentRunOptions, context?: {
102
+ conversationId?: string;
103
+ }): Promise<AgentResult>;
104
+ /**
105
+ * Returns extra identity strings (platform user ids, bot ids) that should
106
+ * be treated as this agent for the purposes of `addressed-only` mode in
107
+ * `assemblePrompt`.
108
+ *
109
+ * The default implementation collects `botUserId` from every attached channel
110
+ * that exposes it (e.g. `SlackChannel` after `auth.test` resolves). Override
111
+ * this to add further aliases.
112
+ */
113
+ protected getAgentAliases(): string[];
114
+ /**
115
+ * Send a message to a named channel via the registry.
116
+ */
117
+ protected sendTo(channelName: string, message: string): Promise<void>;
118
+ /**
119
+ * Ask the user a question and pause execution.
120
+ */
121
+ protected ask(question: string, options?: {
122
+ context?: Record<string, unknown>;
123
+ maxRetries?: number;
124
+ expiresIn?: number;
125
+ }): Promise<AgentResult>;
126
+ /**
127
+ * Get the current pending ask for a conversation.
128
+ */
129
+ protected getPendingAsk(conversationId?: string): PendingAsk | null;
130
+ /**
131
+ * Resolve a pending ask with an answer.
132
+ */
133
+ protected resolvePendingAsk(id: string, answer: string): Promise<void>;
134
+ /**
135
+ * Evaluate if an answer sufficiently addresses a question.
136
+ */
137
+ protected evaluateAnswer(question: string, answer: string, options?: {
138
+ simpleValidation?: (answer: string) => boolean;
139
+ }): Promise<boolean>;
140
+ /**
141
+ * Handle a pending ask reply with automatic retry logic.
142
+ */
143
+ protected handlePendingAsk(pending: PendingAsk, reply: string, onSufficient: (answer: string) => Promise<AgentResult> | AgentResult, onInsufficient?: () => Promise<AgentResult> | AgentResult): Promise<AgentResult>;
144
+ /**
145
+ * Delegate a task to another agent by name (fire-and-forget).
146
+ */
147
+ protected delegate(agentName: string, input: Partial<AgentInput>): Promise<void>;
148
+ /**
149
+ * Delegate a task to another agent and wait for the result.
150
+ */
151
+ protected delegateAndWait(agentName: string, input: Partial<AgentInput>): Promise<AgentResult>;
152
+ onBeforeRun(_input: AgentInput<TIntent>): Promise<void>;
153
+ onStepComplete(_step: WorkflowStep): Promise<void>;
154
+ onComplete(_result: AgentResult): Promise<void>;
155
+ onError(_error: Error): Promise<void>;
156
+ /**
157
+ * Build the `AssemblerOptions` used for this call to `assemblePrompt`.
158
+ *
159
+ * Merges any subclass-provided `assemblerOptions.agentAliases` with platform-bot
160
+ * identities discovered on configured channels (e.g. `SlackChannel.botUserId`,
161
+ * `TelegramChannel.botUserId`). Read lazily on each `run()` so that identities
162
+ * populated asynchronously by each channel's startup self-check are picked up
163
+ * without a race.
164
+ */
165
+ private _resolveAssemblerOptions;
166
+ /**
167
+ * Returns the effective interceptor list for a channel binding. Prepends
168
+ * `createCaptureInterceptor` automatically so every inbound message and
169
+ * agent reply is persisted without manual wiring. The `CAPTURE_INTERCEPTOR_MARKER`
170
+ * check prevents double-registration if the developer already added one.
171
+ */
172
+ private _getEffectiveInterceptors;
173
+ /**
174
+ * Bind a message handler to a channel.
175
+ * Extracted here so both standalone start() and AgentRegistry can reuse the same logic.
176
+ */
177
+ private _bindChannel;
178
+ private _attachWorkflowStepUpdates;
179
+ private _acquireConversationLock;
180
+ private extractSteps;
181
+ }
182
+ interface AgentEvents {
183
+ 'agent:start': (input: AgentInput) => void;
184
+ 'agent:step': (step: WorkflowStep) => void;
185
+ 'agent:complete': (result: AgentResult) => void;
186
+ 'agent:error': (error: Error) => void;
187
+ }
188
+
189
+ export { type AgentEvents as A, BaseAgent as B };
@@ -0,0 +1,189 @@
1
+ import { EventEmitter } from 'events';
2
+ import { ModeConfig, ConversationStore, AssemblerOptions, Toolpack } from 'toolpack-sdk';
3
+ import { A as AgentInput, W as WorkflowStep, a as AgentResult, C as ChannelInterface, I as Interceptor, b as IAgentRegistry, B as BaseAgentOptions, c as AgentRunOptions, P as PendingAsk } from './types-BWoRx1ZE.js';
4
+
5
+ /**
6
+ * Abstract base class for all agents.
7
+ * Extend this to create custom agents with specific behaviors.
8
+ */
9
+ declare abstract class BaseAgent<TIntent extends string = string> extends EventEmitter {
10
+ /** Unique agent identifier */
11
+ abstract name: string;
12
+ /** Human-readable description of what this agent does */
13
+ abstract description: string;
14
+ /**
15
+ * Mode this agent runs in. Each agent owns a full ModeConfig including its
16
+ * system prompt, allowed tools, workflow, and tool-search policy. The mode
17
+ * is registered with the Toolpack on first run and activated for every
18
+ * invocation.
19
+ *
20
+ * Use built-in modes (AGENT_MODE, CHAT_MODE, CODING_MODE) as a base, or
21
+ * compose a custom ModeConfig.
22
+ */
23
+ abstract mode: ModeConfig | string;
24
+ /** Provider override (e.g., 'anthropic', 'openai') - inherits from Toolpack if not set */
25
+ provider?: string;
26
+ /** Model override - inherits from provider default if not set */
27
+ model?: string;
28
+ /** Workflow configuration merged on top of mode config */
29
+ workflow?: Record<string, unknown>;
30
+ /**
31
+ * Conversation history store. Auto-initialised to `InMemoryConversationStore` in the
32
+ * constructor so subclass field initialisers (e.g. `interceptors = [createCaptureInterceptor({
33
+ * store: this.conversationHistory })]`) can reference it safely. Replace with a
34
+ * database-backed implementation for production persistence.
35
+ */
36
+ conversationHistory: ConversationStore;
37
+ /**
38
+ * Options forwarded to `assemblePrompt()` when `run()` builds LLM context from history.
39
+ * Defaults to `assemblePrompt`'s own defaults (addressed-only mode on, 3 000-token budget).
40
+ */
41
+ assemblerOptions?: AssemblerOptions;
42
+ /** Channels this agent listens on and sends responses to */
43
+ channels: ChannelInterface[];
44
+ /** Interceptors applied to every inbound message before invokeAgent is called */
45
+ interceptors: Interceptor[];
46
+ /** Reference to the registry for sendTo() and delegation support */
47
+ _registry?: IAgentRegistry;
48
+ /**
49
+ * Invocation-scoped context fields — set by `_bindChannel` immediately before
50
+ * calling `invokeAgent` and read inside `run()`, `ask()`, and `delegate()`.
51
+ *
52
+ * KNOWN LIMITATION: these are instance-level fields, not async-local storage.
53
+ * Two different conversations processed concurrently by the same agent can
54
+ * clobber each other's values. The conversation lock serialises within a single
55
+ * conversationId, but distinct conversationIds run concurrently.
56
+ *
57
+ * Fix: replace with `AsyncLocalStorage` in a future release. For now, agents
58
+ * that call `this.run()` while processing multiple concurrent conversations
59
+ * should pass `conversationId` explicitly to avoid relying on these fields.
60
+ */
61
+ _triggeringChannel?: string;
62
+ _conversationId?: string;
63
+ _isTriggerChannel?: boolean;
64
+ protected toolpack: Toolpack;
65
+ private readonly _initConfig?;
66
+ private _ownedToolpack;
67
+ private readonly _conversationLocks;
68
+ constructor(options: BaseAgentOptions);
69
+ /**
70
+ * Ensure the Toolpack instance is ready.
71
+ * No-op if the toolpack was provided at construction time.
72
+ * Creates and owns the instance from `apiKey` if it was not.
73
+ */
74
+ _ensureToolpack(): Promise<void>;
75
+ /**
76
+ * Start the agent: initialise Toolpack (if needed), bind message handlers to all
77
+ * configured channels, and begin listening.
78
+ *
79
+ * When using AgentRegistry, the registry calls this after setting `_registry`.
80
+ * For standalone single-agent deployments, call this directly.
81
+ */
82
+ start(): Promise<void>;
83
+ /**
84
+ * Stop all channels and release resources owned by this agent.
85
+ */
86
+ stop(): Promise<void>;
87
+ /**
88
+ * Main entry point for agent invocation.
89
+ * Implement this to handle incoming messages and route to appropriate logic.
90
+ */
91
+ abstract invokeAgent(input: AgentInput<TIntent>): Promise<AgentResult>;
92
+ /**
93
+ * Execute the agent using the Toolpack SDK.
94
+ *
95
+ * @param message - The user message to process.
96
+ * @param _options - Optional per-run workflow overrides.
97
+ * @param context - Optional context overrides. Supply `conversationId` here when
98
+ * invoking from `invokeAgent()` to avoid the instance-level `_conversationId`
99
+ * race that occurs when the same agent handles multiple concurrent conversations.
100
+ */
101
+ protected run(message: string, _options?: AgentRunOptions, context?: {
102
+ conversationId?: string;
103
+ }): Promise<AgentResult>;
104
+ /**
105
+ * Returns extra identity strings (platform user ids, bot ids) that should
106
+ * be treated as this agent for the purposes of `addressed-only` mode in
107
+ * `assemblePrompt`.
108
+ *
109
+ * The default implementation collects `botUserId` from every attached channel
110
+ * that exposes it (e.g. `SlackChannel` after `auth.test` resolves). Override
111
+ * this to add further aliases.
112
+ */
113
+ protected getAgentAliases(): string[];
114
+ /**
115
+ * Send a message to a named channel via the registry.
116
+ */
117
+ protected sendTo(channelName: string, message: string): Promise<void>;
118
+ /**
119
+ * Ask the user a question and pause execution.
120
+ */
121
+ protected ask(question: string, options?: {
122
+ context?: Record<string, unknown>;
123
+ maxRetries?: number;
124
+ expiresIn?: number;
125
+ }): Promise<AgentResult>;
126
+ /**
127
+ * Get the current pending ask for a conversation.
128
+ */
129
+ protected getPendingAsk(conversationId?: string): PendingAsk | null;
130
+ /**
131
+ * Resolve a pending ask with an answer.
132
+ */
133
+ protected resolvePendingAsk(id: string, answer: string): Promise<void>;
134
+ /**
135
+ * Evaluate if an answer sufficiently addresses a question.
136
+ */
137
+ protected evaluateAnswer(question: string, answer: string, options?: {
138
+ simpleValidation?: (answer: string) => boolean;
139
+ }): Promise<boolean>;
140
+ /**
141
+ * Handle a pending ask reply with automatic retry logic.
142
+ */
143
+ protected handlePendingAsk(pending: PendingAsk, reply: string, onSufficient: (answer: string) => Promise<AgentResult> | AgentResult, onInsufficient?: () => Promise<AgentResult> | AgentResult): Promise<AgentResult>;
144
+ /**
145
+ * Delegate a task to another agent by name (fire-and-forget).
146
+ */
147
+ protected delegate(agentName: string, input: Partial<AgentInput>): Promise<void>;
148
+ /**
149
+ * Delegate a task to another agent and wait for the result.
150
+ */
151
+ protected delegateAndWait(agentName: string, input: Partial<AgentInput>): Promise<AgentResult>;
152
+ onBeforeRun(_input: AgentInput<TIntent>): Promise<void>;
153
+ onStepComplete(_step: WorkflowStep): Promise<void>;
154
+ onComplete(_result: AgentResult): Promise<void>;
155
+ onError(_error: Error): Promise<void>;
156
+ /**
157
+ * Build the `AssemblerOptions` used for this call to `assemblePrompt`.
158
+ *
159
+ * Merges any subclass-provided `assemblerOptions.agentAliases` with platform-bot
160
+ * identities discovered on configured channels (e.g. `SlackChannel.botUserId`,
161
+ * `TelegramChannel.botUserId`). Read lazily on each `run()` so that identities
162
+ * populated asynchronously by each channel's startup self-check are picked up
163
+ * without a race.
164
+ */
165
+ private _resolveAssemblerOptions;
166
+ /**
167
+ * Returns the effective interceptor list for a channel binding. Prepends
168
+ * `createCaptureInterceptor` automatically so every inbound message and
169
+ * agent reply is persisted without manual wiring. The `CAPTURE_INTERCEPTOR_MARKER`
170
+ * check prevents double-registration if the developer already added one.
171
+ */
172
+ private _getEffectiveInterceptors;
173
+ /**
174
+ * Bind a message handler to a channel.
175
+ * Extracted here so both standalone start() and AgentRegistry can reuse the same logic.
176
+ */
177
+ private _bindChannel;
178
+ private _attachWorkflowStepUpdates;
179
+ private _acquireConversationLock;
180
+ private extractSteps;
181
+ }
182
+ interface AgentEvents {
183
+ 'agent:start': (input: AgentInput) => void;
184
+ 'agent:step': (step: WorkflowStep) => void;
185
+ 'agent:complete': (result: AgentResult) => void;
186
+ 'agent:error': (error: Error) => void;
187
+ }
188
+
189
+ export { type AgentEvents as A, BaseAgent as B };
@@ -0,0 +1,17 @@
1
+ "use strict";var M=Object.defineProperty;var Z=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var ee=Object.prototype.hasOwnProperty;var te=(o,t)=>{for(var e in t)M(o,e,{get:t[e],enumerable:!0})},ne=(o,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of X(t))!ee.call(o,s)&&s!==e&&M(o,s,{get:()=>t[s],enumerable:!(n=Z(t,s))||n.enumerable});return o};var se=o=>ne(M({},"__esModule",{value:!0}),o);var pe={};te(pe,{IntentClassifierAgent:()=>R,SummarizerAgent:()=>_});module.exports=se(pe);var Y=require("events"),b=require("toolpack-sdk");var x=Symbol("interceptor-skip-sentinel");function U(o){return o===x}function H(){return x}var N=class extends Error{constructor(t,e){super(`Invocation depth ${t} exceeds maximum ${e}`),this.name="InvocationDepthExceededError"}};function W(o,t,e,n,s={}){let i=s.maxInvocationDepth??5;return{async execute(r){let l=(u=>({agent:t,channel:e,registry:n,invocationDepth:u,delegateAndWait:async(p,m)=>{let d=u+1;if(d>i)throw new N(d,i);if(!n)throw new Error(`Cannot delegate to "${p}": agent is running in standalone mode without a registry`);let y=n.getAgent(p);if(!y)throw new Error(`Agent "${p}" not found for delegation`);let g={message:m.message??"",intent:m.intent,data:m.data,context:m.context,conversationId:m.conversationId??r.conversationId??`delegation-${Date.now()}`};return await y.invokeAgent(g)},skip:H}))(0),c=async u=>{let p=u??r;return await t.invokeAgent(p)};for(let u=o.length-1;u>=0;u--){let p=o[u],m=c;c=async d=>await p(d??r,l,m)}return await c()}}}async function B(o,t){let e=await o.execute(t);return e===x?null:e}var E=require("crypto");function re(o){let t=o.context??{},e=t.channelType;return e==="im"||e==="private"||e==="dm"?"dm":t.threadId!==void 0?"thread":"channel"}var O=Symbol.for("toolpack:capture-history");function F(o){let t=o.captureAgentReplies??!0,e=o.getScope??re,n=o.getMessageId??(r=>r.context?.messageId??r.context?.eventId??(0,E.randomUUID)()),s=o.getMentions??(r=>r.context?.mentions??[]),i=async(r,a,l)=>{let c=r.conversationId;if(!c)return a.logger?.warn("[capture-history] Message has no conversationId \u2014 skipping capture"),await l();let u=r.participant;if(u){let m={id:n(r),conversationId:c,participant:u,content:r.message??"",timestamp:new Date().toISOString(),scope:e(r),metadata:{channelType:r.context?.channelType,threadId:r.context?.threadId,messageId:r.context?.messageId,mentions:s(r),channelName:r.context?.channelName,channelId:r.context?.channelId}};try{await o.store.append(m),o.onCaptured?.(m),a.logger?.debug("[capture-history] Captured inbound message",{messageId:m.id,participantId:u.id,conversationId:c})}catch(d){a.logger?.warn("[capture-history] Failed to store inbound message",{error:d instanceof Error?d.message:String(d)})}}let p=await l();if(t&&!U(p)&&p.output!=null){let m={kind:"agent",id:a.agent.name,displayName:a.agent.name},d={id:(0,E.randomUUID)(),conversationId:c,participant:m,content:p.output,timestamp:new Date().toISOString(),scope:e(r),metadata:{channelType:r.context?.channelType,threadId:r.context?.threadId,channelName:r.context?.channelName,channelId:r.context?.channelId}};try{await o.store.append(d),o.onCaptured?.(d),a.logger?.debug("[capture-history] Captured agent reply",{messageId:d.id,agentId:a.agent.name,conversationId:c})}catch(y){a.logger?.warn("[capture-history] Failed to store agent reply",{error:y instanceof Error?y.message:String(y)})}}return p};return i[O]=!0,i}var K=require("crypto");function q(o){return Math.ceil(o.length/4)}function oe(o){return{id:o.id,participant:o.participant,content:o.content,timestamp:o.timestamp}}function ie(o,t){let{participant:e,content:n}=o;return e.kind==="system"?{role:"system",content:n}:e.kind==="agent"?e.id===t?{role:"assistant",content:n}:{role:"user",content:`${e.displayName??e.id} (agent): ${n}`}:{role:"user",content:`${e.displayName??e.id}: ${n}`}}function ae(o,t,e){return!!(o.participant.id===t||o.metadata?.mentions?.some(n=>e.has(n)))}async function J(o,t,e,n,s={},i){let{scope:r,addressedOnlyMode:a=!0,tokenBudget:l=3e3,rollingSummaryThreshold:c=40,timeWindowMinutes:u,maxTurnsToLoad:p=100,agentAliases:m}=s,d=new Set([e,...m??[]]),y=u!==void 0?new Date(Date.now()-u*60*1e3).toISOString():void 0,g=await o.get(t,{scope:r,sinceTimestamp:y,limit:p}),$=g.length;if(a){let h=new Set;for(let f=0;f<g.length;f++){let S=g[f];if(ae(S,e,d)&&h.add(S.id),f<g.length-1){let k=g[f+1];k.participant.kind==="agent"&&k.participant.id===e&&h.add(S.id)}}let v=g[g.length-1];v&&h.add(v.id),g=g.filter(f=>h.has(f.id))}let T=!1;if(g.length>c&&i){let h=Math.floor(g.length/2),v=g.slice(0,h),f=g.slice(h),S=v.filter(k=>!k.metadata?.isSummary);try{let k=await i.invokeAgent({message:"summarize",data:{turns:S.map(oe),agentName:n,agentId:e,maxTokens:Math.floor(l*.25),extractDecisions:!0}}),L=JSON.parse(k.output),j={id:`summary-${(0,K.randomUUID)()}`,conversationId:t,participant:{kind:"system",id:"summarizer"},content:`[Summary of ${L.turnsSummarized} earlier turns]: ${L.summary}`,timestamp:v[0].timestamp,scope:r??"channel",metadata:{isSummary:!0}};g=[j,...f],T=!0;try{await o.append(j),await o.deleteMessages(t,v.map(Q=>Q.id))}catch{}}catch{g=g.slice(-c)}}else g.length>c&&(g=g.slice(-c));let C=g.map(h=>ie(h,e));if(C.length===0)return{messages:[],estimatedTokens:0,turnsLoaded:$,hasSummary:T};let D=C[C.length-1],z=[D],P=q(D.content);for(let h=C.length-2;h>=0;h--){let v=C[h],f=q(v.content);if(P+f>l)break;z.unshift(v),P+=f}return{messages:z,estimatedTokens:P,turnsLoaded:$,hasSummary:T}}var I=class extends Error{constructor(t){super(t),this.name="AgentError"}};var A=class extends Y.EventEmitter{provider;model;workflow;conversationHistory;assemblerOptions;channels=[];interceptors=[];_registry;_triggeringChannel;_conversationId;_isTriggerChannel;toolpack;_initConfig;_ownedToolpack=!1;_conversationLocks=new Map;constructor(t){super(),this.conversationHistory=new b.InMemoryConversationStore,"toolpack"in t?this.toolpack=t.toolpack:this._initConfig=t}async _ensureToolpack(){if(!this.toolpack){if(!this._initConfig)throw new Error(`[${this.name??"agent"}] Cannot start: no apiKey or toolpack provided`);this.toolpack=await b.Toolpack.init({provider:this._initConfig.provider??"anthropic",apiKey:this._initConfig.apiKey,model:this._initConfig.model}),this._ownedToolpack=!0}}async start(){await this._ensureToolpack(),this.mode&&(typeof this.mode=="string"?this.toolpack.setMode(this.mode):(this.toolpack.registerMode(this.mode),this.toolpack.setMode(this.mode.name)));for(let t of this.channels)this._bindChannel(t),t.listen()}async stop(){for(let t of this.channels)"stop"in t&&typeof t.stop=="function"&&await t.stop();this._ownedToolpack&&await this.toolpack.disconnect?.()}async run(t,e,n){let s=n?.conversationId??this._conversationId;await this.onBeforeRun({message:t,conversationId:s}),this.emit("agent:start",{message:t});try{typeof this.mode=="string"?this.toolpack.setMode(this.mode):(this.toolpack.registerMode(this.mode),this.toolpack.setMode(this.mode.name));let i=[];if(s)try{let c=await J(this.conversationHistory,s,this.name,this.name,this._resolveAssemblerOptions()),u=c.messages[c.messages.length-1],p=t.trim(),d=p!==""&&u?.role==="user"&&typeof u.content=="string"&&(u.content===p||u.content.endsWith(`: ${p}`))?c.messages.slice(0,-1):c.messages;i.push(...d)}catch{}t.trim()&&i.push({role:"user",content:t});let r=[];if(s){let c=this.conversationHistory;r.push({name:"conversation_search",displayName:"Conversation Search",description:"Search past conversation history for specific information, questions, or topics mentioned earlier in this conversation.",category:"search",parameters:{type:"object",properties:{query:{type:"string",description:"Keywords or phrases to search for in conversation history."},limit:{type:"number",description:"Maximum number of results to return (default: 5)."}},required:["query"]},execute:async u=>{let p=await c.search(s,String(u.query??""),{limit:typeof u.limit=="number"?u.limit:5});return{results:p.map(m=>({role:m.participant.kind==="agent"?"assistant":"user",content:m.content,timestamp:m.timestamp})),count:p.length}}})}let a=await this.toolpack.generate({messages:i,model:this.model||"",requestTools:r.length>0?r:void 0},this.provider),l={output:a.content||"",steps:this.extractSteps(a),metadata:a.usage?{usage:a.usage}:void 0};return await this.onComplete(l),this.emit("agent:complete",l),l}catch(i){throw await this.onError(i),this.emit("agent:error",i),i}}getAgentAliases(){let t=[];for(let e of this.channels){let n=e.botUserId;n&&t.push(n)}return t}async sendTo(t,e){if(!this._registry)throw new Error("Agent not registered - _registry not set");await this._registry.sendTo(t,{output:e})}async ask(t,e){if(!this._registry)throw new I("Agent not registered - cannot use ask()");if(!this._conversationId)throw new I("No conversationId available - ask() requires a conversation channel");if(this._isTriggerChannel)throw new I("this.ask() called from a trigger channel (ScheduledChannel). Trigger channels have no human recipient \u2014 use a conversation channel (Slack, Telegram, Webhook) instead.");if(!this._triggeringChannel||this._triggeringChannel.trim()==="")throw new I("Cannot use ask() - no triggering channel available. The channel must have a name registered with AgentRegistry.");let n=this._registry.addPendingAsk({conversationId:this._conversationId,agentName:this.name,question:t,context:e?.context??{},maxRetries:e?.maxRetries??2,expiresAt:e?.expiresIn?new Date(Date.now()+e.expiresIn):void 0,channelName:this._triggeringChannel});return await this.sendTo(this._triggeringChannel,t),{output:t,metadata:{waitingForHuman:!0,askId:n.id}}}getPendingAsk(t){if(!this._registry)return null;let e=t??this._conversationId;return e?this._registry.getPendingAsk(e)??null:null}async resolvePendingAsk(t,e){if(!this._registry)throw new I("Agent not registered - cannot resolve ask");await this._registry.resolvePendingAsk(t,e)}async evaluateAnswer(t,e,n){return n?.simpleValidation?n.simpleValidation(e):(await this.run(`Evaluate if this answer sufficiently addresses the question.
2
+
3
+ Question: "${t}"
4
+ Answer: "${e}"
5
+
6
+ Is this answer sufficient? Reply with ONLY "yes" or "no".`,{workflow:{mode:"single-shot"}})).output.toLowerCase().trim().startsWith("yes")}async handlePendingAsk(t,e,n,s){return await this.evaluateAnswer(t.question,e,{simpleValidation:r=>r.trim().length>3})?(await this.resolvePendingAsk(t.id,e),n(e)):t.retries>=t.maxRetries?(await this.resolvePendingAsk(t.id,"__insufficient__"),this._triggeringChannel&&await this.sendTo(this._triggeringChannel,"I was unable to get enough information to proceed. Skipping this step."),s?s():{output:"Step skipped due to insufficient input.",metadata:{skipped:!0,askId:t.id}}):(this._registry?.incrementRetries(t.id),this.ask(`I need a bit more clarity on: "${t.question}". Could you provide more details?`,{context:t.context,maxRetries:t.maxRetries}))}async delegate(t,e){if(!this._registry)throw new I("Agent not registered - cannot use delegate()");let n={message:e.message,intent:e.intent,data:e.data,context:{...e.context||{},delegatedBy:this.name},conversationId:e.conversationId||this._conversationId||`delegation-${Date.now()}`};this._registry.invoke(t,n).catch(s=>{console.error(`[${this.name}] Delegation to ${t} failed:`,s.message)})}async delegateAndWait(t,e){if(!this._registry)throw new I("Agent not registered - cannot use delegateAndWait()");let n={message:e.message,intent:e.intent,data:e.data,context:{...e.context||{},delegatedBy:this.name},conversationId:e.conversationId||this._conversationId||`delegation-${Date.now()}`};return await this._registry.invoke(t,n)}async onBeforeRun(t){}async onStepComplete(t){}async onComplete(t){}async onError(t){}_resolveAssemblerOptions(){let t=this.channels.map(s=>s.botUserId).filter(s=>typeof s=="string"&&s.length>0),e=this.assemblerOptions?.agentAliases??[];if(t.length===0&&e.length===0)return this.assemblerOptions;let n=Array.from(new Set([...e,...t]));return{...this.assemblerOptions,agentAliases:n}}_getEffectiveInterceptors(){return this.interceptors.some(e=>e[O]===!0)?this.interceptors:[F({store:this.conversationHistory}),...this.interceptors]}_bindChannel(t){t.onMessage(async e=>{if(!e.conversationId){console.warn(`[${this.name}] Message received without conversationId \u2014 skipping`);return}let n=await this._acquireConversationLock(e.conversationId),s=()=>{};try{this._triggeringChannel=t.name,this._isTriggerChannel=t.isTriggerChannel,this._conversationId=e.conversationId,s=this._attachWorkflowStepUpdates(t,e);let i=W(this._getEffectiveInterceptors(),this,t,this._registry??null),r=await B(i,e);if(r===null)return;let a={output:r.output,metadata:r.metadata};await t.send({output:a.output,metadata:{...a.metadata,conversationId:e.conversationId,...e.context}})}catch(i){let r=i instanceof Error?i.message:"Unknown error occurred";console.error(`[${this.name}] Error in agent invocation: ${r}`);try{await t.send({output:`Error: ${r}`,metadata:{conversationId:e.conversationId,error:!0,...e.context}})}catch(a){console.error(`[${this.name}] Failed to send error to channel: ${a}`)}}finally{s(),n()}})}_attachWorkflowStepUpdates(t,e){if(t.isTriggerChannel)return()=>{};let n=new Set,s=new Set,i=a=>{a?.id&&n.add(String(a.id))},r=(a,l)=>{if(!l?.id||!n.has(String(l.id))||!a?.result?.output||typeof a.result.output!="string"||l?.steps?.length&&Number(l.steps.length)<=1)return;let c=`${String(l.id)}:${String(a.id??a.number??"unknown")}`;if(s.has(c))return;s.add(c);let u=a.result.output.trim();if(!u)return;let p=u.length>3500?`${u.slice(0,3500)}
7
+ ... [truncated]`:u,m=`Step ${a.number}: ${a.description||"Completed"}`;t.send({output:`${m}
8
+
9
+ ${p}`,metadata:{conversationId:e.conversationId,...e.context}}).catch(d=>{let y=d instanceof Error?d.message:String(d);console.warn(`[${this.name}] Failed to send workflow step update: ${y}`)})};return this.toolpack.on("workflow:plan_created",i),this.toolpack.on("workflow:step_complete",r),()=>{this.toolpack.off("workflow:plan_created",i),this.toolpack.off("workflow:step_complete",r)}}async _acquireConversationLock(t){for(;this._conversationLocks.has(t);)try{await this._conversationLocks.get(t)}catch{}let e,n=new Promise(s=>{e=s});return this._conversationLocks.set(t,n),()=>{this._conversationLocks.delete(t),e()}}extractSteps(t){let e=t;if(e.plan&&typeof e.plan=="object"){let n=e.plan;if(Array.isArray(n.steps))return n.steps.map(s=>({number:s.number||0,description:s.description||"",status:s.status||"completed",result:s.result}))}if(Array.isArray(e.steps))return e.steps}};var V=require("toolpack-sdk"),ce={...V.CHAT_MODE,name:"intent-classifier-mode",systemPrompt:["You classify whether a message is asking an agent to respond.","","Categories:","direct = Message uses @mention, name in greeting, possessive, or commands the agent to act","indirect = Agent is mentioned but unclear if response wanted (talking ABOUT, not TO them)","passive = No addressing detected; agent should only listen, not reply","ignore = Definitely not for this agent (noise, code blocks, other bots)","","Response must start with one of: direct, indirect, passive, ignore"].join(`
10
+ `)},R=class extends A{name="intent-classifier";description="Classifies whether a message is directly addressing an agent for response";mode=ce;constructor(t){super(t)}async invokeAgent(t){let e=t.data;if(e?.isDirectMessage)return{output:"direct",metadata:{classification:"direct",shortCircuit:"dm"}};if(!e?.message)return{output:"ignore",metadata:{error:"No message provided for classification"}};let n=[];if(n.push(`Context: Public channel #${e.channelName}`),n.push(`Target agent: "${e.agentName}" (ID: ${e.agentId})`),n.push(`Message sender: ${e.senderName}`),e.recentContext&&e.recentContext.length>0){n.push(`
11
+ Recent conversation:`);for(let a of e.recentContext)n.push(` ${a.sender}: ${a.content.substring(0,100)}`)}n.push(`
12
+ Message to classify: "${e.message}"`),e.includeExamples&&(n.push(`
13
+ Examples of classifications:`),n.push(` "@${e.agentName} help me" \u2192 direct`),n.push(` "Can someone ask ${e.agentName} about this?" \u2192 indirect`),n.push(` "I was talking to ${e.agentName} earlier" \u2192 passive`),n.push(' "Check the logs" \u2192 ignore')),n.push(`
14
+ Classification (start with direct, indirect, passive, or ignore):`);let s=n.join(`
15
+ `),i=await this.run(s),r=this.normalizeClassification(i.output);return{output:r,metadata:{rawOutput:i.output,classification:r,confidence:"high"}}}normalizeClassification(t){let e=t.toLowerCase().trim().split(/\s+/)[0],n=t.toLowerCase();return["direct","indirect","passive","ignore"].includes(e)?e:n.includes("indirect")||n.includes("mention")?"indirect":n.includes("passive")||n.includes("listen")?"passive":n.includes("ignore")||n.includes("skip")?"ignore":n.includes("direct")||n.includes("addressed")?"direct":"ignore"}};var G=require("toolpack-sdk"),ue={...G.CHAT_MODE,name:"summarizer-mode",systemPrompt:["You are a conversation summarizer for multi-participant chat histories.","Your job is to compress older conversation turns into a dense summary that preserves:","","1. Key facts and information shared","2. Decisions made or action items assigned","3. Context relevant to the target agent's perspective","4. Important questions asked or problems raised","","Summarize from the perspective of the target agent.","If the agent was not addressed in a turn, note it as observed context.","Use bullet points for clarity. Be concise but complete.","","Output format: Return ONLY a JSON object with these fields:","- summary: string (the summary text)","- turnsSummarized: number (count of turns processed)","- hasDecisions: boolean (whether any decisions/action items were found)","- estimatedTokens: number (rough estimate: characters / 4)","","Do not include markdown code blocks, just the raw JSON."].join(`
16
+ `)},_=class extends A{name="summarizer";description="Compresses conversation history into compact summaries for prompt assembly";mode=ue;constructor(t){super(t)}async invokeAgent(t){let e=t.data;if(!e?.turns||e.turns.length===0)return{output:JSON.stringify({summary:"(No history to summarize)",turnsSummarized:0,hasDecisions:!1,estimatedTokens:5}),metadata:{emptyInput:!0}};let n=e.maxTokens??800,s=e.extractDecisions??!0,i=[`Target agent: "${e.agentName}" (ID: ${e.agentId})`,`Maximum summary length: ~${n} tokens`,`Extract decisions/action items: ${s?"yes":"no"}`,"",`Conversation turns to summarize (${e.turns.length} turns):`,""];for(let c of e.turns){let u=new Date(c.timestamp).toLocaleString("en-US",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}),p=c.participant.displayName??c.participant.id,m=c.participant.kind==="agent"?`[BOT] ${p}`:p,d=`[${u}] ${m}: ${c.content.substring(0,200)}`;c.content.length>200&&(d+="..."),c.metadata?.isToolCall&&c.metadata.toolName&&(d+=` [tool: ${c.metadata.toolName}]`),i.push(d)}i.push("","Generate a JSON summary object:");let r=i.join(`
17
+ `),a=await this.run(r),l=this.parseSummarizerOutput(a.output,e.turns.length);return{output:JSON.stringify(l),metadata:{turnsProcessed:e.turns.length,rawOutputLength:a.output.length}}}parseSummarizerOutput(t,e){let n=t.trim(),s=n.match(/```(?:json)?\s*([\s\S]*?)\s*```/);s&&(n=s[1].trim());try{let i=JSON.parse(n);return{summary:typeof i.summary=="string"&&i.summary.length>0?i.summary:this.generateFallbackSummary(e),turnsSummarized:typeof i.turnsSummarized=="number"?i.turnsSummarized:e,hasDecisions:typeof i.hasDecisions=="boolean"?i.hasDecisions:!1,estimatedTokens:typeof i.estimatedTokens=="number"&&i.estimatedTokens>0?i.estimatedTokens:Math.ceil(t.length/4)}}catch{return{summary:this.generateFallbackSummary(e),turnsSummarized:e,hasDecisions:t.toLowerCase().includes("decision")||t.toLowerCase().includes("action"),estimatedTokens:Math.ceil(t.length/4)}}}generateFallbackSummary(t){return`(Summary of ${t} conversation turns - key details preserved in full context)`}};0&&(module.exports={IntentClassifierAgent,SummarizerAgent});
@@ -0,0 +1,74 @@
1
+ export { I as IntentClassification, a as IntentClassifierAgent, b as IntentClassifierInput } from '../intent-classifier-agent-BLXXcbNJ.cjs';
2
+ import { B as BaseAgentOptions, A as AgentInput, a as AgentResult } from '../types-BWoRx1ZE.cjs';
3
+ import { B as BaseAgent } from '../base-agent-CjrUlo6Y.cjs';
4
+ import { Participant, ModeConfig } from 'toolpack-sdk';
5
+ export { Participant } from 'toolpack-sdk';
6
+ import 'events';
7
+
8
+ /**
9
+ * A message turn in the conversation history.
10
+ */
11
+ interface HistoryTurn {
12
+ /** Unique identifier for this turn */
13
+ id: string;
14
+ /** Participant who sent this message */
15
+ participant: Participant;
16
+ /** The message content */
17
+ content: string;
18
+ /** ISO timestamp */
19
+ timestamp: string;
20
+ /** Optional metadata about the turn */
21
+ metadata?: {
22
+ /** Whether this was a tool invocation */
23
+ isToolCall?: boolean;
24
+ /** Tool name if applicable */
25
+ toolName?: string;
26
+ /** Tool result if applicable */
27
+ toolResult?: string;
28
+ };
29
+ }
30
+ /**
31
+ * Input payload for summarization.
32
+ */
33
+ interface SummarizerInput {
34
+ /** The conversation turns to summarize (older messages first) */
35
+ turns: HistoryTurn[];
36
+ /** The target agent's name (for perspective-aware summary) */
37
+ agentName: string;
38
+ /** The agent's unique identifier */
39
+ agentId: string;
40
+ /** Maximum length of the summary in tokens (approximate) */
41
+ maxTokens?: number;
42
+ /** Whether to include action items/decisions in the summary */
43
+ extractDecisions?: boolean;
44
+ }
45
+ /**
46
+ * Result of a summarization operation.
47
+ */
48
+ interface SummarizerOutput {
49
+ /** The generated summary text */
50
+ summary: string;
51
+ /** Number of turns that were summarized */
52
+ turnsSummarized: number;
53
+ /** Whether decisions/action items were extracted */
54
+ hasDecisions: boolean;
55
+ /** Approximate token count of the summary */
56
+ estimatedTokens: number;
57
+ }
58
+ declare class SummarizerAgent extends BaseAgent {
59
+ name: string;
60
+ description: string;
61
+ mode: ModeConfig;
62
+ constructor(options: BaseAgentOptions);
63
+ invokeAgent(input: AgentInput): Promise<AgentResult>;
64
+ /**
65
+ * Parse and validate the LLM output into a SummarizerOutput.
66
+ */
67
+ private parseSummarizerOutput;
68
+ /**
69
+ * Generate a fallback summary when parsing fails.
70
+ */
71
+ private generateFallbackSummary;
72
+ }
73
+
74
+ export { type HistoryTurn, SummarizerAgent, type SummarizerInput, type SummarizerOutput };
@@ -0,0 +1,74 @@
1
+ export { I as IntentClassification, a as IntentClassifierAgent, b as IntentClassifierInput } from '../intent-classifier-agent-BLpDwKVf.js';
2
+ import { B as BaseAgentOptions, A as AgentInput, a as AgentResult } from '../types-BWoRx1ZE.js';
3
+ import { B as BaseAgent } from '../base-agent-Cx2kWzLF.js';
4
+ import { Participant, ModeConfig } from 'toolpack-sdk';
5
+ export { Participant } from 'toolpack-sdk';
6
+ import 'events';
7
+
8
+ /**
9
+ * A message turn in the conversation history.
10
+ */
11
+ interface HistoryTurn {
12
+ /** Unique identifier for this turn */
13
+ id: string;
14
+ /** Participant who sent this message */
15
+ participant: Participant;
16
+ /** The message content */
17
+ content: string;
18
+ /** ISO timestamp */
19
+ timestamp: string;
20
+ /** Optional metadata about the turn */
21
+ metadata?: {
22
+ /** Whether this was a tool invocation */
23
+ isToolCall?: boolean;
24
+ /** Tool name if applicable */
25
+ toolName?: string;
26
+ /** Tool result if applicable */
27
+ toolResult?: string;
28
+ };
29
+ }
30
+ /**
31
+ * Input payload for summarization.
32
+ */
33
+ interface SummarizerInput {
34
+ /** The conversation turns to summarize (older messages first) */
35
+ turns: HistoryTurn[];
36
+ /** The target agent's name (for perspective-aware summary) */
37
+ agentName: string;
38
+ /** The agent's unique identifier */
39
+ agentId: string;
40
+ /** Maximum length of the summary in tokens (approximate) */
41
+ maxTokens?: number;
42
+ /** Whether to include action items/decisions in the summary */
43
+ extractDecisions?: boolean;
44
+ }
45
+ /**
46
+ * Result of a summarization operation.
47
+ */
48
+ interface SummarizerOutput {
49
+ /** The generated summary text */
50
+ summary: string;
51
+ /** Number of turns that were summarized */
52
+ turnsSummarized: number;
53
+ /** Whether decisions/action items were extracted */
54
+ hasDecisions: boolean;
55
+ /** Approximate token count of the summary */
56
+ estimatedTokens: number;
57
+ }
58
+ declare class SummarizerAgent extends BaseAgent {
59
+ name: string;
60
+ description: string;
61
+ mode: ModeConfig;
62
+ constructor(options: BaseAgentOptions);
63
+ invokeAgent(input: AgentInput): Promise<AgentResult>;
64
+ /**
65
+ * Parse and validate the LLM output into a SummarizerOutput.
66
+ */
67
+ private parseSummarizerOutput;
68
+ /**
69
+ * Generate a fallback summary when parsing fails.
70
+ */
71
+ private generateFallbackSummary;
72
+ }
73
+
74
+ export { type HistoryTurn, SummarizerAgent, type SummarizerInput, type SummarizerOutput };
@@ -0,0 +1,17 @@
1
+ import{EventEmitter as Z}from"events";import{Toolpack as X,InMemoryConversationStore as ee}from"toolpack-sdk";var b=Symbol("interceptor-skip-sentinel");function L(i){return i===b}function j(){return b}var T=class extends Error{constructor(t,e){super(`Invocation depth ${t} exceeds maximum ${e}`),this.name="InvocationDepthExceededError"}};function U(i,t,e,n,r={}){let o=r.maxInvocationDepth??5;return{async execute(s){let l=(u=>({agent:t,channel:e,registry:n,invocationDepth:u,delegateAndWait:async(p,m)=>{let d=u+1;if(d>o)throw new T(d,o);if(!n)throw new Error(`Cannot delegate to "${p}": agent is running in standalone mode without a registry`);let y=n.getAgent(p);if(!y)throw new Error(`Agent "${p}" not found for delegation`);let g={message:m.message??"",intent:m.intent,data:m.data,context:m.context,conversationId:m.conversationId??s.conversationId??`delegation-${Date.now()}`};return await y.invokeAgent(g)},skip:j}))(0),c=async u=>{let p=u??s;return await t.invokeAgent(p)};for(let u=i.length-1;u>=0;u--){let p=i[u],m=c;c=async d=>await p(d??s,l,m)}return await c()}}}async function H(i,t){let e=await i.execute(t);return e===b?null:e}import{randomUUID as W}from"crypto";function J(i){let t=i.context??{},e=t.channelType;return e==="im"||e==="private"||e==="dm"?"dm":t.threadId!==void 0?"thread":"channel"}var P=Symbol.for("toolpack:capture-history");function B(i){let t=i.captureAgentReplies??!0,e=i.getScope??J,n=i.getMessageId??(s=>s.context?.messageId??s.context?.eventId??W()),r=i.getMentions??(s=>s.context?.mentions??[]),o=async(s,a,l)=>{let c=s.conversationId;if(!c)return a.logger?.warn("[capture-history] Message has no conversationId \u2014 skipping capture"),await l();let u=s.participant;if(u){let m={id:n(s),conversationId:c,participant:u,content:s.message??"",timestamp:new Date().toISOString(),scope:e(s),metadata:{channelType:s.context?.channelType,threadId:s.context?.threadId,messageId:s.context?.messageId,mentions:r(s),channelName:s.context?.channelName,channelId:s.context?.channelId}};try{await i.store.append(m),i.onCaptured?.(m),a.logger?.debug("[capture-history] Captured inbound message",{messageId:m.id,participantId:u.id,conversationId:c})}catch(d){a.logger?.warn("[capture-history] Failed to store inbound message",{error:d instanceof Error?d.message:String(d)})}}let p=await l();if(t&&!L(p)&&p.output!=null){let m={kind:"agent",id:a.agent.name,displayName:a.agent.name},d={id:W(),conversationId:c,participant:m,content:p.output,timestamp:new Date().toISOString(),scope:e(s),metadata:{channelType:s.context?.channelType,threadId:s.context?.threadId,channelName:s.context?.channelName,channelId:s.context?.channelId}};try{await i.store.append(d),i.onCaptured?.(d),a.logger?.debug("[capture-history] Captured agent reply",{messageId:d.id,agentId:a.agent.name,conversationId:c})}catch(y){a.logger?.warn("[capture-history] Failed to store agent reply",{error:y instanceof Error?y.message:String(y)})}}return p};return o[P]=!0,o}import{randomUUID as Y}from"crypto";function F(i){return Math.ceil(i.length/4)}function V(i){return{id:i.id,participant:i.participant,content:i.content,timestamp:i.timestamp}}function G(i,t){let{participant:e,content:n}=i;return e.kind==="system"?{role:"system",content:n}:e.kind==="agent"?e.id===t?{role:"assistant",content:n}:{role:"user",content:`${e.displayName??e.id} (agent): ${n}`}:{role:"user",content:`${e.displayName??e.id}: ${n}`}}function Q(i,t,e){return!!(i.participant.id===t||i.metadata?.mentions?.some(n=>e.has(n)))}async function q(i,t,e,n,r={},o){let{scope:s,addressedOnlyMode:a=!0,tokenBudget:l=3e3,rollingSummaryThreshold:c=40,timeWindowMinutes:u,maxTurnsToLoad:p=100,agentAliases:m}=r,d=new Set([e,...m??[]]),y=u!==void 0?new Date(Date.now()-u*60*1e3).toISOString():void 0,g=await i.get(t,{scope:s,sinceTimestamp:y,limit:p}),E=g.length;if(a){let h=new Set;for(let f=0;f<g.length;f++){let x=g[f];if(Q(x,e,d)&&h.add(x.id),f<g.length-1){let A=g[f+1];A.participant.kind==="agent"&&A.participant.id===e&&h.add(x.id)}}let v=g[g.length-1];v&&h.add(v.id),g=g.filter(f=>h.has(f.id))}let R=!1;if(g.length>c&&o){let h=Math.floor(g.length/2),v=g.slice(0,h),f=g.slice(h),x=v.filter(A=>!A.metadata?.isSummary);try{let A=await o.invokeAgent({message:"summarize",data:{turns:x.map(V),agentName:n,agentId:e,maxTokens:Math.floor(l*.25),extractDecisions:!0}}),D=JSON.parse(A.output),z={id:`summary-${Y()}`,conversationId:t,participant:{kind:"system",id:"summarizer"},content:`[Summary of ${D.turnsSummarized} earlier turns]: ${D.summary}`,timestamp:v[0].timestamp,scope:s??"channel",metadata:{isSummary:!0}};g=[z,...f],R=!0;try{await i.append(z),await i.deleteMessages(t,v.map(K=>K.id))}catch{}}catch{g=g.slice(-c)}}else g.length>c&&(g=g.slice(-c));let S=g.map(h=>G(h,e));if(S.length===0)return{messages:[],estimatedTokens:0,turnsLoaded:E,hasSummary:R};let O=S[S.length-1],$=[O],_=F(O.content);for(let h=S.length-2;h>=0;h--){let v=S[h],f=F(v.content);if(_+f>l)break;$.unshift(v),_+=f}return{messages:$,estimatedTokens:_,turnsLoaded:E,hasSummary:R}}var I=class extends Error{constructor(t){super(t),this.name="AgentError"}};var C=class extends Z{provider;model;workflow;conversationHistory;assemblerOptions;channels=[];interceptors=[];_registry;_triggeringChannel;_conversationId;_isTriggerChannel;toolpack;_initConfig;_ownedToolpack=!1;_conversationLocks=new Map;constructor(t){super(),this.conversationHistory=new ee,"toolpack"in t?this.toolpack=t.toolpack:this._initConfig=t}async _ensureToolpack(){if(!this.toolpack){if(!this._initConfig)throw new Error(`[${this.name??"agent"}] Cannot start: no apiKey or toolpack provided`);this.toolpack=await X.init({provider:this._initConfig.provider??"anthropic",apiKey:this._initConfig.apiKey,model:this._initConfig.model}),this._ownedToolpack=!0}}async start(){await this._ensureToolpack(),this.mode&&(typeof this.mode=="string"?this.toolpack.setMode(this.mode):(this.toolpack.registerMode(this.mode),this.toolpack.setMode(this.mode.name)));for(let t of this.channels)this._bindChannel(t),t.listen()}async stop(){for(let t of this.channels)"stop"in t&&typeof t.stop=="function"&&await t.stop();this._ownedToolpack&&await this.toolpack.disconnect?.()}async run(t,e,n){let r=n?.conversationId??this._conversationId;await this.onBeforeRun({message:t,conversationId:r}),this.emit("agent:start",{message:t});try{typeof this.mode=="string"?this.toolpack.setMode(this.mode):(this.toolpack.registerMode(this.mode),this.toolpack.setMode(this.mode.name));let o=[];if(r)try{let c=await q(this.conversationHistory,r,this.name,this.name,this._resolveAssemblerOptions()),u=c.messages[c.messages.length-1],p=t.trim(),d=p!==""&&u?.role==="user"&&typeof u.content=="string"&&(u.content===p||u.content.endsWith(`: ${p}`))?c.messages.slice(0,-1):c.messages;o.push(...d)}catch{}t.trim()&&o.push({role:"user",content:t});let s=[];if(r){let c=this.conversationHistory;s.push({name:"conversation_search",displayName:"Conversation Search",description:"Search past conversation history for specific information, questions, or topics mentioned earlier in this conversation.",category:"search",parameters:{type:"object",properties:{query:{type:"string",description:"Keywords or phrases to search for in conversation history."},limit:{type:"number",description:"Maximum number of results to return (default: 5)."}},required:["query"]},execute:async u=>{let p=await c.search(r,String(u.query??""),{limit:typeof u.limit=="number"?u.limit:5});return{results:p.map(m=>({role:m.participant.kind==="agent"?"assistant":"user",content:m.content,timestamp:m.timestamp})),count:p.length}}})}let a=await this.toolpack.generate({messages:o,model:this.model||"",requestTools:s.length>0?s:void 0},this.provider),l={output:a.content||"",steps:this.extractSteps(a),metadata:a.usage?{usage:a.usage}:void 0};return await this.onComplete(l),this.emit("agent:complete",l),l}catch(o){throw await this.onError(o),this.emit("agent:error",o),o}}getAgentAliases(){let t=[];for(let e of this.channels){let n=e.botUserId;n&&t.push(n)}return t}async sendTo(t,e){if(!this._registry)throw new Error("Agent not registered - _registry not set");await this._registry.sendTo(t,{output:e})}async ask(t,e){if(!this._registry)throw new I("Agent not registered - cannot use ask()");if(!this._conversationId)throw new I("No conversationId available - ask() requires a conversation channel");if(this._isTriggerChannel)throw new I("this.ask() called from a trigger channel (ScheduledChannel). Trigger channels have no human recipient \u2014 use a conversation channel (Slack, Telegram, Webhook) instead.");if(!this._triggeringChannel||this._triggeringChannel.trim()==="")throw new I("Cannot use ask() - no triggering channel available. The channel must have a name registered with AgentRegistry.");let n=this._registry.addPendingAsk({conversationId:this._conversationId,agentName:this.name,question:t,context:e?.context??{},maxRetries:e?.maxRetries??2,expiresAt:e?.expiresIn?new Date(Date.now()+e.expiresIn):void 0,channelName:this._triggeringChannel});return await this.sendTo(this._triggeringChannel,t),{output:t,metadata:{waitingForHuman:!0,askId:n.id}}}getPendingAsk(t){if(!this._registry)return null;let e=t??this._conversationId;return e?this._registry.getPendingAsk(e)??null:null}async resolvePendingAsk(t,e){if(!this._registry)throw new I("Agent not registered - cannot resolve ask");await this._registry.resolvePendingAsk(t,e)}async evaluateAnswer(t,e,n){return n?.simpleValidation?n.simpleValidation(e):(await this.run(`Evaluate if this answer sufficiently addresses the question.
2
+
3
+ Question: "${t}"
4
+ Answer: "${e}"
5
+
6
+ Is this answer sufficient? Reply with ONLY "yes" or "no".`,{workflow:{mode:"single-shot"}})).output.toLowerCase().trim().startsWith("yes")}async handlePendingAsk(t,e,n,r){return await this.evaluateAnswer(t.question,e,{simpleValidation:s=>s.trim().length>3})?(await this.resolvePendingAsk(t.id,e),n(e)):t.retries>=t.maxRetries?(await this.resolvePendingAsk(t.id,"__insufficient__"),this._triggeringChannel&&await this.sendTo(this._triggeringChannel,"I was unable to get enough information to proceed. Skipping this step."),r?r():{output:"Step skipped due to insufficient input.",metadata:{skipped:!0,askId:t.id}}):(this._registry?.incrementRetries(t.id),this.ask(`I need a bit more clarity on: "${t.question}". Could you provide more details?`,{context:t.context,maxRetries:t.maxRetries}))}async delegate(t,e){if(!this._registry)throw new I("Agent not registered - cannot use delegate()");let n={message:e.message,intent:e.intent,data:e.data,context:{...e.context||{},delegatedBy:this.name},conversationId:e.conversationId||this._conversationId||`delegation-${Date.now()}`};this._registry.invoke(t,n).catch(r=>{console.error(`[${this.name}] Delegation to ${t} failed:`,r.message)})}async delegateAndWait(t,e){if(!this._registry)throw new I("Agent not registered - cannot use delegateAndWait()");let n={message:e.message,intent:e.intent,data:e.data,context:{...e.context||{},delegatedBy:this.name},conversationId:e.conversationId||this._conversationId||`delegation-${Date.now()}`};return await this._registry.invoke(t,n)}async onBeforeRun(t){}async onStepComplete(t){}async onComplete(t){}async onError(t){}_resolveAssemblerOptions(){let t=this.channels.map(r=>r.botUserId).filter(r=>typeof r=="string"&&r.length>0),e=this.assemblerOptions?.agentAliases??[];if(t.length===0&&e.length===0)return this.assemblerOptions;let n=Array.from(new Set([...e,...t]));return{...this.assemblerOptions,agentAliases:n}}_getEffectiveInterceptors(){return this.interceptors.some(e=>e[P]===!0)?this.interceptors:[B({store:this.conversationHistory}),...this.interceptors]}_bindChannel(t){t.onMessage(async e=>{if(!e.conversationId){console.warn(`[${this.name}] Message received without conversationId \u2014 skipping`);return}let n=await this._acquireConversationLock(e.conversationId),r=()=>{};try{this._triggeringChannel=t.name,this._isTriggerChannel=t.isTriggerChannel,this._conversationId=e.conversationId,r=this._attachWorkflowStepUpdates(t,e);let o=U(this._getEffectiveInterceptors(),this,t,this._registry??null),s=await H(o,e);if(s===null)return;let a={output:s.output,metadata:s.metadata};await t.send({output:a.output,metadata:{...a.metadata,conversationId:e.conversationId,...e.context}})}catch(o){let s=o instanceof Error?o.message:"Unknown error occurred";console.error(`[${this.name}] Error in agent invocation: ${s}`);try{await t.send({output:`Error: ${s}`,metadata:{conversationId:e.conversationId,error:!0,...e.context}})}catch(a){console.error(`[${this.name}] Failed to send error to channel: ${a}`)}}finally{r(),n()}})}_attachWorkflowStepUpdates(t,e){if(t.isTriggerChannel)return()=>{};let n=new Set,r=new Set,o=a=>{a?.id&&n.add(String(a.id))},s=(a,l)=>{if(!l?.id||!n.has(String(l.id))||!a?.result?.output||typeof a.result.output!="string"||l?.steps?.length&&Number(l.steps.length)<=1)return;let c=`${String(l.id)}:${String(a.id??a.number??"unknown")}`;if(r.has(c))return;r.add(c);let u=a.result.output.trim();if(!u)return;let p=u.length>3500?`${u.slice(0,3500)}
7
+ ... [truncated]`:u,m=`Step ${a.number}: ${a.description||"Completed"}`;t.send({output:`${m}
8
+
9
+ ${p}`,metadata:{conversationId:e.conversationId,...e.context}}).catch(d=>{let y=d instanceof Error?d.message:String(d);console.warn(`[${this.name}] Failed to send workflow step update: ${y}`)})};return this.toolpack.on("workflow:plan_created",o),this.toolpack.on("workflow:step_complete",s),()=>{this.toolpack.off("workflow:plan_created",o),this.toolpack.off("workflow:step_complete",s)}}async _acquireConversationLock(t){for(;this._conversationLocks.has(t);)try{await this._conversationLocks.get(t)}catch{}let e,n=new Promise(r=>{e=r});return this._conversationLocks.set(t,n),()=>{this._conversationLocks.delete(t),e()}}extractSteps(t){let e=t;if(e.plan&&typeof e.plan=="object"){let n=e.plan;if(Array.isArray(n.steps))return n.steps.map(r=>({number:r.number||0,description:r.description||"",status:r.status||"completed",result:r.result}))}if(Array.isArray(e.steps))return e.steps}};import{CHAT_MODE as te}from"toolpack-sdk";var ne={...te,name:"intent-classifier-mode",systemPrompt:["You classify whether a message is asking an agent to respond.","","Categories:","direct = Message uses @mention, name in greeting, possessive, or commands the agent to act","indirect = Agent is mentioned but unclear if response wanted (talking ABOUT, not TO them)","passive = No addressing detected; agent should only listen, not reply","ignore = Definitely not for this agent (noise, code blocks, other bots)","","Response must start with one of: direct, indirect, passive, ignore"].join(`
10
+ `)},M=class extends C{name="intent-classifier";description="Classifies whether a message is directly addressing an agent for response";mode=ne;constructor(t){super(t)}async invokeAgent(t){let e=t.data;if(e?.isDirectMessage)return{output:"direct",metadata:{classification:"direct",shortCircuit:"dm"}};if(!e?.message)return{output:"ignore",metadata:{error:"No message provided for classification"}};let n=[];if(n.push(`Context: Public channel #${e.channelName}`),n.push(`Target agent: "${e.agentName}" (ID: ${e.agentId})`),n.push(`Message sender: ${e.senderName}`),e.recentContext&&e.recentContext.length>0){n.push(`
11
+ Recent conversation:`);for(let a of e.recentContext)n.push(` ${a.sender}: ${a.content.substring(0,100)}`)}n.push(`
12
+ Message to classify: "${e.message}"`),e.includeExamples&&(n.push(`
13
+ Examples of classifications:`),n.push(` "@${e.agentName} help me" \u2192 direct`),n.push(` "Can someone ask ${e.agentName} about this?" \u2192 indirect`),n.push(` "I was talking to ${e.agentName} earlier" \u2192 passive`),n.push(' "Check the logs" \u2192 ignore')),n.push(`
14
+ Classification (start with direct, indirect, passive, or ignore):`);let r=n.join(`
15
+ `),o=await this.run(r),s=this.normalizeClassification(o.output);return{output:s,metadata:{rawOutput:o.output,classification:s,confidence:"high"}}}normalizeClassification(t){let e=t.toLowerCase().trim().split(/\s+/)[0],n=t.toLowerCase();return["direct","indirect","passive","ignore"].includes(e)?e:n.includes("indirect")||n.includes("mention")?"indirect":n.includes("passive")||n.includes("listen")?"passive":n.includes("ignore")||n.includes("skip")?"ignore":n.includes("direct")||n.includes("addressed")?"direct":"ignore"}};import{CHAT_MODE as se}from"toolpack-sdk";var re={...se,name:"summarizer-mode",systemPrompt:["You are a conversation summarizer for multi-participant chat histories.","Your job is to compress older conversation turns into a dense summary that preserves:","","1. Key facts and information shared","2. Decisions made or action items assigned","3. Context relevant to the target agent's perspective","4. Important questions asked or problems raised","","Summarize from the perspective of the target agent.","If the agent was not addressed in a turn, note it as observed context.","Use bullet points for clarity. Be concise but complete.","","Output format: Return ONLY a JSON object with these fields:","- summary: string (the summary text)","- turnsSummarized: number (count of turns processed)","- hasDecisions: boolean (whether any decisions/action items were found)","- estimatedTokens: number (rough estimate: characters / 4)","","Do not include markdown code blocks, just the raw JSON."].join(`
16
+ `)},N=class extends C{name="summarizer";description="Compresses conversation history into compact summaries for prompt assembly";mode=re;constructor(t){super(t)}async invokeAgent(t){let e=t.data;if(!e?.turns||e.turns.length===0)return{output:JSON.stringify({summary:"(No history to summarize)",turnsSummarized:0,hasDecisions:!1,estimatedTokens:5}),metadata:{emptyInput:!0}};let n=e.maxTokens??800,r=e.extractDecisions??!0,o=[`Target agent: "${e.agentName}" (ID: ${e.agentId})`,`Maximum summary length: ~${n} tokens`,`Extract decisions/action items: ${r?"yes":"no"}`,"",`Conversation turns to summarize (${e.turns.length} turns):`,""];for(let c of e.turns){let u=new Date(c.timestamp).toLocaleString("en-US",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}),p=c.participant.displayName??c.participant.id,m=c.participant.kind==="agent"?`[BOT] ${p}`:p,d=`[${u}] ${m}: ${c.content.substring(0,200)}`;c.content.length>200&&(d+="..."),c.metadata?.isToolCall&&c.metadata.toolName&&(d+=` [tool: ${c.metadata.toolName}]`),o.push(d)}o.push("","Generate a JSON summary object:");let s=o.join(`
17
+ `),a=await this.run(s),l=this.parseSummarizerOutput(a.output,e.turns.length);return{output:JSON.stringify(l),metadata:{turnsProcessed:e.turns.length,rawOutputLength:a.output.length}}}parseSummarizerOutput(t,e){let n=t.trim(),r=n.match(/```(?:json)?\s*([\s\S]*?)\s*```/);r&&(n=r[1].trim());try{let o=JSON.parse(n);return{summary:typeof o.summary=="string"&&o.summary.length>0?o.summary:this.generateFallbackSummary(e),turnsSummarized:typeof o.turnsSummarized=="number"?o.turnsSummarized:e,hasDecisions:typeof o.hasDecisions=="boolean"?o.hasDecisions:!1,estimatedTokens:typeof o.estimatedTokens=="number"&&o.estimatedTokens>0?o.estimatedTokens:Math.ceil(t.length/4)}}catch{return{summary:this.generateFallbackSummary(e),turnsSummarized:e,hasDecisions:t.toLowerCase().includes("decision")||t.toLowerCase().includes("action"),estimatedTokens:Math.ceil(t.length/4)}}}generateFallbackSummary(t){return`(Summary of ${t} conversation turns - key details preserved in full context)`}};export{M as IntentClassifierAgent,N as SummarizerAgent};