opc-agent 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +91 -32
- package/dist/channels/telegram.d.ts +30 -9
- package/dist/channels/telegram.js +125 -33
- package/dist/cli.js +415 -8
- package/dist/core/agent.d.ts +23 -0
- package/dist/core/agent.js +120 -3
- package/dist/core/runtime.d.ts +1 -0
- package/dist/core/runtime.js +44 -0
- package/dist/core/scheduler.d.ts +52 -0
- package/dist/core/scheduler.js +168 -0
- package/dist/core/subagent.d.ts +28 -0
- package/dist/core/subagent.js +65 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +134 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +17 -1
- package/dist/providers/index.d.ts +5 -1
- package/dist/providers/index.js +16 -9
- package/dist/schema/oad.d.ts +179 -4
- package/dist/schema/oad.js +12 -1
- package/dist/skills/auto-learn.d.ts +28 -0
- package/dist/skills/auto-learn.js +257 -0
- package/dist/tools/builtin/datetime.d.ts +3 -0
- package/dist/tools/builtin/datetime.js +44 -0
- package/dist/tools/builtin/file.d.ts +3 -0
- package/dist/tools/builtin/file.js +151 -0
- package/dist/tools/builtin/index.d.ts +15 -0
- package/dist/tools/builtin/index.js +30 -0
- package/dist/tools/builtin/shell.d.ts +3 -0
- package/dist/tools/builtin/shell.js +43 -0
- package/dist/tools/builtin/web.d.ts +3 -0
- package/dist/tools/builtin/web.js +37 -0
- package/dist/tools/mcp-client.d.ts +24 -0
- package/dist/tools/mcp-client.js +119 -0
- package/package.json +1 -1
- package/src/channels/telegram.ts +212 -90
- package/src/cli.ts +418 -8
- package/src/core/agent.ts +295 -152
- package/src/core/runtime.ts +47 -0
- package/src/core/scheduler.ts +187 -0
- package/src/core/subagent.ts +98 -0
- package/src/daemon.ts +96 -0
- package/src/index.ts +11 -0
- package/src/providers/index.ts +354 -339
- package/src/schema/oad.ts +167 -154
- package/src/skills/auto-learn.ts +262 -0
- package/src/tools/builtin/datetime.ts +41 -0
- package/src/tools/builtin/file.ts +107 -0
- package/src/tools/builtin/index.ts +28 -0
- package/src/tools/builtin/shell.ts +43 -0
- package/src/tools/builtin/web.ts +35 -0
- package/src/tools/mcp-client.ts +131 -0
- package/tests/auto-learn.test.ts +105 -0
- package/tests/builtin-tools.test.ts +83 -0
- package/tests/cli.test.ts +46 -0
- package/tests/subagent.test.ts +130 -0
- package/tests/telegram-discord.test.ts +60 -0
package/src/core/agent.ts
CHANGED
|
@@ -1,152 +1,295 @@
|
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
private
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this.
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
+
|
|
10
|
+
export class BaseAgent extends EventEmitter implements IAgent {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
private _state: AgentState = 'init';
|
|
13
|
+
private skills: Map<string, ISkill> = new Map();
|
|
14
|
+
private channels: IChannel[] = [];
|
|
15
|
+
private memory: MemoryStore;
|
|
16
|
+
private _provider: LLMProvider;
|
|
17
|
+
private systemPrompt: string;
|
|
18
|
+
private historyLimit: number;
|
|
19
|
+
private toolRegistry: MCPToolRegistry = new MCPToolRegistry();
|
|
20
|
+
private maxToolRounds: number;
|
|
21
|
+
private skillLearner?: SkillLearner;
|
|
22
|
+
private autoLearnConfig: { enabled: boolean; minConversationLength: number; improveOnUse: boolean };
|
|
23
|
+
private _subAgentManager?: SubAgentManager;
|
|
24
|
+
|
|
25
|
+
constructor(options: {
|
|
26
|
+
name: string;
|
|
27
|
+
systemPrompt?: string;
|
|
28
|
+
provider?: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
memory?: MemoryStore;
|
|
31
|
+
historyLimit?: number;
|
|
32
|
+
skillsDir?: string;
|
|
33
|
+
learning?: {
|
|
34
|
+
autoSkillCreation?: boolean;
|
|
35
|
+
minConversationLength?: number;
|
|
36
|
+
improveOnUse?: boolean;
|
|
37
|
+
};
|
|
38
|
+
maxToolRounds?: number;
|
|
39
|
+
}) {
|
|
40
|
+
super();
|
|
41
|
+
this.name = options.name;
|
|
42
|
+
this.systemPrompt = options.systemPrompt ?? 'You are a helpful AI agent.';
|
|
43
|
+
this.memory = options.memory ?? new InMemoryStore();
|
|
44
|
+
this._provider = createProvider(options.provider ?? 'openai', options.model);
|
|
45
|
+
this.historyLimit = options.historyLimit ?? 50;
|
|
46
|
+
this.maxToolRounds = options.maxToolRounds ?? 10;
|
|
47
|
+
this.autoLearnConfig = {
|
|
48
|
+
enabled: options.learning?.autoSkillCreation !== false,
|
|
49
|
+
minConversationLength: options.learning?.minConversationLength ?? 3,
|
|
50
|
+
improveOnUse: options.learning?.improveOnUse !== false,
|
|
51
|
+
};
|
|
52
|
+
if (options.skillsDir) {
|
|
53
|
+
this.skillLearner = new SkillLearner(options.skillsDir);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get state(): AgentState {
|
|
58
|
+
return this._state;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get provider(): LLMProvider {
|
|
62
|
+
return this._provider;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getSystemPrompt(): string {
|
|
66
|
+
return this.systemPrompt;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getMemory(): MemoryStore {
|
|
70
|
+
return this.memory;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getSkillLearner(): SkillLearner | undefined {
|
|
74
|
+
return this.skillLearner;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getToolRegistry(): MCPToolRegistry {
|
|
78
|
+
return this.toolRegistry;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
registerTool(tool: MCPTool): void {
|
|
82
|
+
this.toolRegistry.register(tool);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private transition(to: AgentState): void {
|
|
86
|
+
const from = this._state;
|
|
87
|
+
this._state = to;
|
|
88
|
+
this.emit('state:change', from, to);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async init(): Promise<void> {
|
|
92
|
+
if (this.skillLearner) {
|
|
93
|
+
await this.skillLearner.loadLearnedSkills();
|
|
94
|
+
}
|
|
95
|
+
this.transition('ready');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async start(): Promise<void> {
|
|
99
|
+
if (this._state !== 'ready') {
|
|
100
|
+
throw new Error(`Cannot start agent in state: ${this._state}`);
|
|
101
|
+
}
|
|
102
|
+
for (const channel of this.channels) {
|
|
103
|
+
channel.onMessage((msg) => this.handleMessage(msg));
|
|
104
|
+
await channel.start();
|
|
105
|
+
}
|
|
106
|
+
this.transition('running');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async stop(): Promise<void> {
|
|
110
|
+
for (const channel of this.channels) {
|
|
111
|
+
await channel.stop();
|
|
112
|
+
}
|
|
113
|
+
this.transition('stopped');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
registerSkill(skill: ISkill): void {
|
|
117
|
+
this.skills.set(skill.name, skill);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
bindChannel(channel: IChannel): void {
|
|
121
|
+
this.channels.push(channel);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getChannels(): IChannel[] {
|
|
125
|
+
return this.channels;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private getSubAgentManager(): SubAgentManager {
|
|
129
|
+
if (!this._subAgentManager) {
|
|
130
|
+
this._subAgentManager = new SubAgentManager();
|
|
131
|
+
}
|
|
132
|
+
return this._subAgentManager;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async spawnSubAgent(config: SubAgentConfig): Promise<SubAgentResult> {
|
|
136
|
+
return this.getSubAgentManager().spawn(config, this._provider);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async spawnParallel(configs: SubAgentConfig[]): Promise<SubAgentResult[]> {
|
|
140
|
+
return this.getSubAgentManager().spawnParallel(configs, this._provider);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async handleMessage(message: Message): Promise<Message> {
|
|
144
|
+
this.emit('message:in', message);
|
|
145
|
+
|
|
146
|
+
const sessionId = (message.metadata?.sessionId as string) ?? 'default';
|
|
147
|
+
await this.memory.addMessage(sessionId, message);
|
|
148
|
+
|
|
149
|
+
const context: AgentContext = {
|
|
150
|
+
agentName: this.name,
|
|
151
|
+
sessionId,
|
|
152
|
+
messages: (await this.memory.getConversation(sessionId)).slice(-this.historyLimit),
|
|
153
|
+
memory: this.memory,
|
|
154
|
+
metadata: {},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Try skills first
|
|
158
|
+
for (const [name, skill] of this.skills) {
|
|
159
|
+
try {
|
|
160
|
+
const result = await skill.execute(context, message);
|
|
161
|
+
this.emit('skill:execute', name, result);
|
|
162
|
+
if (result.handled && result.response) {
|
|
163
|
+
const response = this.createResponse(result.response, message);
|
|
164
|
+
await this.memory.addMessage(sessionId, response);
|
|
165
|
+
this.emit('message:out', response);
|
|
166
|
+
return response;
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
this.emit('error', err instanceof Error ? err : new Error(String(err)));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check if a learned skill matches — prepend instructions to system prompt
|
|
174
|
+
let effectiveSystemPrompt = this.systemPrompt;
|
|
175
|
+
const matchedSkill = this.skillLearner?.matchSkill(message.content);
|
|
176
|
+
if (matchedSkill) {
|
|
177
|
+
matchedSkill.usageCount++;
|
|
178
|
+
matchedSkill.lastUsed = new Date();
|
|
179
|
+
effectiveSystemPrompt = `[Learned Skill: ${matchedSkill.name}]\n${matchedSkill.instructions}\n\n${this.systemPrompt}`;
|
|
180
|
+
this.emit('skill:matched', matchedSkill);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Fall back to LLM with tool use loop
|
|
184
|
+
const tools = this.toolRegistry.list();
|
|
185
|
+
const llmMessages = [...context.messages];
|
|
186
|
+
let finalResponse = '';
|
|
187
|
+
|
|
188
|
+
for (let round = 0; round <= this.maxToolRounds; round++) {
|
|
189
|
+
const llmResponse = await this._provider.chat(
|
|
190
|
+
llmMessages,
|
|
191
|
+
effectiveSystemPrompt,
|
|
192
|
+
{ tools: tools.length > 0 ? tools : undefined },
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const toolCall = this.parseToolCall(llmResponse);
|
|
196
|
+
if (!toolCall || tools.length === 0 || round === this.maxToolRounds) {
|
|
197
|
+
finalResponse = llmResponse;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Execute tool
|
|
202
|
+
const toolResult = await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
|
|
203
|
+
this.emit('tool:execute', toolCall.name, toolResult);
|
|
204
|
+
|
|
205
|
+
// Add tool call and result to messages for next round
|
|
206
|
+
llmMessages.push({
|
|
207
|
+
id: `tool_call_${Date.now()}`,
|
|
208
|
+
role: 'assistant',
|
|
209
|
+
content: llmResponse,
|
|
210
|
+
timestamp: Date.now(),
|
|
211
|
+
});
|
|
212
|
+
llmMessages.push({
|
|
213
|
+
id: `tool_result_${Date.now()}`,
|
|
214
|
+
role: 'user',
|
|
215
|
+
content: `[Tool Result for ${toolCall.name}]: ${toolResult.content}`,
|
|
216
|
+
timestamp: Date.now(),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const response = this.createResponse(finalResponse, message);
|
|
221
|
+
await this.memory.addMessage(sessionId, response);
|
|
222
|
+
this.emit('message:out', response);
|
|
223
|
+
|
|
224
|
+
// After response, check if we should learn a skill
|
|
225
|
+
if (
|
|
226
|
+
this.skillLearner &&
|
|
227
|
+
this.autoLearnConfig.enabled &&
|
|
228
|
+
context.messages.length >= this.autoLearnConfig.minConversationLength
|
|
229
|
+
) {
|
|
230
|
+
this.skillLearner
|
|
231
|
+
.analyzeForSkillCreation(context.messages, this._provider)
|
|
232
|
+
.then(async (learnedSkill) => {
|
|
233
|
+
if (learnedSkill) {
|
|
234
|
+
await this.skillLearner!.saveSkill(learnedSkill);
|
|
235
|
+
this.emit('skill:learned', learnedSkill);
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
.catch(() => {});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Improve matched skill after use
|
|
242
|
+
if (matchedSkill && this.skillLearner && this.autoLearnConfig.improveOnUse) {
|
|
243
|
+
this.skillLearner
|
|
244
|
+
.improveSkill(matchedSkill, context.messages, this._provider)
|
|
245
|
+
.then(() => this.skillLearner!.saveSkill(matchedSkill))
|
|
246
|
+
.catch(() => {});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return response;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private parseToolCall(response: string): { name: string; arguments: Record<string, unknown> } | null {
|
|
253
|
+
try {
|
|
254
|
+
const parsed = JSON.parse(response);
|
|
255
|
+
if (parsed.tool_call) return parsed.tool_call;
|
|
256
|
+
if (parsed.name && parsed.arguments !== undefined) return parsed;
|
|
257
|
+
} catch { /* not JSON */ }
|
|
258
|
+
|
|
259
|
+
const match = response.match(/<tool_call>\s*(\{[\s\S]*?\})\s*<\/tool_call>/);
|
|
260
|
+
if (match) {
|
|
261
|
+
try {
|
|
262
|
+
const parsed = JSON.parse(match[1]);
|
|
263
|
+
if (parsed.name) return parsed;
|
|
264
|
+
} catch { /* not valid JSON */ }
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async *handleMessageStream(message: Message): AsyncIterable<string> {
|
|
270
|
+
const sessionId = (message.metadata?.sessionId as string) ?? 'default';
|
|
271
|
+
await this.memory.addMessage(sessionId, message);
|
|
272
|
+
|
|
273
|
+
const history = (await this.memory.getConversation(sessionId)).slice(-this.historyLimit);
|
|
274
|
+
|
|
275
|
+
let fullResponse = '';
|
|
276
|
+
for await (const chunk of this._provider.chatStream(history, this.systemPrompt)) {
|
|
277
|
+
fullResponse += chunk;
|
|
278
|
+
yield chunk;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const response = this.createResponse(fullResponse, message);
|
|
282
|
+
await this.memory.addMessage(sessionId, response);
|
|
283
|
+
this.emit('message:out', response);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private createResponse(content: string, inReplyTo: Message): Message {
|
|
287
|
+
return {
|
|
288
|
+
id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
289
|
+
role: 'assistant',
|
|
290
|
+
content,
|
|
291
|
+
timestamp: Date.now(),
|
|
292
|
+
metadata: { inReplyTo: inReplyTo.id },
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
package/src/core/runtime.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { WebSocketChannel } from '../channels/websocket';
|
|
|
7
7
|
import { DeepBrainMemoryStore } from '../memory/deepbrain';
|
|
8
8
|
import { Analytics } from '../analytics';
|
|
9
9
|
import type { OADDocument } from '../schema/oad';
|
|
10
|
+
import { Scheduler } from './scheduler';
|
|
11
|
+
import type { CronJob } from './scheduler';
|
|
10
12
|
import type { ISkill, MemoryStore, Message } from './types';
|
|
11
13
|
import type { Response } from 'express';
|
|
12
14
|
|
|
@@ -27,6 +29,7 @@ export class AgentRuntime {
|
|
|
27
29
|
private shutdownHandlers: (() => Promise<void>)[] = [];
|
|
28
30
|
private isShuttingDown = false;
|
|
29
31
|
private analytics: Analytics = new Analytics();
|
|
32
|
+
private scheduler: Scheduler | null = null;
|
|
30
33
|
|
|
31
34
|
async loadConfig(filePath: string): Promise<OADDocument> {
|
|
32
35
|
this.config = loadOAD(filePath);
|
|
@@ -122,6 +125,42 @@ export class AgentRuntime {
|
|
|
122
125
|
});
|
|
123
126
|
|
|
124
127
|
this.logger.info('Agent initialized', { name: cfg.metadata.name });
|
|
128
|
+
|
|
129
|
+
// Initialize scheduler if jobs are configured
|
|
130
|
+
const schedulerCfg = (cfg.spec as any).scheduler;
|
|
131
|
+
if (schedulerCfg?.jobs && Array.isArray(schedulerCfg.jobs) && schedulerCfg.jobs.length > 0) {
|
|
132
|
+
this.scheduler = new Scheduler(async (job: CronJob) => {
|
|
133
|
+
this.logger.info('Scheduler firing job', { name: job.name, task: job.task });
|
|
134
|
+
if (this.agent) {
|
|
135
|
+
const msg: Message = {
|
|
136
|
+
id: `cron-${job.id}-${Date.now()}`,
|
|
137
|
+
role: 'user',
|
|
138
|
+
content: job.task,
|
|
139
|
+
timestamp: Date.now(),
|
|
140
|
+
metadata: { source: 'scheduler', jobId: job.id, jobName: job.name },
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
await this.agent.handleMessage(msg);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
this.logger.error('Scheduler job failed', { name: job.name, error: err instanceof Error ? err.message : String(err) });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < schedulerCfg.jobs.length; i++) {
|
|
151
|
+
const j = schedulerCfg.jobs[i];
|
|
152
|
+
const id = j.id || j.name?.toLowerCase().replace(/\s+/g, '-') || `job-${i}`;
|
|
153
|
+
this.scheduler.addJob({
|
|
154
|
+
id,
|
|
155
|
+
name: j.name || id,
|
|
156
|
+
schedule: j.schedule,
|
|
157
|
+
task: j.task || '',
|
|
158
|
+
enabled: j.enabled !== false,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
this.logger.info('Scheduler configured', { jobs: schedulerCfg.jobs.length });
|
|
162
|
+
}
|
|
163
|
+
|
|
125
164
|
return this.agent;
|
|
126
165
|
}
|
|
127
166
|
|
|
@@ -129,12 +168,20 @@ export class AgentRuntime {
|
|
|
129
168
|
if (!this.agent) throw new Error('Agent not initialized.');
|
|
130
169
|
this.setupGracefulShutdown();
|
|
131
170
|
await this.agent.start();
|
|
171
|
+
if (this.scheduler) {
|
|
172
|
+
this.scheduler.start();
|
|
173
|
+
this.logger.info('Scheduler started');
|
|
174
|
+
}
|
|
132
175
|
this.logger.info('Agent started');
|
|
133
176
|
}
|
|
134
177
|
|
|
135
178
|
async stop(): Promise<void> {
|
|
136
179
|
if (!this.agent) return;
|
|
137
180
|
this.logger.info('Stopping agent...');
|
|
181
|
+
if (this.scheduler) {
|
|
182
|
+
this.scheduler.stop();
|
|
183
|
+
this.logger.info('Scheduler stopped');
|
|
184
|
+
}
|
|
138
185
|
await this.agent.stop();
|
|
139
186
|
for (const handler of this.shutdownHandlers) {
|
|
140
187
|
await handler();
|