opc-agent 1.4.0 → 2.0.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.
Files changed (198) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +91 -32
  3. package/dist/channels/email.d.ts +32 -26
  4. package/dist/channels/email.js +239 -62
  5. package/dist/channels/feishu.d.ts +21 -6
  6. package/dist/channels/feishu.js +225 -126
  7. package/dist/channels/telegram.d.ts +30 -9
  8. package/dist/channels/telegram.js +125 -33
  9. package/dist/channels/websocket.d.ts +46 -3
  10. package/dist/channels/websocket.js +306 -37
  11. package/dist/channels/wechat.d.ts +33 -13
  12. package/dist/channels/wechat.js +229 -42
  13. package/dist/cli.js +1127 -19
  14. package/dist/core/a2a.d.ts +17 -0
  15. package/dist/core/a2a.js +43 -1
  16. package/dist/core/agent.d.ts +39 -0
  17. package/dist/core/agent.js +228 -3
  18. package/dist/core/runtime.d.ts +7 -0
  19. package/dist/core/runtime.js +205 -2
  20. package/dist/core/sandbox.d.ts +26 -0
  21. package/dist/core/sandbox.js +117 -0
  22. package/dist/core/scheduler.d.ts +52 -0
  23. package/dist/core/scheduler.js +168 -0
  24. package/dist/core/subagent.d.ts +28 -0
  25. package/dist/core/subagent.js +65 -0
  26. package/dist/core/workflow-graph.d.ts +93 -0
  27. package/dist/core/workflow-graph.js +247 -0
  28. package/dist/daemon.d.ts +3 -0
  29. package/dist/daemon.js +134 -0
  30. package/dist/doctor.d.ts +15 -0
  31. package/dist/doctor.js +183 -0
  32. package/dist/eval/index.d.ts +65 -0
  33. package/dist/eval/index.js +191 -0
  34. package/dist/index.d.ts +37 -6
  35. package/dist/index.js +75 -3
  36. package/dist/plugins/content-filter.d.ts +7 -0
  37. package/dist/plugins/content-filter.js +25 -0
  38. package/dist/plugins/index.d.ts +42 -0
  39. package/dist/plugins/index.js +108 -2
  40. package/dist/plugins/logger.d.ts +6 -0
  41. package/dist/plugins/logger.js +20 -0
  42. package/dist/plugins/rate-limiter.d.ts +7 -0
  43. package/dist/plugins/rate-limiter.js +35 -0
  44. package/dist/protocols/a2a/client.d.ts +25 -0
  45. package/dist/protocols/a2a/client.js +115 -0
  46. package/dist/protocols/a2a/index.d.ts +6 -0
  47. package/dist/protocols/a2a/index.js +12 -0
  48. package/dist/protocols/a2a/server.d.ts +41 -0
  49. package/dist/protocols/a2a/server.js +295 -0
  50. package/dist/protocols/a2a/types.d.ts +91 -0
  51. package/dist/protocols/a2a/types.js +15 -0
  52. package/dist/protocols/a2a/utils.d.ts +6 -0
  53. package/dist/protocols/a2a/utils.js +47 -0
  54. package/dist/protocols/agui/client.d.ts +10 -0
  55. package/dist/protocols/agui/client.js +75 -0
  56. package/dist/protocols/agui/index.d.ts +4 -0
  57. package/dist/protocols/agui/index.js +25 -0
  58. package/dist/protocols/agui/server.d.ts +37 -0
  59. package/dist/protocols/agui/server.js +191 -0
  60. package/dist/protocols/agui/types.d.ts +107 -0
  61. package/dist/protocols/agui/types.js +17 -0
  62. package/dist/protocols/index.d.ts +2 -0
  63. package/dist/protocols/index.js +19 -0
  64. package/dist/protocols/mcp/agent-tools.d.ts +11 -0
  65. package/dist/protocols/mcp/agent-tools.js +129 -0
  66. package/dist/protocols/mcp/index.d.ts +5 -0
  67. package/dist/protocols/mcp/index.js +11 -0
  68. package/dist/protocols/mcp/server.d.ts +31 -0
  69. package/dist/protocols/mcp/server.js +248 -0
  70. package/dist/protocols/mcp/types.d.ts +92 -0
  71. package/dist/protocols/mcp/types.js +17 -0
  72. package/dist/providers/index.d.ts +5 -1
  73. package/dist/providers/index.js +16 -9
  74. package/dist/publish/index.d.ts +45 -0
  75. package/dist/publish/index.js +350 -0
  76. package/dist/schema/oad.d.ts +859 -67
  77. package/dist/schema/oad.js +47 -3
  78. package/dist/security/approval.d.ts +36 -0
  79. package/dist/security/approval.js +113 -0
  80. package/dist/security/index.d.ts +4 -0
  81. package/dist/security/index.js +8 -0
  82. package/dist/security/keys.d.ts +16 -0
  83. package/dist/security/keys.js +117 -0
  84. package/dist/skills/auto-learn.d.ts +28 -0
  85. package/dist/skills/auto-learn.js +257 -0
  86. package/dist/studio/server.d.ts +63 -0
  87. package/dist/studio/server.js +625 -0
  88. package/dist/studio-ui/index.html +662 -0
  89. package/dist/telemetry/index.d.ts +93 -0
  90. package/dist/telemetry/index.js +285 -0
  91. package/dist/tools/builtin/datetime.d.ts +3 -0
  92. package/dist/tools/builtin/datetime.js +44 -0
  93. package/dist/tools/builtin/file.d.ts +3 -0
  94. package/dist/tools/builtin/file.js +151 -0
  95. package/dist/tools/builtin/index.d.ts +15 -0
  96. package/dist/tools/builtin/index.js +30 -0
  97. package/dist/tools/builtin/shell.d.ts +3 -0
  98. package/dist/tools/builtin/shell.js +43 -0
  99. package/dist/tools/builtin/web.d.ts +3 -0
  100. package/dist/tools/builtin/web.js +37 -0
  101. package/dist/tools/mcp-client.d.ts +24 -0
  102. package/dist/tools/mcp-client.js +119 -0
  103. package/package.json +5 -3
  104. package/scripts/install.ps1 +31 -0
  105. package/scripts/install.sh +40 -0
  106. package/src/channels/email.ts +351 -177
  107. package/src/channels/feishu.ts +349 -236
  108. package/src/channels/telegram.ts +212 -90
  109. package/src/channels/websocket.ts +399 -87
  110. package/src/channels/wechat.ts +329 -149
  111. package/src/cli.ts +1201 -20
  112. package/src/core/a2a.ts +60 -0
  113. package/src/core/agent.ts +420 -152
  114. package/src/core/runtime.ts +174 -0
  115. package/src/core/sandbox.ts +143 -0
  116. package/src/core/scheduler.ts +187 -0
  117. package/src/core/subagent.ts +98 -0
  118. package/src/core/workflow-graph.ts +365 -0
  119. package/src/daemon.ts +96 -0
  120. package/src/doctor.ts +156 -0
  121. package/src/eval/index.ts +211 -0
  122. package/src/eval/suites/basic.json +16 -0
  123. package/src/eval/suites/memory.json +12 -0
  124. package/src/eval/suites/safety.json +14 -0
  125. package/src/index.ts +65 -6
  126. package/src/plugins/content-filter.ts +23 -0
  127. package/src/plugins/index.ts +133 -2
  128. package/src/plugins/logger.ts +18 -0
  129. package/src/plugins/rate-limiter.ts +38 -0
  130. package/src/protocols/a2a/client.ts +132 -0
  131. package/src/protocols/a2a/index.ts +8 -0
  132. package/src/protocols/a2a/server.ts +333 -0
  133. package/src/protocols/a2a/types.ts +88 -0
  134. package/src/protocols/a2a/utils.ts +50 -0
  135. package/src/protocols/agui/client.ts +83 -0
  136. package/src/protocols/agui/index.ts +4 -0
  137. package/src/protocols/agui/server.ts +218 -0
  138. package/src/protocols/agui/types.ts +153 -0
  139. package/src/protocols/index.ts +2 -0
  140. package/src/protocols/mcp/agent-tools.ts +134 -0
  141. package/src/protocols/mcp/index.ts +8 -0
  142. package/src/protocols/mcp/server.ts +262 -0
  143. package/src/protocols/mcp/types.ts +69 -0
  144. package/src/providers/index.ts +354 -339
  145. package/src/publish/index.ts +376 -0
  146. package/src/schema/oad.ts +204 -154
  147. package/src/security/approval.ts +131 -0
  148. package/src/security/index.ts +3 -0
  149. package/src/security/keys.ts +87 -0
  150. package/src/skills/auto-learn.ts +262 -0
  151. package/src/studio/server.ts +629 -0
  152. package/src/studio-ui/index.html +662 -0
  153. package/src/telemetry/index.ts +324 -0
  154. package/src/tools/builtin/datetime.ts +41 -0
  155. package/src/tools/builtin/file.ts +107 -0
  156. package/src/tools/builtin/index.ts +28 -0
  157. package/src/tools/builtin/shell.ts +43 -0
  158. package/src/tools/builtin/web.ts +35 -0
  159. package/src/tools/mcp-client.ts +131 -0
  160. package/src/types/agent-workstation.d.ts +2 -0
  161. package/tests/a2a-protocol.test.ts +285 -0
  162. package/tests/agui-protocol.test.ts +246 -0
  163. package/tests/auto-learn.test.ts +105 -0
  164. package/tests/builtin-tools.test.ts +83 -0
  165. package/tests/channels/discord.test.ts +79 -0
  166. package/tests/channels/email.test.ts +148 -0
  167. package/tests/channels/feishu.test.ts +123 -0
  168. package/tests/channels/telegram.test.ts +129 -0
  169. package/tests/channels/websocket.test.ts +53 -0
  170. package/tests/channels/wechat.test.ts +170 -0
  171. package/tests/chat-cli.test.ts +160 -0
  172. package/tests/cli.test.ts +46 -0
  173. package/tests/daemon.test.ts +135 -0
  174. package/tests/deepbrain-wire.test.ts +234 -0
  175. package/tests/doctor.test.ts +38 -0
  176. package/tests/eval.test.ts +173 -0
  177. package/tests/init-role.test.ts +124 -0
  178. package/tests/mcp-client.test.ts +92 -0
  179. package/tests/mcp-server.test.ts +178 -0
  180. package/tests/plugin-a2a-enhanced.test.ts +230 -0
  181. package/tests/publish.test.ts +231 -0
  182. package/tests/scheduler.test.ts +200 -0
  183. package/tests/security-enhanced.test.ts +233 -0
  184. package/tests/skill-learner.test.ts +161 -0
  185. package/tests/studio.test.ts +229 -0
  186. package/tests/subagent.test.ts +193 -0
  187. package/tests/telegram-discord.test.ts +60 -0
  188. package/tests/telemetry.test.ts +186 -0
  189. package/tests/tools/builtin-extended.test.ts +138 -0
  190. package/tests/workflow-graph.test.ts +279 -0
  191. package/tutorial/customer-service-agent/README.md +612 -0
  192. package/tutorial/customer-service-agent/SOUL.md +26 -0
  193. package/tutorial/customer-service-agent/agent.yaml +63 -0
  194. package/tutorial/customer-service-agent/package.json +19 -0
  195. package/tutorial/customer-service-agent/src/index.ts +69 -0
  196. package/tutorial/customer-service-agent/src/skills/faq.ts +27 -0
  197. package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -0
  198. package/tutorial/customer-service-agent/tsconfig.json +14 -0
package/src/core/a2a.ts CHANGED
@@ -3,6 +3,66 @@ import { Room } from './room';
3
3
  import type { Message, IAgent } from './types';
4
4
  import { Logger } from './logger';
5
5
 
6
+ // ── Agent Card (v1.6.0 — simplified A2A) ────────────────────
7
+
8
+ export interface AgentCard {
9
+ name: string;
10
+ description: string;
11
+ capabilities: string[];
12
+ endpoint?: string;
13
+ handler?: (message: string) => Promise<string>;
14
+ }
15
+
16
+ export class AgentCardRegistry {
17
+ private cards: Map<string, AgentCard> = new Map();
18
+ private logger = new Logger('a2a:cards');
19
+
20
+ register(card: AgentCard): void {
21
+ this.cards.set(card.name, card);
22
+ this.logger.info('AgentCard registered', { name: card.name });
23
+ }
24
+
25
+ unregister(name: string): void {
26
+ this.cards.delete(name);
27
+ }
28
+
29
+ get(name: string): AgentCard | undefined {
30
+ return this.cards.get(name);
31
+ }
32
+
33
+ find(query: string): AgentCard[] {
34
+ const lower = query.toLowerCase();
35
+ return Array.from(this.cards.values()).filter(a =>
36
+ a.name.toLowerCase().includes(lower) ||
37
+ a.description.toLowerCase().includes(lower) ||
38
+ a.capabilities.some(c => c.toLowerCase().includes(lower))
39
+ );
40
+ }
41
+
42
+ async send(agentName: string, message: string): Promise<string> {
43
+ const agent = this.cards.get(agentName);
44
+ if (!agent) throw new Error(`Agent '${agentName}' not found`);
45
+
46
+ if (agent.handler) {
47
+ return agent.handler(message);
48
+ } else if (agent.endpoint) {
49
+ const res = await fetch(agent.endpoint, {
50
+ method: 'POST',
51
+ headers: { 'Content-Type': 'application/json' },
52
+ body: JSON.stringify({ message }),
53
+ });
54
+ const data = await res.json() as any;
55
+ return data.response || data.content || '';
56
+ }
57
+
58
+ throw new Error(`Agent '${agentName}' has no handler or endpoint`);
59
+ }
60
+
61
+ list(): AgentCard[] {
62
+ return Array.from(this.cards.values());
63
+ }
64
+ }
65
+
6
66
  // ── A2A Types ───────────────────────────────────────────────
7
67
 
8
68
  export interface AgentCapability {
package/src/core/agent.ts CHANGED
@@ -1,152 +1,420 @@
1
- import { EventEmitter } from 'events';
2
- import type { AgentState, IAgent, IChannel, ISkill, Message, MemoryStore, AgentContext } from './types';
3
- import { InMemoryStore } from '../memory';
4
- import { createProvider, type LLMProvider } from '../providers';
5
-
6
- export class BaseAgent extends EventEmitter implements IAgent {
7
- readonly name: string;
8
- private _state: AgentState = 'init';
9
- private skills: Map<string, ISkill> = new Map();
10
- private channels: IChannel[] = [];
11
- private memory: MemoryStore;
12
- private _provider: LLMProvider;
13
- private systemPrompt: string;
14
- private historyLimit: number;
15
-
16
- constructor(options: {
17
- name: string;
18
- systemPrompt?: string;
19
- provider?: string;
20
- model?: string;
21
- memory?: MemoryStore;
22
- historyLimit?: number;
23
- }) {
24
- super();
25
- this.name = options.name;
26
- this.systemPrompt = options.systemPrompt ?? 'You are a helpful AI agent.';
27
- this.memory = options.memory ?? new InMemoryStore();
28
- this._provider = createProvider(options.provider ?? 'openai', options.model);
29
- this.historyLimit = options.historyLimit ?? 50;
30
- }
31
-
32
- get state(): AgentState {
33
- return this._state;
34
- }
35
-
36
- get provider(): LLMProvider {
37
- return this._provider;
38
- }
39
-
40
- getSystemPrompt(): string {
41
- return this.systemPrompt;
42
- }
43
-
44
- getMemory(): MemoryStore {
45
- return this.memory;
46
- }
47
-
48
- private transition(to: AgentState): void {
49
- const from = this._state;
50
- this._state = to;
51
- this.emit('state:change', from, to);
52
- }
53
-
54
- async init(): Promise<void> {
55
- this.transition('ready');
56
- }
57
-
58
- async start(): Promise<void> {
59
- if (this._state !== 'ready') {
60
- throw new Error(`Cannot start agent in state: ${this._state}`);
61
- }
62
- for (const channel of this.channels) {
63
- channel.onMessage((msg) => this.handleMessage(msg));
64
- await channel.start();
65
- }
66
- this.transition('running');
67
- }
68
-
69
- async stop(): Promise<void> {
70
- for (const channel of this.channels) {
71
- await channel.stop();
72
- }
73
- this.transition('stopped');
74
- }
75
-
76
- registerSkill(skill: ISkill): void {
77
- this.skills.set(skill.name, skill);
78
- }
79
-
80
- bindChannel(channel: IChannel): void {
81
- this.channels.push(channel);
82
- }
83
-
84
- getChannels(): IChannel[] {
85
- return this.channels;
86
- }
87
-
88
- async handleMessage(message: Message): Promise<Message> {
89
- this.emit('message:in', message);
90
-
91
- const sessionId = (message.metadata?.sessionId as string) ?? 'default';
92
- await this.memory.addMessage(sessionId, message);
93
-
94
- const context: AgentContext = {
95
- agentName: this.name,
96
- sessionId,
97
- messages: (await this.memory.getConversation(sessionId)).slice(-this.historyLimit),
98
- memory: this.memory,
99
- metadata: {},
100
- };
101
-
102
- // Try skills first
103
- for (const [name, skill] of this.skills) {
104
- try {
105
- const result = await skill.execute(context, message);
106
- this.emit('skill:execute', name, result);
107
- if (result.handled && result.response) {
108
- const response = this.createResponse(result.response, message);
109
- await this.memory.addMessage(sessionId, response);
110
- this.emit('message:out', response);
111
- return response;
112
- }
113
- } catch (err) {
114
- this.emit('error', err instanceof Error ? err : new Error(String(err)));
115
- }
116
- }
117
-
118
- // Fall back to LLM
119
- const llmResponse = await this._provider.chat(context.messages, this.systemPrompt);
120
- const response = this.createResponse(llmResponse, message);
121
- await this.memory.addMessage(sessionId, response);
122
- this.emit('message:out', response);
123
- return response;
124
- }
125
-
126
- async *handleMessageStream(message: Message): AsyncIterable<string> {
127
- const sessionId = (message.metadata?.sessionId as string) ?? 'default';
128
- await this.memory.addMessage(sessionId, message);
129
-
130
- const history = (await this.memory.getConversation(sessionId)).slice(-this.historyLimit);
131
-
132
- let fullResponse = '';
133
- for await (const chunk of this._provider.chatStream(history, this.systemPrompt)) {
134
- fullResponse += chunk;
135
- yield chunk;
136
- }
137
-
138
- const response = this.createResponse(fullResponse, message);
139
- await this.memory.addMessage(sessionId, response);
140
- this.emit('message:out', response);
141
- }
142
-
143
- private createResponse(content: string, inReplyTo: Message): Message {
144
- return {
145
- id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
146
- role: 'assistant',
147
- content,
148
- timestamp: Date.now(),
149
- metadata: { inReplyTo: inReplyTo.id },
150
- };
151
- }
152
- }
1
+ import { EventEmitter } from 'events';
2
+ import type { AgentState, IAgent, IChannel, ISkill, Message, MemoryStore, AgentContext } from './types';
3
+ import { InMemoryStore } from '../memory';
4
+ import { createProvider, type LLMProvider } from '../providers';
5
+ import { SkillLearner } from '../skills/auto-learn';
6
+ import type { MCPTool } from '../tools/mcp';
7
+ import { MCPToolRegistry } from '../tools/mcp';
8
+ import { SubAgentManager, type SubAgentConfig, type SubAgentResult } from './subagent';
9
+ import { Tracer } from '../telemetry';
10
+ import type { Span as TelemetrySpan } from '../telemetry';
11
+
12
+ export class BaseAgent extends EventEmitter implements IAgent {
13
+ readonly name: string;
14
+ private _state: AgentState = 'init';
15
+ private skills: Map<string, ISkill> = new Map();
16
+ private channels: IChannel[] = [];
17
+ private memory: MemoryStore;
18
+ private _provider: LLMProvider;
19
+ private systemPrompt: string;
20
+ private historyLimit: number;
21
+ private toolRegistry: MCPToolRegistry = new MCPToolRegistry();
22
+ private maxToolRounds: number;
23
+ private skillLearner?: SkillLearner;
24
+ private autoLearnConfig: { enabled: boolean; minConversationLength: number; improveOnUse: boolean };
25
+ private _subAgentManager?: SubAgentManager;
26
+ private longTermMemory?: any;
27
+ private longTermMemoryConfig: { autoLearn: boolean; autoRecall: boolean } = { autoLearn: true, autoRecall: true };
28
+ private tracer?: Tracer;
29
+
30
+ constructor(options: {
31
+ name: string;
32
+ systemPrompt?: string;
33
+ provider?: string;
34
+ model?: string;
35
+ memory?: MemoryStore;
36
+ historyLimit?: number;
37
+ skillsDir?: string;
38
+ learning?: {
39
+ autoSkillCreation?: boolean;
40
+ minConversationLength?: number;
41
+ improveOnUse?: boolean;
42
+ };
43
+ maxToolRounds?: number;
44
+ tracer?: Tracer;
45
+ }) {
46
+ super();
47
+ this.name = options.name;
48
+ this.systemPrompt = options.systemPrompt ?? 'You are a helpful AI agent.';
49
+ this.memory = options.memory ?? new InMemoryStore();
50
+ this._provider = createProvider(options.provider ?? 'openai', options.model);
51
+ this.historyLimit = options.historyLimit ?? 50;
52
+ this.maxToolRounds = options.maxToolRounds ?? 10;
53
+ this.autoLearnConfig = {
54
+ enabled: options.learning?.autoSkillCreation !== false,
55
+ minConversationLength: options.learning?.minConversationLength ?? 3,
56
+ improveOnUse: options.learning?.improveOnUse !== false,
57
+ };
58
+ if (options.skillsDir) {
59
+ this.skillLearner = new SkillLearner(options.skillsDir);
60
+ }
61
+ this.tracer = options.tracer;
62
+ }
63
+
64
+ setLongTermMemory(brain: any, config?: { autoLearn?: boolean; autoRecall?: boolean }): void {
65
+ this.longTermMemory = brain;
66
+ if (config) {
67
+ this.longTermMemoryConfig = {
68
+ autoLearn: config.autoLearn !== false,
69
+ autoRecall: config.autoRecall !== false,
70
+ };
71
+ }
72
+ }
73
+
74
+ getLongTermMemory(): any {
75
+ return this.longTermMemory;
76
+ }
77
+
78
+ getLongTermMemoryConfig(): { autoLearn: boolean; autoRecall: boolean } {
79
+ return this.longTermMemoryConfig;
80
+ }
81
+
82
+ get state(): AgentState {
83
+ return this._state;
84
+ }
85
+
86
+ get provider(): LLMProvider {
87
+ return this._provider;
88
+ }
89
+
90
+ getSystemPrompt(): string {
91
+ return this.systemPrompt;
92
+ }
93
+
94
+ getMemory(): MemoryStore {
95
+ return this.memory;
96
+ }
97
+
98
+ getSkillLearner(): SkillLearner | undefined {
99
+ return this.skillLearner;
100
+ }
101
+
102
+ getToolRegistry(): MCPToolRegistry {
103
+ return this.toolRegistry;
104
+ }
105
+
106
+ getTracer(): Tracer | undefined {
107
+ return this.tracer;
108
+ }
109
+
110
+ setTracer(tracer: Tracer): void {
111
+ this.tracer = tracer;
112
+ }
113
+
114
+ registerTool(tool: MCPTool): void {
115
+ this.toolRegistry.register(tool);
116
+ }
117
+
118
+ private transition(to: AgentState): void {
119
+ const from = this._state;
120
+ this._state = to;
121
+ this.emit('state:change', from, to);
122
+ }
123
+
124
+ async init(): Promise<void> {
125
+ if (this.skillLearner) {
126
+ await this.skillLearner.loadLearnedSkills();
127
+ }
128
+ this.transition('ready');
129
+ }
130
+
131
+ async start(): Promise<void> {
132
+ if (this._state !== 'ready') {
133
+ throw new Error(`Cannot start agent in state: ${this._state}`);
134
+ }
135
+ for (const channel of this.channels) {
136
+ channel.onMessage((msg) => this.handleMessage(msg));
137
+ await channel.start();
138
+ }
139
+ this.transition('running');
140
+ }
141
+
142
+ async stop(): Promise<void> {
143
+ for (const channel of this.channels) {
144
+ await channel.stop();
145
+ }
146
+ this.transition('stopped');
147
+ }
148
+
149
+ registerSkill(skill: ISkill): void {
150
+ this.skills.set(skill.name, skill);
151
+ }
152
+
153
+ bindChannel(channel: IChannel): void {
154
+ this.channels.push(channel);
155
+ }
156
+
157
+ getChannels(): IChannel[] {
158
+ return this.channels;
159
+ }
160
+
161
+ private getSubAgentManager(): SubAgentManager {
162
+ if (!this._subAgentManager) {
163
+ this._subAgentManager = new SubAgentManager();
164
+ }
165
+ return this._subAgentManager;
166
+ }
167
+
168
+ async spawnSubAgent(config: SubAgentConfig): Promise<SubAgentResult> {
169
+ return this.getSubAgentManager().spawn(config, this._provider);
170
+ }
171
+
172
+ async spawnParallel(configs: SubAgentConfig[]): Promise<SubAgentResult[]> {
173
+ return this.getSubAgentManager().spawnParallel(configs, this._provider);
174
+ }
175
+
176
+ async handleMessage(message: Message): Promise<Message> {
177
+ this.emit('message:in', message);
178
+
179
+ // Start root span if tracer is configured
180
+ let rootSpan: TelemetrySpan | undefined;
181
+ if (this.tracer) {
182
+ rootSpan = this.tracer.startSpan('handleMessage', {
183
+ kind: 'server',
184
+ attributes: {
185
+ 'message.channel': (message.metadata?.channel as string) || 'unknown',
186
+ 'message.sender': (message.metadata?.sender as string) || 'unknown',
187
+ 'message.length': message.content.length,
188
+ },
189
+ });
190
+ this.tracer.increment('agent.messages.total', 1, { agent: this.name });
191
+ }
192
+
193
+ const sessionId = (message.metadata?.sessionId as string) ?? 'default';
194
+ await this.memory.addMessage(sessionId, message);
195
+
196
+ // === Recall from long-term memory ===
197
+ let memoryContext = '';
198
+ if (this.longTermMemory && this.longTermMemoryConfig.autoRecall) {
199
+ let memorySpan: TelemetrySpan | undefined;
200
+ if (this.tracer && rootSpan) {
201
+ memorySpan = this.tracer.startSpan('memory.recall', { parent: rootSpan, kind: 'client' });
202
+ }
203
+ try {
204
+ const recalled = await this.longTermMemory.recall(message.content);
205
+ if (recalled && (Array.isArray(recalled) ? recalled.length > 0 : true)) {
206
+ memoryContext = '\n\n[Relevant memories]\n' +
207
+ (Array.isArray(recalled)
208
+ ? recalled.map((r: any) => typeof r === 'string' ? r : r.content || r.compiled_truth || '').join('\n')
209
+ : String(recalled));
210
+ }
211
+ if (this.tracer && memorySpan) this.tracer.endSpan(memorySpan, 'ok');
212
+ } catch {
213
+ if (this.tracer && memorySpan) this.tracer.endSpan(memorySpan, 'error');
214
+ // Silent fail — don't break chat if memory fails
215
+ }
216
+ }
217
+
218
+ const context: AgentContext = {
219
+ agentName: this.name,
220
+ sessionId,
221
+ messages: (await this.memory.getConversation(sessionId)).slice(-this.historyLimit),
222
+ memory: this.memory,
223
+ metadata: {},
224
+ };
225
+
226
+ // Try skills first
227
+ for (const [name, skill] of this.skills) {
228
+ try {
229
+ const result = await skill.execute(context, message);
230
+ this.emit('skill:execute', name, result);
231
+ if (result.handled && result.response) {
232
+ const response = this.createResponse(result.response, message);
233
+ await this.memory.addMessage(sessionId, response);
234
+ this.emit('message:out', response);
235
+ return response;
236
+ }
237
+ } catch (err) {
238
+ this.emit('error', err instanceof Error ? err : new Error(String(err)));
239
+ }
240
+ }
241
+
242
+ // Check if a learned skill matches — prepend instructions to system prompt
243
+ let effectiveSystemPrompt = this.systemPrompt;
244
+
245
+ // Inject long-term memory context
246
+ if (memoryContext) {
247
+ effectiveSystemPrompt = effectiveSystemPrompt + memoryContext;
248
+ }
249
+
250
+ const matchedSkill = this.skillLearner?.matchSkill(message.content);
251
+ if (matchedSkill) {
252
+ matchedSkill.usageCount++;
253
+ matchedSkill.lastUsed = new Date();
254
+ effectiveSystemPrompt = `[Learned Skill: ${matchedSkill.name}]\n${matchedSkill.instructions}\n\n${this.systemPrompt}`;
255
+ this.emit('skill:matched', matchedSkill);
256
+ if (this.tracer && rootSpan) {
257
+ this.tracer.addEvent(rootSpan, 'skill.matched', { 'skill.name': matchedSkill.name });
258
+ }
259
+ }
260
+
261
+ // Fall back to LLM with tool use loop
262
+ const tools = this.toolRegistry.list();
263
+ const llmMessages = [...context.messages];
264
+ let finalResponse = '';
265
+
266
+ for (let round = 0; round <= this.maxToolRounds; round++) {
267
+ let llmSpan: TelemetrySpan | undefined;
268
+ if (this.tracer && rootSpan) {
269
+ llmSpan = this.tracer.startSpan('llm.chat', {
270
+ parent: rootSpan,
271
+ kind: 'client',
272
+ attributes: { 'llm.round': round },
273
+ });
274
+ }
275
+
276
+ const llmResponse = await this._provider.chat(
277
+ llmMessages,
278
+ effectiveSystemPrompt,
279
+ { tools: tools.length > 0 ? tools : undefined },
280
+ );
281
+
282
+ if (this.tracer && llmSpan) {
283
+ llmSpan.attributes['llm.response.length'] = llmResponse.length;
284
+ this.tracer.endSpan(llmSpan, 'ok');
285
+ }
286
+
287
+ const toolCall = this.parseToolCall(llmResponse);
288
+ if (!toolCall || tools.length === 0 || round === this.maxToolRounds) {
289
+ finalResponse = llmResponse;
290
+ break;
291
+ }
292
+
293
+ // Execute tool
294
+ let toolSpan: TelemetrySpan | undefined;
295
+ if (this.tracer && rootSpan) {
296
+ toolSpan = this.tracer.startSpan('tool.execute', {
297
+ parent: rootSpan,
298
+ kind: 'internal',
299
+ attributes: { 'tool.name': toolCall.name },
300
+ });
301
+ }
302
+
303
+ const toolResult = await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
304
+ this.emit('tool:execute', toolCall.name, toolResult);
305
+
306
+ if (this.tracer && toolSpan) {
307
+ toolSpan.attributes['tool.result.length'] = toolResult.content?.length || 0;
308
+ this.tracer.endSpan(toolSpan, 'ok');
309
+ }
310
+
311
+ // Add tool call and result to messages for next round
312
+ llmMessages.push({
313
+ id: `tool_call_${Date.now()}`,
314
+ role: 'assistant',
315
+ content: llmResponse,
316
+ timestamp: Date.now(),
317
+ });
318
+ llmMessages.push({
319
+ id: `tool_result_${Date.now()}`,
320
+ role: 'user',
321
+ content: `[Tool Result for ${toolCall.name}]: ${toolResult.content}`,
322
+ timestamp: Date.now(),
323
+ });
324
+ }
325
+
326
+ const response = this.createResponse(finalResponse, message);
327
+ await this.memory.addMessage(sessionId, response);
328
+ this.emit('message:out', response);
329
+
330
+ // End root telemetry span
331
+ if (this.tracer && rootSpan) {
332
+ rootSpan.attributes['response.length'] = finalResponse.length;
333
+ this.tracer.endSpan(rootSpan, 'ok');
334
+ this.tracer.histogram('agent.message.duration', rootSpan.endTime! - rootSpan.startTime, { agent: this.name });
335
+ }
336
+
337
+ // === Learn from interaction ===
338
+ if (this.longTermMemory && this.longTermMemoryConfig.autoLearn) {
339
+ try {
340
+ await this.longTermMemory.learn(
341
+ `User: ${message.content}\nAssistant: ${finalResponse}`,
342
+ { tags: ['conversation', (message.metadata?.channel as string) || 'unknown'] },
343
+ );
344
+ } catch {
345
+ // Silent fail
346
+ }
347
+ }
348
+
349
+ // After response, check if we should learn a skill
350
+ if (
351
+ this.skillLearner &&
352
+ this.autoLearnConfig.enabled &&
353
+ context.messages.length >= this.autoLearnConfig.minConversationLength
354
+ ) {
355
+ this.skillLearner
356
+ .analyzeForSkillCreation(context.messages, this._provider)
357
+ .then(async (learnedSkill) => {
358
+ if (learnedSkill) {
359
+ await this.skillLearner!.saveSkill(learnedSkill);
360
+ this.emit('skill:learned', learnedSkill);
361
+ }
362
+ })
363
+ .catch(() => {});
364
+ }
365
+
366
+ // Improve matched skill after use
367
+ if (matchedSkill && this.skillLearner && this.autoLearnConfig.improveOnUse) {
368
+ this.skillLearner
369
+ .improveSkill(matchedSkill, context.messages, this._provider)
370
+ .then(() => this.skillLearner!.saveSkill(matchedSkill))
371
+ .catch(() => {});
372
+ }
373
+
374
+ return response;
375
+ }
376
+
377
+ private parseToolCall(response: string): { name: string; arguments: Record<string, unknown> } | null {
378
+ try {
379
+ const parsed = JSON.parse(response);
380
+ if (parsed.tool_call) return parsed.tool_call;
381
+ if (parsed.name && parsed.arguments !== undefined) return parsed;
382
+ } catch { /* not JSON */ }
383
+
384
+ const match = response.match(/<tool_call>\s*(\{[\s\S]*?\})\s*<\/tool_call>/);
385
+ if (match) {
386
+ try {
387
+ const parsed = JSON.parse(match[1]);
388
+ if (parsed.name) return parsed;
389
+ } catch { /* not valid JSON */ }
390
+ }
391
+ return null;
392
+ }
393
+
394
+ async *handleMessageStream(message: Message): AsyncIterable<string> {
395
+ const sessionId = (message.metadata?.sessionId as string) ?? 'default';
396
+ await this.memory.addMessage(sessionId, message);
397
+
398
+ const history = (await this.memory.getConversation(sessionId)).slice(-this.historyLimit);
399
+
400
+ let fullResponse = '';
401
+ for await (const chunk of this._provider.chatStream(history, this.systemPrompt)) {
402
+ fullResponse += chunk;
403
+ yield chunk;
404
+ }
405
+
406
+ const response = this.createResponse(fullResponse, message);
407
+ await this.memory.addMessage(sessionId, response);
408
+ this.emit('message:out', response);
409
+ }
410
+
411
+ private createResponse(content: string, inReplyTo: Message): Message {
412
+ return {
413
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
414
+ role: 'assistant',
415
+ content,
416
+ timestamp: Date.now(),
417
+ metadata: { inReplyTo: inReplyTo.id },
418
+ };
419
+ }
420
+ }